Crop automation

This commit is contained in:
Connor Yoh 2025-10-01 14:16:41 +01:00
parent b5fc9fe7b9
commit 7bb7f9f32b
4 changed files with 163 additions and 68 deletions

View File

@ -0,0 +1,41 @@
/**
* CropAutomationSettings - Used for automation only
*
* Simplified crop settings for automation that doesn't require a file preview.
* Allows users to manually enter crop coordinates and dimensions.
*/
import { Stack } from "@mantine/core";
import { CropParameters } from "../../../hooks/tools/crop/useCropParameters";
import { Rectangle } from "../../../utils/cropCoordinates";
import CropCoordinateInputs from "./CropCoordinateInputs";
interface CropAutomationSettingsProps {
parameters: CropParameters;
onParameterChange: <K extends keyof CropParameters>(key: K, value: CropParameters[K]) => void;
disabled?: boolean;
}
const CropAutomationSettings = ({ parameters, onParameterChange, disabled = false }: CropAutomationSettingsProps) => {
// Handle coordinate changes
const handleCoordinateChange = (field: keyof Rectangle, value: number | string) => {
const numValue = typeof value === 'string' ? parseFloat(value) : value;
if (isNaN(numValue)) return;
const newCropArea = { ...parameters.cropArea, [field]: numValue };
onParameterChange('cropArea', newCropArea);
};
return (
<Stack gap="md">
<CropCoordinateInputs
cropArea={parameters.cropArea}
onCoordinateChange={handleCoordinateChange}
disabled={disabled}
showAutomationInfo={true}
/>
</Stack>
);
};
export default CropAutomationSettings;

View File

@ -0,0 +1,101 @@
import { Stack, Text, Group, NumberInput, Alert } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { Rectangle, PDFBounds } from "../../../utils/cropCoordinates";
interface CropCoordinateInputsProps {
cropArea: Rectangle;
onCoordinateChange: (field: keyof Rectangle, value: number | string) => void;
disabled?: boolean;
pdfBounds?: PDFBounds;
showAutomationInfo?: boolean;
}
const CropCoordinateInputs = ({
cropArea,
onCoordinateChange,
disabled = false,
pdfBounds,
showAutomationInfo = false
}: CropCoordinateInputsProps) => {
const { t } = useTranslation();
return (
<Stack gap="xs">
{showAutomationInfo && (
<Alert color="blue" variant="light">
<Text size="xs">
{t("crop.automation.info", "Enter crop coordinates in PDF points. Origin (0,0) is at bottom-left. These values will be applied to all PDFs processed in this automation.")}
</Text>
</Alert>
)}
<Text size="sm" fw={500}>
{t("crop.coordinates.title", "Position and Size")}
</Text>
<Group grow>
<NumberInput
label={t("crop.coordinates.x", "X Position")}
description={showAutomationInfo ? t("crop.coordinates.x.desc", "Left edge (points)") : undefined}
value={Math.round(cropArea.x * 10) / 10}
onChange={(value) => onCoordinateChange('x', value)}
disabled={disabled}
min={0}
max={pdfBounds?.actualWidth}
step={0.1}
decimalScale={1}
size={showAutomationInfo ? "sm" : "xs"}
/>
<NumberInput
label={t("crop.coordinates.y", "Y Position")}
description={showAutomationInfo ? t("crop.coordinates.y.desc", "Bottom edge (points)") : undefined}
value={Math.round(cropArea.y * 10) / 10}
onChange={(value) => onCoordinateChange('y', value)}
disabled={disabled}
min={0}
max={pdfBounds?.actualHeight}
step={0.1}
decimalScale={1}
size={showAutomationInfo ? "sm" : "xs"}
/>
</Group>
<Group grow>
<NumberInput
label={t("crop.coordinates.width", "Width")}
description={showAutomationInfo ? t("crop.coordinates.width.desc", "Crop width (points)") : undefined}
value={Math.round(cropArea.width * 10) / 10}
onChange={(value) => onCoordinateChange('width', value)}
disabled={disabled}
min={0.1}
max={pdfBounds?.actualWidth}
step={0.1}
decimalScale={1}
size={showAutomationInfo ? "sm" : "xs"}
/>
<NumberInput
label={t("crop.coordinates.height", "Height")}
description={showAutomationInfo ? t("crop.coordinates.height.desc", "Crop height (points)") : undefined}
value={Math.round(cropArea.height * 10) / 10}
onChange={(value) => onCoordinateChange('height', value)}
disabled={disabled}
min={0.1}
max={pdfBounds?.actualHeight}
step={0.1}
decimalScale={1}
size={showAutomationInfo ? "sm" : "xs"}
/>
</Group>
{showAutomationInfo && (
<Alert color="gray" variant="light">
<Text size="xs">
{t("crop.automation.reference", "Reference: A4 page is 595.28 × 841.89 points (210mm × 297mm). 1 inch = 72 points.")}
</Text>
</Alert>
)}
</Stack>
);
};
export default CropCoordinateInputs;

View File

