mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-24 12:36:13 +00:00
Redesign settings to be closer to Figma
This commit is contained in:
parent
1991cdbe07
commit
ca9b18844b
@ -1596,13 +1596,18 @@
|
|||||||
},
|
},
|
||||||
"auto": {
|
"auto": {
|
||||||
"header": "Auto Redact",
|
"header": "Auto Redact",
|
||||||
|
"settings": "Redaction Settings",
|
||||||
"colorLabel": "Colour",
|
"colorLabel": "Colour",
|
||||||
"textsToRedactLabel": "Text to Redact (line-separated)",
|
"wordsToRedact": {
|
||||||
"textsToRedactPlaceholder": "e.g. \nConfidential \nTop-Secret",
|
"title": "Words to Redact",
|
||||||
|
"placeholder": "Enter a word",
|
||||||
|
"addAnother": "Add Another",
|
||||||
|
"examples": "Examples: Confidential, Top-Secret"
|
||||||
|
},
|
||||||
"useRegexLabel": "Use Regex",
|
"useRegexLabel": "Use Regex",
|
||||||
"wholeWordSearchLabel": "Whole Word Search",
|
"wholeWordSearchLabel": "Whole Word Search",
|
||||||
"customPaddingLabel": "Custom Extra Padding",
|
"customPaddingLabel": "Custom Extra Padding",
|
||||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)"
|
"convertPDFToImageLabel": "Convert PDF to PDF-Image"
|
||||||
},
|
},
|
||||||
"manual": {
|
"manual": {
|
||||||
"header": "Manual Redaction",
|
"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 { useTranslation } from "react-i18next";
|
||||||
import { RedactParameters } from "../../../hooks/tools/redact/useRedactParameters";
|
import { RedactParameters } from "../../../hooks/tools/redact/useRedactParameters";
|
||||||
|
import WordsToRedactInput from "./WordsToRedactInput";
|
||||||
|
|
||||||
interface AutomaticRedactSettingsProps {
|
interface AutomaticRedactSettingsProps {
|
||||||
parameters: RedactParameters;
|
parameters: RedactParameters;
|
||||||
@ -11,61 +12,53 @@ interface AutomaticRedactSettingsProps {
|
|||||||
const AutomaticRedactSettings = ({ parameters, onParameterChange, disabled = false }: AutomaticRedactSettingsProps) => {
|
const AutomaticRedactSettings = ({ parameters, onParameterChange, disabled = false }: AutomaticRedactSettingsProps) => {
|
||||||
const { t } = useTranslation();
|
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 (
|
return (
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Divider ml='-md' />
|
<Divider ml='-md' />
|
||||||
|
|
||||||
{/* Text to Redact */}
|
{/* Words to Redact */}
|
||||||
<Stack gap="sm">
|
<WordsToRedactInput
|
||||||
<Text size="sm" fw={500}>
|
wordsToRedact={parameters.wordsToRedact}
|
||||||
{t('redact.auto.textsToRedactLabel', 'Text to Redact (line-separated)')}
|
onWordsChange={(words) => onParameterChange('wordsToRedact', words)}
|
||||||
</Text>
|
|
||||||
<Textarea
|
|
||||||
placeholder={t('redact.auto.textsToRedactPlaceholder', 'e.g. \nConfidential \nTop-Secret')}
|
|
||||||
value={parameters.listOfText}
|
|
||||||
onChange={(e) => onParameterChange('listOfText', e.target.value)}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
rows={4}
|
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
{/* Redaction Color */}
|
{/* Redaction Settings */}
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text size="sm" fw={500}>
|
<Text size="sm" fw={500}>
|
||||||
{t('redact.auto.colorLabel', 'Color')}
|
{t('redact.auto.settings', 'Redaction Settings')}
|
||||||
</Text>
|
</Text>
|
||||||
<Select
|
|
||||||
|
{/* Box Color */}
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Text size="sm">{t('redact.auto.colorLabel', 'Colour')}</Text>
|
||||||
|
<Group gap="sm">
|
||||||
|
<ColorInput
|
||||||
value={parameters.redactColor}
|
value={parameters.redactColor}
|
||||||
onChange={(value) => {
|
onChange={(value) => onParameterChange('redactColor', value)}
|
||||||
if (value) {
|
|
||||||
onParameterChange('redactColor', value);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
data={colorOptions}
|
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>
|
</Stack>
|
||||||
|
|
||||||
<Divider />
|
{/* Use Regex */}
|
||||||
|
|
||||||
{/* Search Options */}
|
|
||||||
<Stack gap="sm">
|
|
||||||
<Text size="sm" fw={500}>Search Options</Text>
|
|
||||||
|
|
||||||
<label
|
<label
|
||||||
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
|
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
|
||||||
title="Use regular expressions for pattern matching"
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -76,9 +69,9 @@ const AutomaticRedactSettings = ({ parameters, onParameterChange, disabled = fal
|
|||||||
<Text size="sm">{t('redact.auto.useRegexLabel', 'Use Regex')}</Text>
|
<Text size="sm">{t('redact.auto.useRegexLabel', 'Use Regex')}</Text>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
{/* Whole Word Search */}
|
||||||
<label
|
<label
|
||||||
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
|
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
|
||||||
title="Match whole words only, not partial matches within words"
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -88,30 +81,10 @@ const AutomaticRedactSettings = ({ parameters, onParameterChange, disabled = fal
|
|||||||
/>
|
/>
|
||||||
<Text size="sm">{t('redact.auto.wholeWordSearchLabel', 'Whole Word Search')}</Text>
|
<Text size="sm">{t('redact.auto.wholeWordSearchLabel', 'Whole Word Search')}</Text>
|
||||||
</label>
|
</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
|
<label
|
||||||
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
|
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
|
||||||
title="Convert PDF to PDF-Image to remove text behind the redaction box"
|
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
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';
|
import { RedactMode } from '../../../hooks/tools/redact/useRedactParameters';
|
||||||
|
|
||||||
interface RedactModeSelectorProps {
|
interface RedactModeSelectorProps {
|
||||||
@ -11,40 +11,35 @@ interface RedactModeSelectorProps {
|
|||||||
export default function RedactModeSelector({ mode, onModeChange, disabled }: RedactModeSelectorProps) {
|
export default function RedactModeSelector({ mode, onModeChange, disabled }: RedactModeSelectorProps) {
|
||||||
const { t } = useTranslation();
|
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 (
|
return (
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text size="sm" fw={600}>
|
<Text size="sm" fw={600}>
|
||||||
{t('redact.modeSelector.title', 'Redaction Mode')}
|
{t('redact.modeSelector.title', 'Redaction Mode')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Radio.Group
|
<Select
|
||||||
value={mode}
|
value={mode}
|
||||||
onChange={(value) => onModeChange(value as RedactMode)}
|
onChange={(value) => {
|
||||||
>
|
if (value && value !== 'manual') { // Don't allow manual selection yet
|
||||||
<Stack gap="xs">
|
onModeChange(value as RedactMode);
|
||||||
<Radio
|
}
|
||||||
value="automatic"
|
}}
|
||||||
label={t('redact.modeSelector.automatic', 'Automatic')}
|
|
||||||
description={t('redact.modeSelector.automaticDesc', 'Redact text based on search terms')}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
data={modeOptions}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<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>
|
|
||||||
</Stack>
|
</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);
|
formData.append("fileInput", file);
|
||||||
|
|
||||||
if (parameters.mode === 'automatic') {
|
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("useRegex", parameters.useRegex.toString());
|
||||||
formData.append("wholeWordSearch", parameters.wholeWordSearch.toString());
|
formData.append("wholeWordSearch", parameters.wholeWordSearch.toString());
|
||||||
formData.append("redactColor", parameters.redactColor.replace('#', ''));
|
formData.append("redactColor", parameters.redactColor.replace('#', ''));
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
|
import { BaseParameters } from '../../../types/parameters';
|
||||||
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
|
import { useBaseParameters, BaseParametersHook } from '../shared/useBaseParameters';
|
||||||
|
|
||||||
export type RedactMode = 'automatic' | 'manual';
|
export type RedactMode = 'automatic' | 'manual';
|
||||||
|
|
||||||
export interface RedactParameters {
|
export interface RedactParameters extends BaseParameters {
|
||||||
mode: RedactMode;
|
mode: RedactMode;
|
||||||
|
|
||||||
// Automatic redaction parameters
|
// Automatic redaction parameters
|
||||||
listOfText: string;
|
wordsToRedact: string[];
|
||||||
useRegex: boolean;
|
useRegex: boolean;
|
||||||
wholeWordSearch: boolean;
|
wholeWordSearch: boolean;
|
||||||
redactColor: string;
|
redactColor: string;
|
||||||
@ -16,7 +17,7 @@ export interface RedactParameters {
|
|||||||
|
|
||||||
export const defaultParameters: RedactParameters = {
|
export const defaultParameters: RedactParameters = {
|
||||||
mode: 'automatic',
|
mode: 'automatic',
|
||||||
listOfText: '',
|
wordsToRedact: [],
|
||||||
useRegex: false,
|
useRegex: false,
|
||||||
wholeWordSearch: false,
|
wholeWordSearch: false,
|
||||||
redactColor: '#000000',
|
redactColor: '#000000',
|
||||||
@ -24,8 +25,10 @@ export const defaultParameters: RedactParameters = {
|
|||||||
convertPDFToImage: true,
|
convertPDFToImage: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useRedactParameters = (): BaseParametersHook<RedactParameters> => {
|
export type RedactParametersHook = BaseParametersHook<RedactParameters>;
|
||||||
return useBaseParameters<RedactParameters>({
|
|
||||||
|
export const useRedactParameters = (): RedactParametersHook => {
|
||||||
|
return useBaseParameters({
|
||||||
defaultParameters,
|
defaultParameters,
|
||||||
endpointName: (params) => {
|
endpointName: (params) => {
|
||||||
if (params.mode === 'automatic') {
|
if (params.mode === 'automatic') {
|
||||||
@ -36,7 +39,7 @@ export const useRedactParameters = (): BaseParametersHook<RedactParameters> => {
|
|||||||
},
|
},
|
||||||
validateFn: (params) => {
|
validateFn: (params) => {
|
||||||
if (params.mode === 'automatic') {
|
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
|
// Manual mode validation would go here when implemented
|
||||||
return false;
|
return false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user