Stirling-PDF/frontend/src/components/tools/overlayPdfs/OverlayPdfsSettings.tsx
EthanHealy01 a339f71116
Overlay PDF tool (#4620)
# Description of Changes

- Added the OverlayPDF tool

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] 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)
- [ ] I have performed a self-review of my own code
- [ ] 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)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] 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.
2025-10-10 14:35:09 +01:00

179 lines
6.8 KiB
TypeScript

import { Stack, Text, Group, Select, SegmentedControl, NumberInput, Button, ActionIcon, Divider } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { type OverlayPdfsParameters, type OverlayMode } from '../../../hooks/tools/overlayPdfs/useOverlayPdfsParameters';
import LocalIcon from '../../shared/LocalIcon';
import { useFilesModalContext } from '../../../contexts/FilesModalContext';
import styles from './OverlayPdfsSettings.module.css';
interface OverlayPdfsSettingsProps {
parameters: OverlayPdfsParameters;
onParameterChange: <K extends keyof OverlayPdfsParameters>(key: K, value: OverlayPdfsParameters[K]) => void;
disabled?: boolean;
}
export default function OverlayPdfsSettings({ parameters, onParameterChange, disabled = false }: OverlayPdfsSettingsProps) {
const { t } = useTranslation();
const { openFilesModal } = useFilesModalContext();
const handleOverlayFilesChange = (files: File[]) => {
onParameterChange('overlayFiles', files);
// Reset counts to match number of files if in FixedRepeatOverlay
if (parameters.overlayMode === 'FixedRepeatOverlay') {
const nextCounts = files.map((_, i) => parameters.counts[i] && parameters.counts[i] > 0 ? parameters.counts[i] : 1);
onParameterChange('counts', nextCounts);
}
};
const handleModeChange = (mode: OverlayMode) => {
onParameterChange('overlayMode', mode);
if (mode !== 'FixedRepeatOverlay') {
onParameterChange('counts', []);
} else if (parameters.overlayFiles?.length > 0) {
onParameterChange('counts', parameters.overlayFiles.map((_, i) => parameters.counts[i] && parameters.counts[i] > 0 ? parameters.counts[i] : 1));
}
};
const handleOpenOverlayFilesModal = () => {
if (disabled) return;
openFilesModal({
customHandler: (files: File[]) => {
handleOverlayFilesChange([...(parameters.overlayFiles || []), ...files]);
}
});
};
return (
<Stack gap="md">
<Stack gap="xs">
<Text size="sm" fw={500}>{t('overlay-pdfs.mode.label', 'Overlay Mode')}</Text>
<Select
data={[
{ value: 'SequentialOverlay', label: t('overlay-pdfs.mode.sequential', 'Sequential Overlay') },
{ value: 'InterleavedOverlay', label: t('overlay-pdfs.mode.interleaved', 'Interleaved Overlay') },
{ value: 'FixedRepeatOverlay', label: t('overlay-pdfs.mode.fixedRepeat', 'Fixed Repeat Overlay') },
]}
value={parameters.overlayMode}
onChange={(v) => handleModeChange((v || 'SequentialOverlay') as OverlayMode)}
disabled={disabled}
/>
</Stack>
<Divider />
<Stack gap="xs">
<Text size="sm" fw={500}>{t('overlay-pdfs.position.label', 'Overlay Position')}</Text>
<SegmentedControl
value={String(parameters.overlayPosition)}
onChange={(v) => onParameterChange('overlayPosition', (v === '1' ? 1 : 0) as 0 | 1)}
data={[
{ label: t('overlay-pdfs.position.foreground', 'Foreground'), value: '0' },
{ label: t('overlay-pdfs.position.background', 'Background'), value: '1' },
]}
disabled={disabled}
/>
</Stack>
{parameters.overlayMode === 'FixedRepeatOverlay' && (
<>
<Divider />
<Stack gap="xs">
<Text size="sm" fw={500}>{t('overlay-pdfs.counts.label', 'Overlay Counts')}</Text>
{parameters.overlayFiles?.length > 0 ? (
<Stack gap="xs">
{parameters.overlayFiles.map((_, index) => (
<Group key={index} gap="xs" wrap="nowrap">
<Text size="sm" className={styles.countLabel}>
{t('overlay-pdfs.counts.item', 'Count for file')} {index + 1}
</Text>
<NumberInput
min={1}
step={1}
value={parameters.counts[index] ?? 1}
onChange={(value) => {
const next = [...(parameters.counts || [])];
next[index] = Number(value) || 1;
onParameterChange('counts', next);
}}
disabled={disabled}
/>
</Group>
))}
</Stack>
) : (
<Text size="sm" c="dimmed">
{t('overlay-pdfs.counts.noFiles', 'Add overlay files to configure counts')}
</Text>
)}
</Stack>
</>
)}
<Divider />
<Stack gap="xs">
<Text size="sm" fw={500}>{t('overlay-pdfs.overlayFiles.label', 'Overlay Files')}</Text>
<Button
size="xs"
color="blue"
onClick={handleOpenOverlayFilesModal}
disabled={disabled}
leftSection={<LocalIcon icon="add" width="14" height="14" />}
fullWidth
>
{parameters.overlayFiles?.length > 0
? t('overlay-pdfs.overlayFiles.addMore', 'Add more PDFs...')
: t('overlay-pdfs.overlayFiles.placeholder', 'Choose PDF(s)...')}
</Button>
{parameters.overlayFiles?.length > 0 && (() => {
return (
<div className={styles.fileListContainer}>
<Stack gap="xs">
{parameters.overlayFiles.map((file, index) => (
<Group
key={index}
justify="space-between"
p="xs"
className={styles.fileItem}
>
<Group gap="xs" className={styles.fileGroup}>
<div className={styles.fileNameContainer}>
<div
className={styles.fileName}
title={file.name}
>
{file.name}
</div>
</div>
<Text size="xs" c="dimmed" className={styles.fileSize}>
({(file.size / 1024).toFixed(1)} KB)
</Text>
</Group>
<ActionIcon
size="sm"
variant="subtle"
color="red"
className={styles.removeButton}
onClick={() => {
const next = (parameters.overlayFiles || []).filter((_, i) => i !== index);
handleOverlayFilesChange(next);
}}
disabled={disabled}
>
<LocalIcon icon="close-rounded" width="14" height="14" />
</ActionIcon>
</Group>
))}
</Stack>
</div>
);
})()}
</Stack>
</Stack>
);
}