mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-09-24 12:36:13 +00:00
Separate into tool steps
This commit is contained in:
parent
ca9b18844b
commit
bb96522326
@ -1597,7 +1597,7 @@
|
|||||||
"auto": {
|
"auto": {
|
||||||
"header": "Auto Redact",
|
"header": "Auto Redact",
|
||||||
"settings": "Redaction Settings",
|
"settings": "Redaction Settings",
|
||||||
"colorLabel": "Colour",
|
"colorLabel": "Box Colour",
|
||||||
"wordsToRedact": {
|
"wordsToRedact": {
|
||||||
"title": "Words to Redact",
|
"title": "Words to Redact",
|
||||||
"placeholder": "Enter a word",
|
"placeholder": "Enter a word",
|
||||||
|
@ -33,27 +33,28 @@ const AutomaticRedactSettings = ({ parameters, onParameterChange, disabled = fal
|
|||||||
|
|
||||||
{/* Box Color */}
|
{/* Box Color */}
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text size="sm">{t('redact.auto.colorLabel', 'Colour')}</Text>
|
<Text size="sm">{t('redact.auto.colorLabel', 'Box Colour')}</Text>
|
||||||
<Group gap="sm">
|
<ColorInput
|
||||||
<ColorInput
|
value={parameters.redactColor}
|
||||||
value={parameters.redactColor}
|
onChange={(value) => onParameterChange('redactColor', value)}
|
||||||
onChange={(value) => onParameterChange('redactColor', value)}
|
disabled={disabled}
|
||||||
disabled={disabled}
|
size="sm"
|
||||||
size="sm"
|
/>
|
||||||
style={{ width: '80px' }}
|
</Stack>
|
||||||
/>
|
|
||||||
<NumberInput
|
{/* Box Padding */}
|
||||||
value={parameters.customPadding}
|
<Stack gap="sm">
|
||||||
onChange={(value) => onParameterChange('customPadding', typeof value === 'number' ? value : 0.1)}
|
<Text size="sm">{t('redact.auto.paddingLabel', 'Box Padding')}</Text>
|
||||||
min={0}
|
<NumberInput
|
||||||
max={10}
|
value={parameters.customPadding}
|
||||||
step={0.1}
|
onChange={(value) => onParameterChange('customPadding', typeof value === 'number' ? value : 0.1)}
|
||||||
disabled={disabled}
|
min={0}
|
||||||
size="sm"
|
max={10}
|
||||||
style={{ width: '80px' }}
|
step={0.1}
|
||||||
placeholder="0.1"
|
disabled={disabled}
|
||||||
/>
|
size="sm"
|
||||||
</Group>
|
placeholder="0.1"
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{/* Use Regex */}
|
{/* Use Regex */}
|
||||||
|
76
frontend/src/components/tools/redact/RedactAdvancedStep.tsx
Normal file
76
frontend/src/components/tools/redact/RedactAdvancedStep.tsx
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { Stack, Text, NumberInput, ColorInput } from '@mantine/core';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { RedactParameters } from '../../../hooks/tools/redact/useRedactParameters';
|
||||||
|
|
||||||
|
interface RedactAdvancedStepProps {
|
||||||
|
parameters: RedactParameters;
|
||||||
|
onParameterChange: <K extends keyof RedactParameters>(key: K, value: RedactParameters[K]) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RedactAdvancedStep({ parameters, onParameterChange, disabled }: RedactAdvancedStepProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
{/* Box Color */}
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Text size="sm" fw={500}>{t('redact.advanced.colorLabel', 'Box Color')}</Text>
|
||||||
|
<ColorInput
|
||||||
|
value={parameters.redactColor}
|
||||||
|
onChange={(value) => onParameterChange('redactColor', value)}
|
||||||
|
disabled={disabled}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Box Padding */}
|
||||||
|
<Stack gap="sm">
|
||||||
|
<Text size="sm" fw={500}>{t('redact.advanced.paddingLabel', 'Box 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}
|
||||||
|
size="sm"
|
||||||
|
placeholder="0.1"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{/* Use Regex */}
|
||||||
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={parameters.useRegex}
|
||||||
|
onChange={(e) => onParameterChange('useRegex', e.target.checked)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<Text size="sm">{t('redact.advanced.useRegexLabel', 'Use Regex')}</Text>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{/* Whole Word Search */}
|
||||||
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={parameters.wholeWordSearch}
|
||||||
|
onChange={(e) => onParameterChange('wholeWordSearch', e.target.checked)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<Text size="sm">{t('redact.advanced.wholeWordSearchLabel', 'Whole Word Search')}</Text>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
{/* Convert PDF to PDF-Image */}
|
||||||
|
<label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={parameters.convertPDFToImage}
|
||||||
|
onChange={(e) => onParameterChange('convertPDFToImage', e.target.checked)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
<Text size="sm">{t('redact.advanced.convertPDFToImageLabel', 'Convert PDF to PDF-Image (Used to remove text behind the box)')}</Text>
|
||||||
|
</label>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
21
frontend/src/components/tools/redact/RedactModeStep.tsx
Normal file
21
frontend/src/components/tools/redact/RedactModeStep.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Stack } from '@mantine/core';
|
||||||
|
import { RedactParameters } from '../../../hooks/tools/redact/useRedactParameters';
|
||||||
|
import RedactModeSelector from './RedactModeSelector';
|
||||||
|
|
||||||
|
interface RedactModeStepProps {
|
||||||
|
parameters: RedactParameters;
|
||||||
|
onParameterChange: <K extends keyof RedactParameters>(key: K, value: RedactParameters[K]) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RedactModeStep({ parameters, onParameterChange, disabled }: RedactModeStepProps) {
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
<RedactModeSelector
|
||||||
|
mode={parameters.mode}
|
||||||
|
onModeChange={(mode) => onParameterChange('mode', mode)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
21
frontend/src/components/tools/redact/RedactWordsStep.tsx
Normal file
21
frontend/src/components/tools/redact/RedactWordsStep.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Stack } from '@mantine/core';
|
||||||
|
import { RedactParameters } from '../../../hooks/tools/redact/useRedactParameters';
|
||||||
|
import WordsToRedactInput from './WordsToRedactInput';
|
||||||
|
|
||||||
|
interface RedactWordsStepProps {
|
||||||
|
parameters: RedactParameters;
|
||||||
|
onParameterChange: <K extends keyof RedactParameters>(key: K, value: RedactParameters[K]) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RedactWordsStep({ parameters, onParameterChange, disabled }: RedactWordsStepProps) {
|
||||||
|
return (
|
||||||
|
<Stack gap="md">
|
||||||
|
<WordsToRedactInput
|
||||||
|
wordsToRedact={parameters.wordsToRedact}
|
||||||
|
onWordsChange={(words) => onParameterChange('wordsToRedact', words)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
@ -58,14 +58,13 @@ export default function WordsToRedactInput({ wordsToRedact, onWordsChange, disab
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Add new word input */}
|
{/* Add new word input */}
|
||||||
<Group gap="sm">
|
<Stack gap="sm">
|
||||||
<TextInput
|
<TextInput
|
||||||
placeholder={t('redact.auto.wordsToRedact.placeholder', 'Enter a word')}
|
placeholder={t('redact.auto.wordsToRedact.placeholder', 'Enter a word')}
|
||||||
value={currentWord}
|
value={currentWord}
|
||||||
onChange={(e) => setCurrentWord(e.target.value)}
|
onChange={(e) => setCurrentWord(e.target.value)}
|
||||||
onKeyDown={handleKeyPress}
|
onKeyDown={handleKeyPress}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={{ flex: 1 }}
|
|
||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@ -73,10 +72,11 @@ export default function WordsToRedactInput({ wordsToRedact, onWordsChange, disab
|
|||||||
variant="light"
|
variant="light"
|
||||||
onClick={addWord}
|
onClick={addWord}
|
||||||
disabled={disabled || !currentWord.trim()}
|
disabled={disabled || !currentWord.trim()}
|
||||||
|
style={{ alignSelf: 'flex-start' }}
|
||||||
>
|
>
|
||||||
+ {t('redact.auto.wordsToRedact.addAnother', 'Add Another')}
|
+ {t('redact.auto.wordsToRedact.addAnother', 'Add Another')}
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Stack>
|
||||||
|
|
||||||
{/* Examples */}
|
{/* Examples */}
|
||||||
{wordsToRedact.length === 0 && (
|
{wordsToRedact.length === 0 && (
|
||||||
|
79
frontend/src/components/tooltips/useRedactTips.ts
Normal file
79
frontend/src/components/tooltips/useRedactTips.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { TooltipContent } from '../../types/tips';
|
||||||
|
|
||||||
|
export const useRedactModeTips = (): TooltipContent => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return {
|
||||||
|
header: {
|
||||||
|
title: t("redact.tooltip.mode.header.title", "Redaction Method")
|
||||||
|
},
|
||||||
|
tips: [
|
||||||
|
{
|
||||||
|
title: t("redact.tooltip.mode.automatic.title", "Automatic Redaction"),
|
||||||
|
description: t("redact.tooltip.mode.automatic.text", "Automatically finds and redacts specified text throughout the document. Perfect for removing consistent sensitive information like names, SSNs, or confidential markers.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("redact.tooltip.mode.manual.title", "Manual Redaction"),
|
||||||
|
description: t("redact.tooltip.mode.manual.text", "Click and drag to manually select specific areas to redact. Gives you precise control over what gets redacted. (Coming soon)")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRedactWordsTips = (): TooltipContent => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return {
|
||||||
|
header: {
|
||||||
|
title: t("redact.tooltip.words.header.title", "Words to Redact")
|
||||||
|
},
|
||||||
|
tips: [
|
||||||
|
{
|
||||||
|
title: t("redact.tooltip.words.description.title", "Text Matching"),
|
||||||
|
description: t("redact.tooltip.words.description.text", "Enter words or phrases to find and redact in your document. Each word will be searched for separately."),
|
||||||
|
bullets: [
|
||||||
|
t("redact.tooltip.words.bullet1", "Add one word at a time"),
|
||||||
|
t("redact.tooltip.words.bullet2", "Press Enter or click 'Add Another' to add"),
|
||||||
|
t("redact.tooltip.words.bullet3", "Click × to remove words")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("redact.tooltip.words.examples.title", "Common Examples"),
|
||||||
|
description: t("redact.tooltip.words.examples.text", "Typical words to redact include: 'Confidential', 'SSN:', phone numbers, email addresses, or specific names.")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRedactAdvancedTips = (): TooltipContent => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return {
|
||||||
|
header: {
|
||||||
|
title: t("redact.tooltip.advanced.header.title", "Advanced Redaction Settings")
|
||||||
|
},
|
||||||
|
tips: [
|
||||||
|
{
|
||||||
|
title: t("redact.tooltip.advanced.color.title", "Box Color & Padding"),
|
||||||
|
description: t("redact.tooltip.advanced.color.text", "Customize the appearance of redaction boxes. Black is standard, but you can choose any color. Padding adds extra space around the found text."),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("redact.tooltip.advanced.regex.title", "Use Regex"),
|
||||||
|
description: t("redact.tooltip.advanced.regex.text", "Enable regular expressions for advanced pattern matching. Useful for finding phone numbers, emails, or complex patterns."),
|
||||||
|
bullets: [
|
||||||
|
t("redact.tooltip.advanced.regex.bullet1", "Example: \\d{3}-\\d{3}-\\d{4} for phone numbers"),
|
||||||
|
t("redact.tooltip.advanced.regex.bullet2", "Use with caution - test thoroughly")
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("redact.tooltip.advanced.wholeWord.title", "Whole Word Search"),
|
||||||
|
description: t("redact.tooltip.advanced.wholeWord.text", "Only match complete words, not partial matches. 'John' won't match 'Johnson' when enabled.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("redact.tooltip.advanced.convert.title", "Convert to PDF-Image"),
|
||||||
|
description: t("redact.tooltip.advanced.convert.text", "Converts the PDF to an image-based PDF after redaction. This ensures text behind redaction boxes is completely removed and unrecoverable.")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
};
|
@ -1,14 +1,23 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useState } from "react";
|
||||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||||
import RedactSettings from "../components/tools/redact/RedactSettings";
|
import RedactModeStep from "../components/tools/redact/RedactModeStep";
|
||||||
|
import RedactWordsStep from "../components/tools/redact/RedactWordsStep";
|
||||||
|
import RedactAdvancedStep from "../components/tools/redact/RedactAdvancedStep";
|
||||||
import { useRedactParameters } from "../hooks/tools/redact/useRedactParameters";
|
import { useRedactParameters } from "../hooks/tools/redact/useRedactParameters";
|
||||||
import { useRedactOperation } from "../hooks/tools/redact/useRedactOperation";
|
import { useRedactOperation } from "../hooks/tools/redact/useRedactOperation";
|
||||||
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
|
import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
|
||||||
import { BaseToolProps, ToolComponent } from "../types/tool";
|
import { BaseToolProps, ToolComponent } from "../types/tool";
|
||||||
|
import { useRedactModeTips, useRedactWordsTips, useRedactAdvancedTips } from "../components/tooltips/useRedactTips";
|
||||||
|
|
||||||
const Redact = (props: BaseToolProps) => {
|
const Redact = (props: BaseToolProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// State for managing step collapse status
|
||||||
|
const [methodCollapsed, setMethodCollapsed] = useState(false);
|
||||||
|
const [wordsCollapsed, setWordsCollapsed] = useState(false);
|
||||||
|
const [advancedCollapsed, setAdvancedCollapsed] = useState(true);
|
||||||
|
|
||||||
const base = useBaseTool(
|
const base = useBaseTool(
|
||||||
'redact',
|
'redact',
|
||||||
useRedactParameters,
|
useRedactParameters,
|
||||||
@ -16,6 +25,11 @@ const Redact = (props: BaseToolProps) => {
|
|||||||
props
|
props
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Tooltips for each step
|
||||||
|
const modeTips = useRedactModeTips();
|
||||||
|
const wordsTips = useRedactWordsTips();
|
||||||
|
const advancedTips = useRedactAdvancedTips();
|
||||||
|
|
||||||
const isExecuteDisabled = () => {
|
const isExecuteDisabled = () => {
|
||||||
if (base.params.parameters.mode === 'manual') {
|
if (base.params.parameters.mode === 'manual') {
|
||||||
return true; // Manual mode not implemented yet
|
return true; // Manual mode not implemented yet
|
||||||
@ -23,6 +37,11 @@ const Redact = (props: BaseToolProps) => {
|
|||||||
return !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled;
|
return !base.params.validateParameters() || !base.hasFiles || !base.endpointEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Compute actual collapsed state based on results and user state
|
||||||
|
const getActualCollapsedState = (userCollapsed: boolean) => {
|
||||||
|
return base.hasResults ? true : userCollapsed; // Force collapse when results are shown
|
||||||
|
};
|
||||||
|
|
||||||
return createToolFlow({
|
return createToolFlow({
|
||||||
files: {
|
files: {
|
||||||
selectedFiles: base.selectedFiles,
|
selectedFiles: base.selectedFiles,
|
||||||
@ -30,11 +49,38 @@ const Redact = (props: BaseToolProps) => {
|
|||||||
},
|
},
|
||||||
steps: [
|
steps: [
|
||||||
{
|
{
|
||||||
title: "Settings",
|
title: t("redact.steps.method", "Method"),
|
||||||
isCollapsed: base.settingsCollapsed,
|
isCollapsed: getActualCollapsedState(methodCollapsed),
|
||||||
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
|
onCollapsedClick: () => base.settingsCollapsed ? base.handleSettingsReset() : setMethodCollapsed(!methodCollapsed),
|
||||||
|
tooltip: modeTips,
|
||||||
content: (
|
content: (
|
||||||
<RedactSettings
|
<RedactModeStep
|
||||||
|
parameters={base.params.parameters}
|
||||||
|
onParameterChange={base.params.updateParameter}
|
||||||
|
disabled={base.endpointLoading}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("redact.steps.words", "Words to Redact"),
|
||||||
|
isCollapsed: getActualCollapsedState(wordsCollapsed),
|
||||||
|
onCollapsedClick: () => base.settingsCollapsed ? base.handleSettingsReset() : setWordsCollapsed(!wordsCollapsed),
|
||||||
|
tooltip: wordsTips,
|
||||||
|
content: (
|
||||||
|
<RedactWordsStep
|
||||||
|
parameters={base.params.parameters}
|
||||||
|
onParameterChange={base.params.updateParameter}
|
||||||
|
disabled={base.endpointLoading}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("redact.steps.advanced", "Advanced Settings"),
|
||||||
|
isCollapsed: getActualCollapsedState(advancedCollapsed),
|
||||||
|
onCollapsedClick: () => base.settingsCollapsed ? base.handleSettingsReset() : setAdvancedCollapsed(!advancedCollapsed),
|
||||||
|
tooltip: advancedTips,
|
||||||
|
content: (
|
||||||
|
<RedactAdvancedStep
|
||||||
parameters={base.params.parameters}
|
parameters={base.params.parameters}
|
||||||
onParameterChange={base.params.updateParameter}
|
onParameterChange={base.params.updateParameter}
|
||||||
disabled={base.endpointLoading}
|
disabled={base.endpointLoading}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user