mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-02-17 13:52:14 +01:00
# Description of Changes This pull request removes unnecessary imports of the default React object from multiple frontend files. The changes help clean up the codebase by only importing specific React hooks where needed, rather than importing the entire React object. **Code cleanup and import optimization:** * Removed default `React` imports from component files, retaining only the necessary React hooks (such as `useEffect`, `useState`, `useMemo`, etc.) in files like `FileEditor.tsx`, `FullscreenToolList.tsx`, `ToolPanel.tsx`, `PageNumberPreview.tsx`, `AdjustContrastPreview.tsx`, `AutomationRun.tsx`, `LocalEmbedPDFWithAnnotations.tsx`, `ToolRegistryProvider.tsx`, `useTranslatedToolRegistry.tsx`, and `AdjustContrast.tsx`. [[1]](diffhunk://#diff-481d0a2d8a1714d34d21181db63a020b08dfccfbfa80bf47ac9af382dff25310L1-R1) [[2]](diffhunk://#diff-1d6e9507cb0744e03ec0e80c510874bfc5054986b0275ae3b8592eb67b5ec0f2L1-R1) [[3]](diffhunk://#diff-8ee3da71652291722dc6130f44565c098fe0f9cdf5e8ec0ba3c631be8980b13eL1-R1) [[4]](diffhunk://#diff-ecc12bf9b557e947ae2f1866d07446b19bad1fbdf143bf231dd3076b1e794826L1-R1) [[5]](diffhunk://#diff-6ba4ca6f491368b62e160639e97207f5c1d35fee77f4eebd39133630e0ecb7a1L1-R1) [[6]](diffhunk://#diff-ff7cba3dba3b1f4ec4c8758a9fbe539351f96225284d0c61cca2642ec7a8e486L1-R1) [[7]](diffhunk://#diff-d99cf54aa50d266c08844fac31c79e73a7f1714adeedb186d1decab8b9fb7f78L1-R1) [[8]](diffhunk://#diff-3467ae2b00d2ea95c360bc367adfbae124a4fb1d2627e889d12fb00e288bf508L1-R1) [[9]](diffhunk://#diff-0a3e636736c137356dd9354ff3cacbd302ebda40147545e13c62d073525d1969L1-R1) [[10]](diffhunk://#diff-2fed64bea41254c30dcc038f6b92943272bcaa771af200b8a3dc1a2cef6b5ca7L2-R2) * Removed default `React` imports from presentational and settings components that do not use JSX at the top level, such as `Workbench.tsx`, `SliderWithInput.tsx`, `AdjustContrastBasicSettings.tsx`, `AdjustContrastColorSettings.tsx`, `AdjustContrastSingleStepSettings.tsx`, `FileSummaryHeader.tsx`, `SignatureSection.tsx`, `SignatureStatusBadge.tsx`, and `ThumbnailPreview.tsx`. [[1]](diffhunk://#diff-6ffa9f7048b8e2a454ccf52b712179784cf32d42ecac9c85331c595a4cee39b4L1) [[2]](diffhunk://#diff-a1159e58f6668bc6de9595b4014fd7b8e0a19f9efa75294ba80184cfe54b601fL1) [[3]](diffhunk://#diff-ce5bbd748c15bc456e7f01180b7ff04c80c782e3d6662384f28e032af36ed3ccL1) [[4]](diffhunk://#diff-494006ec5e237eb7b3a16b9bc144a6ed49ed38c547d95b68a89f69a5af6676ceL1) [[5]](diffhunk://#diff-e61a3e2d98c9601eea868062258b925e6f6d672f49df14e3684b12f736622db4L1) [[6]](diffhunk://#diff-97df8b451114e347bb3f581ff5c91057601fb821e224479e1106493ce9479dcdL1) [[7]](diffhunk://#diff-cc070bfc4dc892a4e9a2be725c9f27ab66bdbc821a525fad10e14b27096d4e5aL1) [[8]](diffhunk://#diff-c179df2634412e4938bcd686f86b3bdbd1a6039d8a8b62c44fd0c085cc58af74L1) [[9]](diffhunk://#diff-64403230a8c8e90135bd8d7cd275c40d8e22bd3a22ed642dec5451018eec3c10L1) These changes reduce unnecessary imports and make the codebase cleaner and more consistent. --- ## 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. Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
242 lines
8.4 KiB
TypeScript
242 lines
8.4 KiB
TypeScript
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { AddPageNumbersParameters } from './useAddPageNumbersParameters';
|
|
import { pdfWorkerManager } from '../../../services/pdfWorkerManager';
|
|
import { useThumbnailGeneration } from '../../../hooks/useThumbnailGeneration';
|
|
import styles from './PageNumberPreview.module.css';
|
|
|
|
// Simple utilities for page numbers (adapted from stamp)
|
|
const A4_ASPECT_RATIO = 0.707;
|
|
|
|
const getFirstSelectedPage = (input: string): number => {
|
|
if (!input) return 1;
|
|
const parts = input.split(',').map(s => s.trim()).filter(Boolean);
|
|
for (const part of parts) {
|
|
if (/^\d+\s*-\s*\d+$/.test(part)) {
|
|
const low = parseInt(part.split('-')[0].trim(), 10);
|
|
if (Number.isFinite(low) && low > 0) return low;
|
|
}
|
|
const n = parseInt(part, 10);
|
|
if (Number.isFinite(n) && n > 0) return n;
|
|
}
|
|
return 1;
|
|
};
|
|
|
|
|
|
const detectOverallBackgroundColor = async (thumbnailSrc: string | null): Promise<'light' | 'dark'> => {
|
|
if (!thumbnailSrc) {
|
|
return 'light'; // Default to light background if no thumbnail
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
const img = new Image();
|
|
img.crossOrigin = 'anonymous';
|
|
|
|
img.onload = () => {
|
|
try {
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
if (!ctx) {
|
|
resolve('light');
|
|
return;
|
|
}
|
|
|
|
canvas.width = img.width;
|
|
canvas.height = img.height;
|
|
ctx.drawImage(img, 0, 0);
|
|
|
|
// Sample the entire image at reduced resolution for performance
|
|
const sampleWidth = Math.min(100, img.width);
|
|
const sampleHeight = Math.min(100, img.height);
|
|
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
|
const data = imageData.data;
|
|
|
|
let totalBrightness = 0;
|
|
let pixelCount = 0;
|
|
|
|
// Sample every nth pixel for performance
|
|
const step = Math.max(1, Math.floor((img.width * img.height) / (sampleWidth * sampleHeight)));
|
|
|
|
for (let i = 0; i < data.length; i += 4 * step) {
|
|
const r = data[i];
|
|
const g = data[i + 1];
|
|
const b = data[i + 2];
|
|
|
|
// Calculate perceived brightness using luminance formula
|
|
const brightness = (0.299 * r + 0.587 * g + 0.114 * b);
|
|
totalBrightness += brightness;
|
|
pixelCount++;
|
|
}
|
|
|
|
const averageBrightness = totalBrightness / pixelCount;
|
|
|
|
// Threshold: 128 is middle gray
|
|
resolve(averageBrightness > 128 ? 'light' : 'dark');
|
|
} catch (error) {
|
|
console.warn('Error detecting background color:', error);
|
|
resolve('light'); // Default fallback
|
|
}
|
|
};
|
|
|
|
img.onerror = () => resolve('light');
|
|
img.src = thumbnailSrc;
|
|
});
|
|
};
|
|
|
|
type Props = {
|
|
parameters: AddPageNumbersParameters;
|
|
onParameterChange: <K extends keyof AddPageNumbersParameters>(key: K, value: AddPageNumbersParameters[K]) => void;
|
|
file?: File | null;
|
|
showQuickGrid?: boolean;
|
|
};
|
|
|
|
export default function PageNumberPreview({ parameters, onParameterChange, file, showQuickGrid }: Props) {
|
|
const { t } = useTranslation();
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const [, setContainerSize] = useState<{ width: number; height: number }>({ width: 0, height: 0 });
|
|
const [pageSize, setPageSize] = useState<{ widthPts: number; heightPts: number } | null>(null);
|
|
const [pageThumbnail, setPageThumbnail] = useState<string | null>(null);
|
|
const { requestThumbnail } = useThumbnailGeneration();
|
|
const [hoverTile, setHoverTile] = useState<number | null>(null);
|
|
const [textColor, setTextColor] = useState<string>('#fff');
|
|
|
|
// Observe container size for responsive positioning
|
|
useEffect(() => {
|
|
const node = containerRef.current;
|
|
if (!node) return;
|
|
const resize = () => {
|
|
const aspect = pageSize ? (pageSize.widthPts / pageSize.heightPts) : A4_ASPECT_RATIO;
|
|
setContainerSize({ width: node.clientWidth, height: node.clientWidth / aspect });
|
|
};
|
|
resize();
|
|
const ro = new ResizeObserver(resize);
|
|
ro.observe(node);
|
|
return () => ro.disconnect();
|
|
}, [pageSize]);
|
|
|
|
// Load first PDF page size in points for accurate scaling
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
const load = async () => {
|
|
if (!file || file.type !== 'application/pdf') {
|
|
setPageSize(null);
|
|
return;
|
|
}
|
|
try {
|
|
const buffer = await file.arrayBuffer();
|
|
const pdf = await pdfWorkerManager.createDocument(buffer, { disableAutoFetch: true, disableStream: true });
|
|
const page = await pdf.getPage(1);
|
|
const viewport = page.getViewport({ scale: 1 });
|
|
if (!cancelled) {
|
|
setPageSize({ widthPts: viewport.width, heightPts: viewport.height });
|
|
}
|
|
pdfWorkerManager.destroyDocument(pdf);
|
|
} catch {
|
|
if (!cancelled) setPageSize(null);
|
|
}
|
|
};
|
|
load();
|
|
return () => { cancelled = true; };
|
|
}, [file]);
|
|
|
|
// Load first-page thumbnail for background preview
|
|
useEffect(() => {
|
|
let isActive = true;
|
|
const loadThumb = async () => {
|
|
if (!file || file.type !== 'application/pdf') {
|
|
setPageThumbnail(null);
|
|
return;
|
|
}
|
|
try {
|
|
const pageNumber = Math.max(1, getFirstSelectedPage(parameters.pagesToNumber || '1'));
|
|
const pageId = `${file.name}:${file.size}:${file.lastModified}:page:${pageNumber}`;
|
|
const thumb = await requestThumbnail(pageId, file, pageNumber);
|
|
if (isActive) setPageThumbnail(thumb || null);
|
|
} catch {
|
|
if (isActive) setPageThumbnail(null);
|
|
}
|
|
};
|
|
loadThumb();
|
|
return () => { isActive = false; };
|
|
}, [file, parameters.pagesToNumber, requestThumbnail]);
|
|
|
|
// Detect text color based on overall PDF background
|
|
useEffect(() => {
|
|
if (!pageThumbnail) {
|
|
setTextColor('#fff'); // Default to white for no thumbnail
|
|
return;
|
|
}
|
|
|
|
const detectColor = async () => {
|
|
const backgroundType = await detectOverallBackgroundColor(pageThumbnail);
|
|
setTextColor(backgroundType === 'light' ? '#000' : '#fff');
|
|
};
|
|
|
|
detectColor();
|
|
}, [pageThumbnail]);
|
|
|
|
const containerStyle = useMemo(() => ({
|
|
position: 'relative' as const,
|
|
width: '100%',
|
|
aspectRatio: `${(pageSize?.widthPts ?? 595.28) / (pageSize?.heightPts ?? 841.89)} / 1`,
|
|
backgroundColor: pageThumbnail ? 'transparent' : 'rgba(255,255,255,0.03)',
|
|
border: '1px solid var(--border-default, #333)',
|
|
overflow: 'hidden' as const
|
|
}), [pageSize, pageThumbnail]);
|
|
|
|
return (
|
|
<div>
|
|
<div className={styles.previewHeader}>
|
|
<div className={styles.divider} />
|
|
<div className={styles.previewLabel}>{t('addPageNumbers.preview', 'Preview Page Numbers')}</div>
|
|
</div>
|
|
<div
|
|
ref={containerRef}
|
|
className={`${styles.container} ${styles.containerBorder} ${pageThumbnail ? styles.containerWithThumbnail : styles.containerWithoutThumbnail}`}
|
|
style={containerStyle}
|
|
>
|
|
{pageThumbnail && (
|
|
<img
|
|
src={pageThumbnail}
|
|
alt="page preview"
|
|
className={`${styles.pageThumbnail} ph-no-capture`}
|
|
draggable={false}
|
|
/>
|
|
)}
|
|
|
|
{/* Quick position overlay grid - EXACT copy from stamp */}
|
|
{showQuickGrid && (
|
|
<div className={styles.quickGrid}>
|
|
{Array.from({ length: 9 }).map((_, i) => {
|
|
const idx = (i + 1) as 1|2|3|4|5|6|7|8|9;
|
|
const selected = parameters.position === idx;
|
|
return (
|
|
<button
|
|
key={idx}
|
|
type="button"
|
|
className={`${styles.gridTile} ${selected || hoverTile === idx ? styles.gridTileSelected : ''} ${hoverTile === idx ? styles.gridTileHovered : ''}`}
|
|
onClick={() => onParameterChange('position', idx as any)}
|
|
onMouseEnter={() => setHoverTile(idx)}
|
|
onMouseLeave={() => setHoverTile(null)}
|
|
style={{
|
|
color: textColor,
|
|
textShadow: textColor === '#fff'
|
|
? '1px 1px 2px rgba(0, 0, 0, 0.8)'
|
|
: '1px 1px 2px rgba(255, 255, 255, 0.8)'
|
|
}}
|
|
>
|
|
{idx}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className={styles.previewDisclaimer}>
|
|
{t('addPageNumbers.previewDisclaimer', 'Preview is approximate. Final output may vary due to PDF font metrics.')}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|