mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-01-14 20:11:17 +01:00
[V2] feat(crop): add auto-crop whitespace option to crop tool UI (#5275)
# Description of Changes This pull request adds an auto-crop feature to the PDF crop tool, allowing users to automatically crop whitespace from PDFs. The UI now includes an "Auto-crop whitespace" checkbox, and when enabled, manual crop controls are hidden. The crop operation logic and form data submission have been updated to support this new option. **Auto-crop Feature Implementation** * Added an `autoCrop` boolean parameter to the `CropParameters` interface and set its default value to `false` in `useCropParameters.ts`. * Updated the crop operation logic in `useCropOperation.ts` to include the `autoCrop` parameter in the form data and only send manual crop coordinates if `autoCrop` is disabled. **User Interface Updates** * Added an "Auto-crop whitespace" checkbox to the crop settings UI in `CropSettings.tsx`, which toggles the auto-crop feature * Modified the crop settings UI to hide manual crop controls and validation alerts when auto-crop is enabled **Localization** * Added a new translation string for "Auto-crop whitespace" in the English locale file `translation.toml`. <img width="363" height="998" alt="image" src="https://github.com/user-attachments/assets/a92988b8-eea0-47e7-961f-b4a6e018ff2f" /> <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [X] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [X] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [X] I have performed a self-review of my own code - [X] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [X] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [X] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. Signed-off-by: Balázs Szücs <bszucs1209@gmail.com>
This commit is contained in:
parent
d97820c232
commit
43a5f72b01
@ -2968,6 +2968,7 @@ header = "Crop PDF"
|
||||
submit = "Apply Crop"
|
||||
noFileSelected = "Select a PDF file to begin cropping"
|
||||
reset = "Reset to full PDF"
|
||||
autoCrop = "Auto-crop whitespace"
|
||||
|
||||
[crop.preview]
|
||||
title = "Crop Area Selection"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useMemo, useState, useEffect } from "react";
|
||||
import { Stack, Text, Box, Group, ActionIcon, Center, Alert } from "@mantine/core";
|
||||
import { Stack, Text, Box, Group, ActionIcon, Center, Alert, Checkbox } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import RestartAltIcon from "@mui/icons-material/RestartAlt";
|
||||
import { CropParametersHook } from "@app/hooks/tools/crop/useCropParameters";
|
||||
@ -151,69 +151,81 @@ const CropSettings = ({ parameters, disabled = false }: CropSettingsProps) => {
|
||||
|
||||
return (
|
||||
<Stack gap="md" data-tour="crop-settings">
|
||||
{/* PDF Preview with Crop Selector */}
|
||||
<Stack gap="xs">
|
||||
<Group justify="space-between" align="center">
|
||||
<Text size="sm" fw={500}>
|
||||
{t("crop.preview.title", "Crop Area Selection")}
|
||||
</Text>
|
||||
<ActionIcon
|
||||
variant="outline"
|
||||
onClick={handleReset}
|
||||
disabled={disabled || isFullCrop}
|
||||
title={t("crop.reset", "Reset to full PDF")}
|
||||
aria-label={t("crop.reset", "Reset to full PDF")}
|
||||
>
|
||||
<RestartAltIcon style={{ fontSize: '1rem' }} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
<Center>
|
||||
<Box
|
||||
style={{
|
||||
width: CONTAINER_SIZE,
|
||||
height: CONTAINER_SIZE,
|
||||
border: '1px solid var(--mantine-color-gray-3)',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: 'var(--mantine-color-gray-0)',
|
||||
overflow: 'hidden',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<CropAreaSelector
|
||||
pdfBounds={pdfBounds}
|
||||
cropArea={cropArea}
|
||||
onCropAreaChange={handleCropAreaChange}
|
||||
disabled={disabled}
|
||||
>
|
||||
<DocumentThumbnail
|
||||
file={selectedStub}
|
||||
thumbnail={thumbnail}
|
||||
style={{
|
||||
width: pdfBounds.thumbnailWidth,
|
||||
height: pdfBounds.thumbnailHeight,
|
||||
position: 'absolute',
|
||||
left: pdfBounds.offsetX,
|
||||
top: pdfBounds.offsetY
|
||||
}}
|
||||
/>
|
||||
</CropAreaSelector>
|
||||
</Box>
|
||||
</Center>
|
||||
|
||||
</Stack>
|
||||
|
||||
{/* Manual Coordinate Input */}
|
||||
<CropCoordinateInputs
|
||||
cropArea={cropArea}
|
||||
onCoordinateChange={handleCoordinateChange}
|
||||
{/* Auto-Crop Checkbox */}
|
||||
<Checkbox
|
||||
label={t("crop.autoCrop", "Auto-crop whitespace")}
|
||||
checked={parameters.parameters.autoCrop}
|
||||
onChange={(e) => parameters.updateParameter('autoCrop', e.currentTarget.checked)}
|
||||
disabled={disabled}
|
||||
pdfBounds={pdfBounds}
|
||||
showAutomationInfo={false}
|
||||
/>
|
||||
|
||||
{/* Validation Alert */}
|
||||
{!isCropValid && (
|
||||
{/* PDF Preview with Crop Selector - Only show when autoCrop is false */}
|
||||
{!parameters.parameters.autoCrop && (
|
||||
<Stack gap="xs">
|
||||
<Group justify="space-between" align="center">
|
||||
<Text size="sm" fw={500}>
|
||||
{t("crop.preview.title", "Crop Area Selection")}
|
||||
</Text>
|
||||
<ActionIcon
|
||||
variant="outline"
|
||||
onClick={handleReset}
|
||||
disabled={disabled || isFullCrop}
|
||||
title={t("crop.reset", "Reset to full PDF")}
|
||||
aria-label={t("crop.reset", "Reset to full PDF")}
|
||||
>
|
||||
<RestartAltIcon style={{ fontSize: '1rem' }} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
<Center>
|
||||
<Box
|
||||
style={{
|
||||
width: CONTAINER_SIZE,
|
||||
height: CONTAINER_SIZE,
|
||||
border: '1px solid var(--mantine-color-gray-3)',
|
||||
borderRadius: '8px',
|
||||
backgroundColor: 'var(--mantine-color-gray-0)',
|
||||
overflow: 'hidden',
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
<CropAreaSelector
|
||||
pdfBounds={pdfBounds}
|
||||
cropArea={cropArea}
|
||||
onCropAreaChange={handleCropAreaChange}
|
||||
disabled={disabled}
|
||||
>
|
||||
<DocumentThumbnail
|
||||
file={selectedStub}
|
||||
thumbnail={thumbnail}
|
||||
style={{
|
||||
width: pdfBounds.thumbnailWidth,
|
||||
height: pdfBounds.thumbnailHeight,
|
||||
position: 'absolute',
|
||||
left: pdfBounds.offsetX,
|
||||
top: pdfBounds.offsetY
|
||||
}}
|
||||
/>
|
||||
</CropAreaSelector>
|
||||
</Box>
|
||||
</Center>
|
||||
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{/* Manual Coordinate Input - Only show when autoCrop is false */}
|
||||
{!parameters.parameters.autoCrop && (
|
||||
<CropCoordinateInputs
|
||||
cropArea={cropArea}
|
||||
onCoordinateChange={handleCoordinateChange}
|
||||
disabled={disabled}
|
||||
pdfBounds={pdfBounds}
|
||||
showAutomationInfo={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Validation Alert - Only show when autoCrop is false */}
|
||||
{!parameters.parameters.autoCrop && !isCropValid && (
|
||||
<Alert color="red" variant="light">
|
||||
<Text size="xs">
|
||||
{t("crop.error.invalidArea", "Crop area extends beyond PDF boundaries")}
|
||||
|
||||
@ -7,13 +7,17 @@ import { CropParameters, defaultParameters } from '@app/hooks/tools/crop/useCrop
|
||||
export const buildCropFormData = (parameters: CropParameters, file: File): FormData => {
|
||||
const formData = new FormData();
|
||||
formData.append("fileInput", file);
|
||||
const cropArea = parameters.cropArea;
|
||||
|
||||
// Backend expects precise float values for PDF coordinates
|
||||
formData.append("x", cropArea.x.toString());
|
||||
formData.append("y", cropArea.y.toString());
|
||||
formData.append("width", cropArea.width.toString());
|
||||
formData.append("height", cropArea.height.toString());
|
||||
if (!parameters.autoCrop) {
|
||||
const cropArea = parameters.cropArea;
|
||||
|
||||
formData.append("x", cropArea.x.toString());
|
||||
formData.append("y", cropArea.y.toString());
|
||||
formData.append("width", cropArea.width.toString());
|
||||
formData.append("height", cropArea.height.toString());
|
||||
}
|
||||
|
||||
formData.append("autoCrop", parameters.autoCrop.toString());
|
||||
|
||||
return formData;
|
||||
};
|
||||
|
||||
@ -6,10 +6,12 @@ import { DEFAULT_CROP_AREA } from '@app/constants/cropConstants';
|
||||
|
||||
export interface CropParameters extends BaseParameters {
|
||||
cropArea: Rectangle;
|
||||
autoCrop: boolean;
|
||||
}
|
||||
|
||||
export const defaultParameters: CropParameters = {
|
||||
cropArea: DEFAULT_CROP_AREA,
|
||||
autoCrop: false,
|
||||
};
|
||||
|
||||
export type CropParametersHook = BaseParametersHook<CropParameters> & {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user