mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-24 04:26:14 +00:00
Redesign settings to be closer to Figma
This commit is contained in:
parent
1991cdbe07
commit
ca9b18844b
@ -1596,13 +1596,18 @@
|
||||
},
|
||||
"auto": {
|
||||
"header": "Auto Redact",
|
||||
"settings": "Redaction Settings",
|
||||
"colorLabel": "Colour",
|
||||
"textsToRedactLabel": "Text to Redact (line-separated)",
|
||||
"textsToRedactPlaceholder": "e.g. \nConfidential \nTop-Secret",
|
||||
"wordsToRedact": {
|
||||
"title": "Words to Redact",
|
||||
"placeholder": "Enter a word",
|
||||
"addAnother": "Add Another",
|
||||
"examples": "Examples: Confidential, Top-Secret"
|
||||
},
|
||||
"useRegexLabel": "Use Regex",
|
||||
"wholeWordSearchLabel": "Whole Word Search",
|
||||
"customPaddingLabel": "Custom Extra Padding",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)"
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image"
|
||||
},
|
||||
"manual": {
|
||||
"header": "Manual Redaction",
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Stack, Text, Textarea, Select, NumberInput, Divider } from "@mantine/core";
|
||||
import { Stack, Divider, Text, NumberInput, Group, ColorInput } from "@mantine/core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { RedactParameters } from "../../../hooks/tools/redact/useRedactParameters";
|
||||
import WordsToRedactInput from "./WordsToRedactInput";
|
||||
|
||||
interface AutomaticRedactSettingsProps {
|
||||
parameters: RedactParameters;
|
||||
@ -11,61 +12,53 @@ interface AutomaticRedactSettingsProps {
|
||||
const AutomaticRedactSettings = ({ parameters, onParameterChange, disabled = false }: AutomaticRedactSettingsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const colorOptions = [
|
||||
{ value: '#000000', label: t('black', 'Black') },
|
||||
{ value: '#FFFFFF', label: t('white', 'White') },
|
||||
{ value: '#FF0000', label: t('red', 'Red') },
|
||||
{ value: '#00FF00', label: t('green', 'Green') },
|
||||
{ value: '#0000FF', label: t('blue', 'Blue') },
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<Divider ml='-md' />
|
||||
|
||||
{/* Text to Redact */}
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={500}>
|
||||
{t('redact.auto.textsToRedactLabel', 'Text to Redact (line-separated)')}
|
||||
</Text>
|
||||
<Textarea
|
||||
placeholder={t('redact.auto.textsToRedactPlaceholder', 'e.g. \nConfidential \nTop-Secret')}
|
||||
value={parameters.listOfText}
|
||||
onChange={(e) => onParameterChange('listOfText', e.target.value)}
|
||||
disabled={disabled}
|
||||
rows={4}
|
||||
required
|
||||
/>
|
||||
</Stack>
|
||||
{/* Words to Redact */}
|
||||
<WordsToRedactInput
|
||||
wordsToRedact={parameters.wordsToRedact}
|
||||
onWordsChange={(words) => onParameterChange('wordsToRedact', words)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Redaction Color */}
|
||||
{/* Redaction Settings */}
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={500}>
|
||||
{t('redact.auto.colorLabel', 'Color')}
|
||||
{t('redact.auto.settings', 'Redaction Settings')}
|
||||
</Text>
|
||||
<Select
|
||||
value={parameters.redactColor}
|
||||
onChange={(value) => {
|
||||
if (value) {
|
||||
onParameterChange('redactColor', value);
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
data={colorOptions}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Search Options */}
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={500}>Search Options</Text>
|
||||
{/* Box Color */}
|
||||
<Stack gap="sm">
|
||||
<Text size="sm">{t('redact.auto.colorLabel', 'Colour')}</Text>
|
||||
<Group gap="sm">
|
||||
<ColorInput
|
||||
value={parameters.redactColor}
|
||||
onChange={(value) => onParameterChange('redactColor', value)}
|
||||
disabled={disabled}
|
||||
size="sm"
|
||||
style={{ width: '80px' }}
|
||||
/>
|
||||
<NumberInput
|
||||
value={parameters.customPadding}
|
||||
onChange={(value) => onParameterChange('customPadding', typeof value === 'number' ? value : 0.1)}
|
||||
min={0}
|
||||
max={10}
|
||||
step={0.1}
|
||||
disabled={disabled}
|
||||
size="sm"
|
||||
style={{ width: '80px' }}
|
||||
placeholder="0.1"
|
||||
/>
|
||||
</Group>
|
||||
</Stack>
|
||||
|
||||
{/* Use Regex */}
|
||||
<label
|
||||
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
|
||||
title="Use regular expressions for pattern matching"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -76,9 +69,9 @@ const AutomaticRedactSettings = ({ parameters, onParameterChange, disabled = fal
|
||||
<Text size="sm">{t('redact.auto.useRegexLabel', 'Use Regex')}</Text>
|
||||
</label>
|
||||
|
||||
{/* Whole Word Search */}
|
||||
<label
|
||||
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
|
||||
title="Match whole words only, not partial matches within words"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
@ -88,30 +81,10 @@ const AutomaticRedactSettings = ({ parameters, onParameterChange, disabled = fal
|
||||
/>
|
||||
<Text size="sm">{t('redact.auto.wholeWordSearchLabel', 'Whole Word Search')}</Text>
|
||||
</label>
|
||||
</Stack>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Advanced Options */}
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={500}>Advanced Options</Text>
|
||||
|
||||
<Stack gap="sm">
|
||||
<Text size="sm">{t('redact.auto.customPaddingLabel', 'Custom Extra Padding')}</Text>
|
||||
<NumberInput
|
||||
value={parameters.customPadding}
|
||||
onChange={(value) => onParameterChange('customPadding', typeof value === 'number' ? value : 0.1)}
|
||||
min={0}
|
||||
max={10}
|
||||
step={0.1}
|
||||
disabled={disabled}
|
||||
placeholder="0.1"
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
{/* Convert PDF to PDF-Image */}
|
||||
<label
|
||||
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
|
||||
title="Convert PDF to PDF-Image to remove text behind the redaction box"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Radio, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { Select, Stack, Text } from '@mantine/core';
|
||||
import { RedactMode } from '../../../hooks/tools/redact/useRedactParameters';
|
||||
|
||||
interface RedactModeSelectorProps {
|
||||
@ -11,40 +11,35 @@ interface RedactModeSelectorProps {
|
||||
export default function RedactModeSelector({ mode, onModeChange, disabled }: RedactModeSelectorProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const modeOptions = [
|
||||
{
|
||||
value: 'automatic',
|
||||
label: t('redact.modeSelector.automatic', 'Automatic'),
|
||||
disabled: false
|
||||
},
|
||||
{
|
||||
value: 'manual',
|
||||
label: t('redact.modeSelector.manual', 'Manual'),
|
||||
disabled: true // Disabled until implemented
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={600}>
|
||||
{t('redact.modeSelector.title', 'Redaction Mode')}
|
||||
</Text>
|
||||
|
||||
<Radio.Group
|
||||
<Select
|
||||
value={mode}
|
||||
onChange={(value) => onModeChange(value as RedactMode)}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
<Radio
|
||||
value="automatic"
|
||||
label={t('redact.modeSelector.automatic', 'Automatic')}
|
||||
description={t('redact.modeSelector.automaticDesc', 'Redact text based on search terms')}
|
||||
disabled={disabled}
|
||||
/>
|
||||
|
||||
<Tooltip
|
||||
label={t('redact.modeSelector.manualComingSoon', 'Manual redaction coming soon')}
|
||||
position="right"
|
||||
>
|
||||
<div>
|
||||
<Radio
|
||||
value="manual"
|
||||
label={t('redact.modeSelector.manual', 'Manual')}
|
||||
description={t('redact.modeSelector.manualDesc', 'Click and drag to redact specific areas')}
|
||||
disabled={true}
|
||||
style={{ opacity: 0.5 }}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Stack>
|
||||
</Radio.Group>
|
||||
onChange={(value) => {
|
||||
if (value && value !== 'manual') { // Don't allow manual selection yet
|
||||
onModeChange(value as RedactMode);
|
||||
}
|
||||
}}
|
||||
disabled={disabled}
|
||||
data={modeOptions}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
89
frontend/src/components/tools/redact/WordsToRedactInput.tsx
Normal file
89
frontend/src/components/tools/redact/WordsToRedactInput.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Stack, Text, TextInput, Button, Group, ActionIcon } from '@mantine/core';
|
||||
|
||||
interface WordsToRedactInputProps {
|
||||
wordsToRedact: string[];
|
||||
onWordsChange: (words: string[]) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export default function WordsToRedactInput({ wordsToRedact, onWordsChange, disabled }: WordsToRedactInputProps) {
|
||||
const { t } = useTranslation();
|
||||
const [currentWord, setCurrentWord] = useState('');
|
||||
|
||||
const addWord = () => {
|
||||
if (currentWord.trim() && !wordsToRedact.includes(currentWord.trim())) {
|
||||
onWordsChange([...wordsToRedact, currentWord.trim()]);
|
||||
setCurrentWord('');
|
||||
}
|
||||
};
|
||||
|
||||
const removeWord = (index: number) => {
|
||||
onWordsChange(wordsToRedact.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const handleKeyPress = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addWord();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="sm">
|
||||
<Text size="sm" fw={500}>
|
||||
{t('redact.auto.wordsToRedact.title', 'Words to Redact')}
|
||||
</Text>
|
||||
|
||||
{/* Current words */}
|
||||
{wordsToRedact.map((word, index) => (
|
||||
<Group key={index} justify="space-between" style={{
|
||||
padding: '8px 12px',
|
||||
backgroundColor: '#f8f9fa',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid #e9ecef'
|
||||
}}>
|
||||
<Text size="sm">{word}</Text>
|
||||
<ActionIcon
|
||||
size="sm"
|
||||
variant="subtle"
|
||||
color="red"
|
||||
onClick={() => removeWord(index)}
|
||||
disabled={disabled}
|
||||
>
|
||||
×
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
))}
|
||||
|
||||
{/* Add new word input */}
|
||||
<Group gap="sm">
|
||||
<TextInput
|
||||
placeholder={t('redact.auto.wordsToRedact.placeholder', 'Enter a word')}
|
||||
value={currentWord}
|
||||
onChange={(e) => setCurrentWord(e.target.value)}
|
||||
onKeyDown={handleKeyPress}
|
||||
disabled={disabled}
|
||||
style={{ flex: 1 }}
|
||||
size="sm"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="light"
|
||||
onClick={addWord}
|
||||
disabled={disabled || !currentWord.trim()}
|
||||
>
|
||||
+ {t('redact.auto.wordsToRedact.addAnother', 'Add Another')}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
{/* Examples */}
|
||||
{wordsToRedact.length === 0 && (
|
||||
<Text size="xs" c="dimmed">
|
||||
{t('redact.auto.wordsToRedact.examples', 'Examples: Confidential, Top-Secret')}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
@ -9,7 +9,8 @@ export const buildRedactFormData = (parameters: RedactParameters, file: File): F
|
||||
formData.append("fileInput", file);
|
||||
|
||||
if (parameters.mode === 'automatic') {
|
||||
formData.append("listOfText", parameters.listOfText);
|
||||
// Convert array to newline-separated string as expected by backend
|
||||
formData.append("listOfText", parameters.wordsToRedact.join('\n'));
|
||||
formData.append("useRegex", parameters.useRegex.toString());
|
||||
formData.append("wholeWordSearch", parameters.wholeWordSearch.toString());
|
||||
formData.append("redactColor", parameters.redactColor.replace('#', ''));
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { BaseParameters } from '../../../types/parameters';
|
||||
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
|
||||
|
||||
export type RedactMode = 'automatic' | 'manual';
|
||||
|
||||
export interface RedactParameters {
|
||||
export interface RedactParameters extends BaseParameters {
|
||||
mode: RedactMode;
|
||||
|
||||
// Automatic redaction parameters
|
||||
listOfText: string;
|
||||
wordsToRedact: string[];
|
||||
useRegex: boolean;
|
||||
wholeWordSearch: boolean;
|
||||
redactColor: string;
|
||||
@ -16,7 +17,7 @@ export interface RedactParameters {
|
||||
|
||||
export const defaultParameters: RedactParameters = {
|
||||
mode: 'automatic',
|
||||
listOfText: '',
|
||||
wordsToRedact: [],
|
||||
useRegex: false,
|
||||
wholeWordSearch: false,
|
||||
redactColor: '#000000',
|
||||
@ -24,8 +25,10 @@ export const defaultParameters: RedactParameters = {
|
||||
convertPDFToImage: true,
|
||||
};
|
||||
|
||||
export const useRedactParameters = (): BaseParametersHook<RedactParameters> => {
|
||||
return useBaseParameters<RedactParameters>({
|
||||
export type RedactParametersHook = BaseParametersHook<RedactParameters>;
|
||||
|
||||
export const useRedactParameters = (): RedactParametersHook => {
|
||||
return useBaseParameters({
|
||||
defaultParameters,
|
||||
endpointName: (params) => {
|
||||
if (params.mode === 'automatic') {
|
||||
@ -36,7 +39,7 @@ export const useRedactParameters = (): BaseParametersHook<RedactParameters> => {
|
||||
},
|
||||
validateFn: (params) => {
|
||||
if (params.mode === 'automatic') {
|
||||
return params.listOfText.trim().length > 0;
|
||||
return params.wordsToRedact.length > 0 && params.wordsToRedact.some(word => word.trim().length > 0);
|
||||
}
|
||||
// Manual mode validation would go here when implemented
|
||||
return false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user