@ -1,10 +1,11 @@
import { useMemo, useState, useEffect } from "react";
import { Stack, Text, Box, Group, NumberInput, ActionIcon, Center, Alert } from "@mantine/core";
import { Stack, Text, Box, Group, ActionIcon, Center, Alert } from "@mantine/core";
import { useTranslation } from "react-i18next";
import RestartAltIcon from "@mui/icons-material/RestartAlt";
import { CropParametersHook } from "../../../hooks/tools/crop/useCropParameters";
import { useSelectedFiles } from "../../../contexts/file/fileHooks";
import CropAreaSelector from "./CropAreaSelector";
import CropCoordinateInputs from "./CropCoordinateInputs";
import { DEFAULT_CROP_AREA } from "../../../constants/cropConstants";
import { PAGE_SIZES } from "../../../constants/pageSizeConstants";
import {
@ -190,71 +191,22 @@ const CropSettings = ({ parameters, disabled = false }: CropSettingsProps) => {
</Stack>
{/* Manual Coordinate Input */}
<Stack gap="xs">
<Text size="sm" fw={500}>
{t("crop.coordinates.title", "Position and Size")}
</Text>
<CropCoordinateInputs
cropArea={cropArea}
onCoordinateChange={handleCoordinateChange}
disabled={disabled}
pdfBounds={pdfBounds}
showAutomationInfo={false}
/>
<Group grow>
<NumberInput
label={t("crop.coordinates.x", "X Position")}
value={Math.round(cropArea.x * 10) / 10}
onChange={(value) => handleCoordinateChange('x', value)}
disabled={disabled}
min={0}
max={pdfBounds.actualWidth}
step={0.1}
decimalScale={1}
size="xs"
/>
<NumberInput
label={t("crop.coordinates.y", "Y Position")}
value={Math.round(cropArea.y * 10) / 10}
onChange={(value) => handleCoordinateChange('y', value)}
disabled={disabled}
min={0}
max={pdfBounds.actualHeight}
step={0.1}
decimalScale={1}
size="xs"
/>
</Group>
<Group grow>
<NumberInput
label={t("crop.coordinates.width", "Width")}
value={Math.round(cropArea.width * 10) / 10}
onChange={(value) => handleCoordinateChange('width', value)}
disabled={disabled}
min={0.1}
max={pdfBounds.actualWidth}
step={0.1}
decimalScale={1}
size="xs"
/>
<NumberInput
label={t("crop.coordinates.height", "Height")}
value={Math.round(cropArea.height * 10) / 10}
onChange={(value) => handleCoordinateChange('height', value)}
disabled={disabled}
min={0.1}
max={pdfBounds.actualHeight}
step={0.1}
decimalScale={1}
size="xs"
/>
</Group>
{/* Validation Alert */}
{!isCropValid && (
<Alert color="red" variant="light">
<Text size="xs">
{t("crop.error.invalidArea", "Crop area extends beyond PDF boundaries")}
</Text>
</Alert>
)}
</Stack>
{/* Validation Alert */}
{!isCropValid && (
<Alert color="red" variant="light">
<Text size="xs">
{t("crop.error.invalidArea", "Crop area extends beyond PDF boundaries")}
</Text>
</Alert>
)}
</Stack>
);
};

View File

@ -70,7 +70,6 @@ import AddWatermarkSingleStepSettings from "../components/tools/addWatermark/Add
import OCRSettings from "../components/tools/ocr/OCRSettings";
import ConvertSettings from "../components/tools/convert/ConvertSettings";
import ChangePermissionsSettings from "../components/tools/changePermissions/ChangePermissionsSettings";
import CertificateTypeSettings from "../components/tools/certSign/CertificateTypeSettings";
import BookletImpositionSettings from "../components/tools/bookletImposition/BookletImpositionSettings";
import FlattenSettings from "../components/tools/flatten/FlattenSettings";
import RedactSingleStepSettings from "../components/tools/redact/RedactSingleStepSettings";
@ -95,6 +94,7 @@ import ExtractImagesSettings from "../components/tools/extractImages/ExtractImag
import ReplaceColorSettings from "../components/tools/replaceColor/ReplaceColorSettings";
import AddStampAutomationSettings from "../components/tools/addStamp/AddStampAutomationSettings";
import CertSignAutomationSettings from "../components/tools/certSign/CertSignAutomationSettings";
import CropAutomationSettings from "../components/tools/crop/CropAutomationSettings";
const showPlaceholderTools = true; // Show all tools; grey out unavailable ones in UI
@ -235,7 +235,8 @@ export function useFlatToolRegistry(): ToolRegistry {
subcategoryId: SubcategoryId.SIGNING,
operationConfig: signOperationConfig,
automationSettings: SignSettings, // TODO:: not all settings shown, suggested next tools shown
synonyms: getSynonyms(t, "sign")
synonyms: getSynonyms(t, "sign"),
supportsAutomate: false, //TODO make support Sign
},
// Document Security
@ -395,7 +396,7 @@ export function useFlatToolRegistry(): ToolRegistry {
maxFiles: -1,
endpoints: ["crop"],
operationConfig: cropOperationConfig,
automationSettings: CropSettings, //TODO: Implement CropSettings
automationSettings: CropAutomationSettings,
},
rotate: {
icon: <LocalIcon icon="rotate-right-rounded" width="1.5rem" height="1.5rem" />,