mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-18 01:19:24 +00:00
Refactor into tool steps
This commit is contained in:
parent
6d04a80216
commit
55b8455b66
@ -1143,6 +1143,9 @@
|
||||
"true": "True",
|
||||
"false": "False"
|
||||
},
|
||||
"advanced": {
|
||||
"title": "Advanced Options"
|
||||
},
|
||||
"customFields": {
|
||||
"title": "Custom Metadata",
|
||||
"description": "Add custom metadata fields to the document",
|
||||
@ -1185,6 +1188,27 @@
|
||||
"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": "Delete All Metadata",
|
||||
"text": "Complete metadata removal for privacy and clean documents."
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1,246 +0,0 @@
|
||||
import { Stack, TextInput, Select, Checkbox, Button, Group, Divider, Text } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ChangeMetadataParameters } from "../../../hooks/tools/changeMetadata/useChangeMetadataParameters";
|
||||
import { TrappedStatus } from "../../../types/metadata";
|
||||
import { PDFMetadataService } from "../../../services/pdfMetadataService";
|
||||
import { useSelectedFiles } from "../../../contexts/file/fileHooks";
|
||||
|
||||
interface ChangeMetadataSettingsProps {
|
||||
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 ChangeMetadataSettings = ({
|
||||
parameters,
|
||||
onParameterChange,
|
||||
disabled = false,
|
||||
addCustomMetadata,
|
||||
removeCustomMetadata,
|
||||
updateCustomMetadata
|
||||
}: ChangeMetadataSettingsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { selectedFiles } = useSelectedFiles();
|
||||
const [isExtractingMetadata, setIsExtractingMetadata] = useState(false);
|
||||
const [hasExtractedMetadata, setHasExtractedMetadata] = useState(false);
|
||||
|
||||
const isDeleteAllEnabled = parameters.deleteAll;
|
||||
const fieldsDisabled = disabled || isDeleteAllEnabled || isExtractingMetadata;
|
||||
|
||||
// Extract metadata from first file when files change
|
||||
useEffect(() => {
|
||||
const extractMetadata = async () => {
|
||||
if (selectedFiles.length === 0 || hasExtractedMetadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstFile = selectedFiles[0];
|
||||
if (!firstFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsExtractingMetadata(true);
|
||||
try {
|
||||
const result = await PDFMetadataService.extractMetadata(firstFile);
|
||||
|
||||
if (result.success) {
|
||||
const metadata = result.metadata;
|
||||
|
||||
// Pre-populate all fields with extracted metadata
|
||||
onParameterChange('title', metadata.title);
|
||||
onParameterChange('author', metadata.author);
|
||||
onParameterChange('subject', metadata.subject);
|
||||
onParameterChange('keywords', metadata.keywords);
|
||||
onParameterChange('creator', metadata.creator);
|
||||
onParameterChange('producer', metadata.producer);
|
||||
onParameterChange('creationDate', metadata.creationDate);
|
||||
onParameterChange('modificationDate', metadata.modificationDate);
|
||||
onParameterChange('trapped', metadata.trapped);
|
||||
|
||||
// Set custom metadata entries directly to avoid state update timing issues
|
||||
onParameterChange('customMetadata', metadata.customMetadata);
|
||||
|
||||
setHasExtractedMetadata(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to extract metadata:', error);
|
||||
} finally {
|
||||
setIsExtractingMetadata(false);
|
||||
}
|
||||
};
|
||||
|
||||
extractMetadata();
|
||||
}, [selectedFiles, hasExtractedMetadata, onParameterChange, addCustomMetadata, updateCustomMetadata, removeCustomMetadata, parameters.customMetadata]);
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
{/* Delete All Option */}
|
||||
<Checkbox
|
||||
label={t('changeMetadata.deleteAll.label', 'Delete all metadata')}
|
||||
description={t('changeMetadata.deleteAll.description', 'Remove all metadata from the PDF document')}
|
||||
checked={parameters.deleteAll}
|
||||
onChange={(e) => onParameterChange('deleteAll', e.target.checked)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Standard Metadata Fields */}
|
||||
<Stack gap="md">
|
||||
<Text size="sm" fw={500}>
|
||||
{t('changeMetadata.standardFields.title', 'Standard Metadata')}
|
||||
</Text>
|
||||
|
||||
<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={fieldsDisabled}
|
||||
/>
|
||||
|
||||
<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={fieldsDisabled}
|
||||
/>
|
||||
|
||||
<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={fieldsDisabled}
|
||||
/>
|
||||
|
||||
<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={fieldsDisabled}
|
||||
/>
|
||||
|
||||
<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={fieldsDisabled}
|
||||
/>
|
||||
|
||||
<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={fieldsDisabled}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Date Fields */}
|
||||
<Text size="sm" fw={500}>
|
||||
{t('changeMetadata.dates.title', 'Document Dates')}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
{t('changeMetadata.dates.format', 'Format: yyyy/MM/dd HH:mm:ss')}
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
label={t('changeMetadata.creationDate.label', 'Creation Date')}
|
||||
placeholder={t('changeMetadata.creationDate.placeholder', 'e.g. 2025/01/17 14:30:00')}
|
||||
value={parameters.creationDate}
|
||||
onChange={(e) => onParameterChange('creationDate', e.target.value)}
|
||||
disabled={fieldsDisabled}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label={t('changeMetadata.modificationDate.label', 'Modification Date')}
|
||||
placeholder={t('changeMetadata.modificationDate.placeholder', 'e.g. 2025/01/17 14:30:00')}
|
||||
value={parameters.modificationDate}
|
||||
onChange={(e) => onParameterChange('modificationDate', e.target.value)}
|
||||
disabled={fieldsDisabled}
|
||||
/>
|
||||
|
||||
{/* Trapped Status */}
|
||||
<Select
|
||||
label={t('changeMetadata.trapped.label', 'Trapped Status')}
|
||||
description={t('changeMetadata.trapped.description', 'Indicates whether the document has been trapped for high-quality printing')}
|
||||
value={parameters.trapped}
|
||||
onChange={(value) => {
|
||||
if (value) {
|
||||
onParameterChange('trapped', value as TrappedStatus);
|
||||
}
|
||||
}}
|
||||
disabled={fieldsDisabled}
|
||||
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 */}
|
||||
<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={fieldsDisabled}
|
||||
>
|
||||
{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={fieldsDisabled}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder={t('changeMetadata.customFields.valuePlaceholder', 'Custom value')}
|
||||
value={entry.value}
|
||||
onChange={(e) => updateCustomMetadata(entry.id, entry.key, e.target.value)}
|
||||
disabled={fieldsDisabled}
|
||||
/>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="light"
|
||||
color="red"
|
||||
onClick={() => removeCustomMetadata(entry.id)}
|
||||
disabled={fieldsDisabled}
|
||||
>
|
||||
{t('changeMetadata.customFields.remove', 'Remove')}
|
||||
</Button>
|
||||
</Stack>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChangeMetadataSettings;
|
@ -0,0 +1,121 @@
|
||||
import { Stack, Divider, Text } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ChangeMetadataParameters } 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 CustomMetadataStep from "./steps/CustomMetadataStep";
|
||||
import AdvancedOptionsStep from "./steps/AdvancedOptionsStep";
|
||||
|
||||
interface ChangeMetadataSingleStepProps {
|
||||
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 ChangeMetadataSingleStep = ({
|
||||
parameters,
|
||||
onParameterChange,
|
||||
disabled = false,
|
||||
addCustomMetadata,
|
||||
removeCustomMetadata,
|
||||
updateCustomMetadata
|
||||
}: ChangeMetadataSingleStepProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Create a params object that matches the hook interface
|
||||
const paramsHook = {
|
||||
parameters,
|
||||
updateParameter: onParameterChange,
|
||||
addCustomMetadata,
|
||||
removeCustomMetadata,
|
||||
updateCustomMetadata,
|
||||
};
|
||||
|
||||
// Extract metadata from uploaded files
|
||||
const { isExtractingMetadata } = useMetadataExtraction(paramsHook);
|
||||
|
||||
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 />
|
||||
|
||||
{/* Custom Metadata */}
|
||||
<Stack gap="md">
|
||||
<Text size="sm" fw={500}>
|
||||
{t('changeMetadata.customFields.title', 'Custom Metadata')}
|
||||
</Text>
|
||||
<CustomMetadataStep
|
||||
parameters={parameters}
|
||||
onParameterChange={onParameterChange}
|
||||
disabled={fieldsDisabled}
|
||||
addCustomMetadata={addCustomMetadata}
|
||||
removeCustomMetadata={removeCustomMetadata}
|
||||
updateCustomMetadata={updateCustomMetadata}
|
||||
/>
|
||||
</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}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChangeMetadataSingleStep;
|
@ -0,0 +1,39 @@
|
||||
import { Select } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ChangeMetadataParameters } from "../../../../hooks/tools/changeMetadata/useChangeMetadataParameters";
|
||||
import { TrappedStatus } from "../../../../types/metadata";
|
||||
|
||||
interface AdvancedOptionsStepProps {
|
||||
parameters: ChangeMetadataParameters;
|
||||
onParameterChange: <K extends keyof ChangeMetadataParameters>(key: K, value: ChangeMetadataParameters[K]) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const AdvancedOptionsStep = ({
|
||||
parameters,
|
||||
onParameterChange,
|
||||
disabled = false
|
||||
}: AdvancedOptionsStepProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Select
|
||||
label={t('changeMetadata.trapped.label', 'Trapped Status')}
|
||||
description={t('changeMetadata.trapped.description', 'Indicates whether the document has been trapped for high-quality printing')}
|
||||
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') }
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdvancedOptionsStep;
|
@ -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;
|
@ -0,0 +1,29 @@
|
||||
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.label', 'Delete all metadata')}
|
||||
description={t('changeMetadata.deleteAll.description', 'Remove all metadata from the PDF document')}
|
||||
checked={parameters.deleteAll}
|
||||
onChange={(e) => onParameterChange('deleteAll', e.target.checked)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteAllStep;
|
@ -0,0 +1,43 @@
|
||||
import { Stack, TextInput, Text } from "@mantine/core";
|
||||
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">
|
||||
<Text size="xs" c="dimmed">
|
||||
{t('changeMetadata.dates.format', 'Format: yyyy/MM/dd HH:mm:ss')}
|
||||
</Text>
|
||||
|
||||
<TextInput
|
||||
label={t('changeMetadata.creationDate.label', 'Creation Date')}
|
||||
placeholder={t('changeMetadata.creationDate.placeholder', 'e.g. 2025/01/17 14:30:00')}
|
||||
value={parameters.creationDate}
|
||||
onChange={(e) => onParameterChange('creationDate', e.target.value)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
label={t('changeMetadata.modificationDate.label', 'Modification Date')}
|
||||
placeholder={t('changeMetadata.modificationDate.placeholder', 'e.g. 2025/01/17 14:30:00')}
|
||||
value={parameters.modificationDate}
|
||||
onChange={(e) => onParameterChange('modificationDate', e.target.value)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default DocumentDatesStep;
|
@ -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;
|
@ -1,12 +1,28 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TooltipContent } from '../../types/tips';
|
||||
|
||||
export const useChangeMetadataTips = (): TooltipContent => {
|
||||
export const useDeleteAllTips = (): TooltipContent => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return {
|
||||
header: {
|
||||
title: t("changeMetadata.tooltip.header.title", "PDF Metadata Overview")
|
||||
title: t("changeMetadata.tooltip.deleteAll.title", "Delete All Metadata")
|
||||
},
|
||||
tips: [
|
||||
{
|
||||
title: t("changeMetadata.tooltip.deleteAll.title", "Delete All Metadata"),
|
||||
description: t("changeMetadata.tooltip.deleteAll.text", "Complete metadata removal for privacy and clean documents."),
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
export const useStandardMetadataTips = (): TooltipContent => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return {
|
||||
header: {
|
||||
title: t("changeMetadata.tooltip.standardFields.title", "Standard Fields")
|
||||
},
|
||||
tips: [
|
||||
{
|
||||
@ -19,7 +35,19 @@ export const useChangeMetadataTips = (): TooltipContent => {
|
||||
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: [
|
||||
{
|
||||
title: t("changeMetadata.tooltip.dates.title", "Date Fields"),
|
||||
description: t("changeMetadata.tooltip.dates.text", "When the document was created and modified."),
|
||||
@ -28,14 +56,47 @@ export const useChangeMetadataTips = (): TooltipContent => {
|
||||
t("changeMetadata.tooltip.dates.bullet2", "Modification Date: When last changed"),
|
||||
t("changeMetadata.tooltip.dates.bullet3", "Format: yyyy/MM/dd HH:mm:ss (e.g., 2025/01/17 14:30:00)")
|
||||
]
|
||||
},
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
export const useCustomMetadataTips = (): TooltipContent => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return {
|
||||
header: {
|
||||
title: t("changeMetadata.tooltip.customFields.title", "Custom Metadata")
|
||||
},
|
||||
tips: [
|
||||
{
|
||||
title: t("changeMetadata.tooltip.options.title", "Additional Options"),
|
||||
description: t("changeMetadata.tooltip.options.text", "Custom fields and privacy controls."),
|
||||
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.options.bullet1", "Custom Metadata: Add your own key-value pairs"),
|
||||
t("changeMetadata.tooltip.options.bullet2", "Trapped Status: High-quality printing setting"),
|
||||
t("changeMetadata.tooltip.options.bullet3", "Delete All: Remove all metadata for privacy")
|
||||
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")
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -53,7 +53,7 @@ import RedactSingleStepSettings from "../components/tools/redact/RedactSingleSte
|
||||
import Redact from "../tools/Redact";
|
||||
import { ToolId } from "../types/toolId";
|
||||
import MergeSettings from '../components/tools/merge/MergeSettings';
|
||||
import ChangeMetadataSettings from "../components/tools/changeMetadata/ChangeMetadataSettings";
|
||||
import ChangeMetadataSingleStep from "../components/tools/changeMetadata/ChangeMetadataSingleStep";
|
||||
|
||||
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
|
||||
|
||||
@ -299,7 +299,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
||||
maxFiles: -1,
|
||||
endpoints: ["update-metadata"],
|
||||
operationConfig: changeMetadataOperationConfig,
|
||||
settingsComponent: ChangeMetadataSettings,
|
||||
settingsComponent: ChangeMetadataSingleStep,
|
||||
},
|
||||
// Page Formatting
|
||||
|
||||
|
@ -0,0 +1,60 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { PDFMetadataService } from "../../../services/pdfMetadataService";
|
||||
import { useSelectedFiles } from "../../../contexts/file/fileHooks";
|
||||
import { ChangeMetadataParametersHook } from "./useChangeMetadataParameters";
|
||||
|
||||
export const useMetadataExtraction = (params: ChangeMetadataParametersHook) => {
|
||||
const { selectedFiles } = useSelectedFiles();
|
||||
const [isExtractingMetadata, setIsExtractingMetadata] = useState(false);
|
||||
const [hasExtractedMetadata, setHasExtractedMetadata] = useState(false);
|
||||
|
||||
// Extract metadata from first file when files change
|
||||
useEffect(() => {
|
||||
const extractMetadata = async () => {
|
||||
if (selectedFiles.length === 0 || hasExtractedMetadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstFile = selectedFiles[0];
|
||||
if (!firstFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsExtractingMetadata(true);
|
||||
try {
|
||||
const result = await PDFMetadataService.extractMetadata(firstFile);
|
||||
|
||||
if (result.success) {
|
||||
const metadata = result.metadata;
|
||||
|
||||
// Pre-populate all fields with extracted metadata
|
||||
params.updateParameter('title', metadata.title);
|
||||
params.updateParameter('author', metadata.author);
|
||||
params.updateParameter('subject', metadata.subject);
|
||||
params.updateParameter('keywords', metadata.keywords);
|
||||
params.updateParameter('creator', metadata.creator);
|
||||
params.updateParameter('producer', metadata.producer);
|
||||
params.updateParameter('creationDate', metadata.creationDate);
|
||||
params.updateParameter('modificationDate', metadata.modificationDate);
|
||||
params.updateParameter('trapped', metadata.trapped);
|
||||
|
||||
// Set custom metadata entries directly to avoid state update timing issues
|
||||
params.updateParameter('customMetadata', metadata.customMetadata);
|
||||
|
||||
setHasExtractedMetadata(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to extract metadata:', error);
|
||||
} finally {
|
||||
setIsExtractingMetadata(false);
|
||||
}
|
||||
};
|
||||
|
||||
extractMetadata();
|
||||
}, [selectedFiles, hasExtractedMetadata, params]);
|
||||
|
||||
return {
|
||||
isExtractingMetadata,
|
||||
hasExtractedMetadata,
|
||||
};
|
||||
};
|
@ -1,15 +1,40 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||
import ChangeMetadataSettings from "../components/tools/changeMetadata/ChangeMetadataSettings";
|
||||
import DeleteAllStep from "../components/tools/changeMetadata/steps/DeleteAllStep";
|
||||
import StandardMetadataStep from "../components/tools/changeMetadata/steps/StandardMetadataStep";
|
||||
import DocumentDatesStep from "../components/tools/changeMetadata/steps/DocumentDatesStep";
|
||||
import CustomMetadataStep from "../components/tools/changeMetadata/steps/CustomMetadataStep";
|
||||
import AdvancedOptionsStep from "../components/tools/changeMetadata/steps/AdvancedOptionsStep";
|
||||
import { useChangeMetadataParameters } from "../hooks/tools/changeMetadata/useChangeMetadataParameters";
|
||||
import { useChangeMetadataOperation } from "../hooks/tools/changeMetadata/useChangeMetadataOperation";
|
||||
import { useMetadataExtraction } from "../hooks/tools/changeMetadata/useMetadataExtraction";
|
||||
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
|
||||
import { BaseToolProps, ToolComponent } from "../types/tool";
|
||||
import { useChangeMetadataTips } from "../components/tooltips/useChangeMetadataTips";
|
||||
import {
|
||||
useDeleteAllTips,
|
||||
useStandardMetadataTips,
|
||||
useDocumentDatesTips,
|
||||
useCustomMetadataTips,
|
||||
useAdvancedOptionsTips
|
||||
} from "../components/tooltips/useChangeMetadataTips";
|
||||
|
||||
const ChangeMetadata = (props: BaseToolProps) => {
|
||||
const { t } = useTranslation();
|
||||
const changeMetadataTips = useChangeMetadataTips();
|
||||
|
||||
// Individual tooltips for each step
|
||||
const deleteAllTips = useDeleteAllTips();
|
||||
const standardMetadataTips = useStandardMetadataTips();
|
||||
const documentDatesTips = useDocumentDatesTips();
|
||||
const customMetadataTips = useCustomMetadataTips();
|
||||
const advancedOptionsTips = useAdvancedOptionsTips();
|
||||
|
||||
// Individual step collapse states
|
||||
const [deleteAllCollapsed, setDeleteAllCollapsed] = useState(false);
|
||||
const [standardMetadataCollapsed, setStandardMetadataCollapsed] = useState(false);
|
||||
const [documentDatesCollapsed, setDocumentDatesCollapsed] = useState(true);
|
||||
const [customMetadataCollapsed, setCustomMetadataCollapsed] = useState(true);
|
||||
const [advancedOptionsCollapsed, setAdvancedOptionsCollapsed] = useState(true);
|
||||
|
||||
const base = useBaseTool(
|
||||
'changeMetadata',
|
||||
@ -18,6 +43,14 @@ const ChangeMetadata = (props: BaseToolProps) => {
|
||||
props,
|
||||
);
|
||||
|
||||
// Extract metadata from uploaded files
|
||||
const { isExtractingMetadata } = useMetadataExtraction(base.params);
|
||||
|
||||
// Compute actual collapsed state based on results and user state
|
||||
const getActualCollapsedState = (userCollapsed: boolean) => {
|
||||
return (!base.hasFiles || base.hasResults) ? true : userCollapsed; // Force collapse when results are shown
|
||||
};
|
||||
|
||||
return createToolFlow({
|
||||
files: {
|
||||
selectedFiles: base.selectedFiles,
|
||||
@ -25,21 +58,83 @@ const ChangeMetadata = (props: BaseToolProps) => {
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
title: t("changeMetadata.settings.title", "Metadata Settings"),
|
||||
isCollapsed: base.settingsCollapsed,
|
||||
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
|
||||
tooltip: changeMetadataTips,
|
||||
title: t("changeMetadata.deleteAll.label", "Delete All Metadata"),
|
||||
isCollapsed: getActualCollapsedState(deleteAllCollapsed),
|
||||
onCollapsedClick: base.hasResults
|
||||
? (base.settingsCollapsed ? base.handleSettingsReset : undefined)
|
||||
: () => setDeleteAllCollapsed(!deleteAllCollapsed),
|
||||
tooltip: deleteAllTips,
|
||||
content: (
|
||||
<ChangeMetadataSettings
|
||||
<DeleteAllStep
|
||||
parameters={base.params.parameters}
|
||||
onParameterChange={base.params.updateParameter}
|
||||
disabled={base.endpointLoading}
|
||||
disabled={base.endpointLoading || isExtractingMetadata}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("changeMetadata.standardFields.title", "Standard Metadata"),
|
||||
isCollapsed: getActualCollapsedState(standardMetadataCollapsed),
|
||||
onCollapsedClick: base.hasResults
|
||||
? (base.settingsCollapsed ? base.handleSettingsReset : undefined)
|
||||
: () => setStandardMetadataCollapsed(!standardMetadataCollapsed),
|
||||
tooltip: standardMetadataTips,
|
||||
content: (
|
||||
<StandardMetadataStep
|
||||
parameters={base.params.parameters}
|
||||
onParameterChange={base.params.updateParameter}
|
||||
disabled={base.endpointLoading || base.params.parameters.deleteAll || isExtractingMetadata}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("changeMetadata.dates.title", "Document Dates"),
|
||||
isCollapsed: getActualCollapsedState(documentDatesCollapsed),
|
||||
onCollapsedClick: base.hasResults
|
||||
? (base.settingsCollapsed ? base.handleSettingsReset : undefined)
|
||||
: () => setDocumentDatesCollapsed(!documentDatesCollapsed),
|
||||
tooltip: documentDatesTips,
|
||||
content: (
|
||||
<DocumentDatesStep
|
||||
parameters={base.params.parameters}
|
||||
onParameterChange={base.params.updateParameter}
|
||||
disabled={base.endpointLoading || base.params.parameters.deleteAll || isExtractingMetadata}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("changeMetadata.customFields.title", "Custom Metadata"),
|
||||
isCollapsed: getActualCollapsedState(customMetadataCollapsed),
|
||||
onCollapsedClick: base.hasResults
|
||||
? (base.settingsCollapsed ? base.handleSettingsReset : undefined)
|
||||
: () => setCustomMetadataCollapsed(!customMetadataCollapsed),
|
||||
tooltip: customMetadataTips,
|
||||
content: (
|
||||
<CustomMetadataStep
|
||||
parameters={base.params.parameters}
|
||||
onParameterChange={base.params.updateParameter}
|
||||
disabled={base.endpointLoading || base.params.parameters.deleteAll || isExtractingMetadata}
|
||||
addCustomMetadata={base.params.addCustomMetadata}
|
||||
removeCustomMetadata={base.params.removeCustomMetadata}
|
||||
updateCustomMetadata={base.params.updateCustomMetadata}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("changeMetadata.advanced.title", "Advanced Options"),
|
||||
isCollapsed: getActualCollapsedState(advancedOptionsCollapsed),
|
||||
onCollapsedClick: base.hasResults
|
||||
? (base.settingsCollapsed ? base.handleSettingsReset : undefined)
|
||||
: () => setAdvancedOptionsCollapsed(!advancedOptionsCollapsed),
|
||||
tooltip: advancedOptionsTips,
|
||||
content: (
|
||||
<AdvancedOptionsStep
|
||||
parameters={base.params.parameters}
|
||||
onParameterChange={base.params.updateParameter}
|
||||
disabled={base.endpointLoading || isExtractingMetadata}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
executeButton: {
|
||||
text: t("changeMetadata.submit", "Update Metadata"),
|
||||
|
Loading…
x
Reference in New Issue
Block a user