mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-12-18 20:04:17 +01:00
change requests + allow transitioning between previews when multiple files are selected
This commit is contained in:
parent
1ccc1ac11a
commit
2fc9cd9724
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import { Stack } from '@mantine/core';
|
||||
import { AdjustContrastParameters } from '../../../hooks/tools/adjustContrast/useAdjustContrastParameters';
|
||||
import AdjustContrastBasicSettings from './AdjustContrastBasicSettings';
|
||||
import AdjustContrastColorSettings from './AdjustContrastColorSettings';
|
||||
|
||||
interface Props {
|
||||
parameters: AdjustContrastParameters;
|
||||
onParameterChange: <K extends keyof AdjustContrastParameters>(key: K, value: AdjustContrastParameters[K]) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
// Single-step settings used by Automate to configure Adjust Contrast in one panel
|
||||
export default function AdjustContrastSingleStepSettings({ parameters, onParameterChange, disabled }: Props) {
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
<AdjustContrastBasicSettings
|
||||
parameters={parameters}
|
||||
onParameterChange={onParameterChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<AdjustContrastColorSettings
|
||||
parameters={parameters}
|
||||
onParameterChange={onParameterChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ import { reorganizePagesOperationConfig } from "../hooks/tools/reorganizePages/u
|
||||
import RemovePassword from "../tools/RemovePassword";
|
||||
import { SubcategoryId, ToolCategoryId, ToolRegistry } from "./toolsTaxonomy";
|
||||
import AdjustContrast from "../tools/AdjustContrast";
|
||||
import AdjustContrastSingleStepSettings from "../components/tools/adjustContrast/AdjustContrastSingleStepSettings";
|
||||
import { adjustContrastOperationConfig } from "../hooks/tools/adjustContrast/useAdjustContrastOperation";
|
||||
import { getSynonyms } from "../utils/toolSynonyms";
|
||||
import AddWatermark from "../tools/AddWatermark";
|
||||
@ -642,6 +643,7 @@ export function useFlatToolRegistry(): ToolRegistry {
|
||||
categoryId: ToolCategoryId.ADVANCED_TOOLS,
|
||||
subcategoryId: SubcategoryId.ADVANCED_FORMATTING,
|
||||
operationConfig: adjustContrastOperationConfig,
|
||||
settingsComponent: AdjustContrastSingleStepSettings,
|
||||
synonyms: getSynonyms(t, "adjustContrast"),
|
||||
},
|
||||
repair: {
|
||||
|
||||
@ -3,6 +3,7 @@ import { ToolType, useToolOperation } from '../shared/useToolOperation';
|
||||
import { AdjustContrastParameters, defaultParameters } from './useAdjustContrastParameters';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { applyAdjustmentsToCanvas } from '../../../components/tools/adjustContrast/utils';
|
||||
import { pdfWorkerManager } from '../../../services/pdfWorkerManager';
|
||||
import { createFileFromApiResponse } from '../../../utils/fileResponseUtils';
|
||||
|
||||
async function renderPdfPageToCanvas(pdf: any, pageNumber: number, scale: number): Promise<HTMLCanvasElement> {
|
||||
@ -19,11 +20,8 @@ async function renderPdfPageToCanvas(pdf: any, pageNumber: number, scale: number
|
||||
|
||||
// adjustment logic moved to shared util
|
||||
|
||||
async function processPdfClientSide(params: AdjustContrastParameters, files: File[]): Promise<File[]> {
|
||||
const outputs: File[] = [];
|
||||
const { pdfWorkerManager } = await import('../../../services/pdfWorkerManager');
|
||||
|
||||
for (const file of files) {
|
||||
// Render, adjust, and assemble all pages of a single PDF into a new PDF
|
||||
async function buildAdjustedPdfForFile(file: File, params: AdjustContrastParameters): Promise<File> {
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const pdf = await pdfWorkerManager.createDocument(arrayBuffer, {});
|
||||
const pageCount = pdf.numPages;
|
||||
@ -44,11 +42,37 @@ async function processPdfClientSide(params: AdjustContrastParameters, files: Fil
|
||||
|
||||
const pdfBytes = await newDoc.save();
|
||||
const out = createFileFromApiResponse(pdfBytes, { 'content-type': 'application/pdf' }, file.name);
|
||||
outputs.push(out);
|
||||
pdfWorkerManager.destroyDocument(pdf);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
return outputs;
|
||||
async function processPdfClientSide(params: AdjustContrastParameters, files: File[]): Promise<File[]> {
|
||||
// Limit concurrency to avoid exhausting memory/CPU while still getting speedups
|
||||
// Heuristic: use up to 4 workers on capable machines, otherwise 2-3
|
||||
let CONCURRENCY_LIMIT = 2;
|
||||
if (typeof navigator !== 'undefined' && typeof navigator.hardwareConcurrency === 'number') {
|
||||
if (navigator.hardwareConcurrency >= 8) CONCURRENCY_LIMIT = 4;
|
||||
else if (navigator.hardwareConcurrency >= 4) CONCURRENCY_LIMIT = 3;
|
||||
}
|
||||
CONCURRENCY_LIMIT = Math.min(CONCURRENCY_LIMIT, files.length);
|
||||
|
||||
const mapWithConcurrency = async <T, R>(items: T[], limit: number, worker: (item: T, index: number) => Promise<R>): Promise<R[]> => {
|
||||
const results: R[] = new Array(items.length);
|
||||
let nextIndex = 0;
|
||||
|
||||
const workers = new Array(Math.min(limit, items.length)).fill(0).map(async () => {
|
||||
let current = nextIndex++;
|
||||
while (current < items.length) {
|
||||
results[current] = await worker(items[current], current);
|
||||
current = nextIndex++;
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(workers);
|
||||
return results;
|
||||
};
|
||||
|
||||
return mapWithConcurrency(files, CONCURRENCY_LIMIT, (file) => buildAdjustedPdfForFile(file, params));
|
||||
}
|
||||
|
||||
export const adjustContrastOperationConfig = {
|
||||
@ -56,6 +80,8 @@ export const adjustContrastOperationConfig = {
|
||||
customProcessor: processPdfClientSide,
|
||||
operationType: 'adjustContrast',
|
||||
defaultParameters,
|
||||
// Single-step settings component for Automate
|
||||
settingsComponentPath: 'components/tools/adjustContrast/AdjustContrastSingleStepSettings',
|
||||
} as const;
|
||||
|
||||
export const useAdjustContrastOperation = () => {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { createToolFlow } from '../components/tools/shared/createToolFlow';
|
||||
import { BaseToolProps, ToolComponent } from '../types/tool';
|
||||
import { useBaseTool } from '../hooks/tools/shared/useBaseTool';
|
||||
@ -8,6 +9,7 @@ import AdjustContrastBasicSettings from '../components/tools/adjustContrast/Adju
|
||||
import AdjustContrastColorSettings from '../components/tools/adjustContrast/AdjustContrastColorSettings';
|
||||
import AdjustContrastPreview from '../components/tools/adjustContrast/AdjustContrastPreview';
|
||||
import { useAccordionSteps } from '../hooks/tools/shared/useAccordionSteps';
|
||||
import NavigationArrows from '../components/shared/filePreview/NavigationArrows';
|
||||
|
||||
const AdjustContrast = (props: BaseToolProps) => {
|
||||
const { t } = useTranslation();
|
||||
@ -27,6 +29,23 @@ const AdjustContrast = (props: BaseToolProps) => {
|
||||
afterResults: base.handleSettingsReset
|
||||
});
|
||||
|
||||
// Track which selected file is being previewed. Clamp when selection changes.
|
||||
const [previewIndex, setPreviewIndex] = useState(0);
|
||||
const totalSelected = base.selectedFiles.length;
|
||||
|
||||
useEffect(() => {
|
||||
if (previewIndex >= totalSelected) {
|
||||
setPreviewIndex(Math.max(0, totalSelected - 1));
|
||||
}
|
||||
}, [totalSelected, previewIndex]);
|
||||
|
||||
const currentFile = useMemo(() => {
|
||||
return totalSelected > 0 ? base.selectedFiles[previewIndex] : null;
|
||||
}, [base.selectedFiles, previewIndex, totalSelected]);
|
||||
|
||||
const handlePrev = () => setPreviewIndex((i) => Math.max(0, i - 1));
|
||||
const handleNext = () => setPreviewIndex((i) => Math.min(totalSelected - 1, i + 1));
|
||||
|
||||
return createToolFlow({
|
||||
files: {
|
||||
selectedFiles: base.selectedFiles,
|
||||
@ -59,10 +78,25 @@ const AdjustContrast = (props: BaseToolProps) => {
|
||||
},
|
||||
],
|
||||
preview: (
|
||||
<AdjustContrastPreview
|
||||
file={base.selectedFiles[0] || null}
|
||||
parameters={base.params.parameters}
|
||||
/>
|
||||
<div>
|
||||
<NavigationArrows
|
||||
onPrevious={handlePrev}
|
||||
onNext={handleNext}
|
||||
disabled={totalSelected <= 1}
|
||||
>
|
||||
<div style={{ width: '100%' }}>
|
||||
<AdjustContrastPreview
|
||||
file={currentFile || null}
|
||||
parameters={base.params.parameters}
|
||||
/>
|
||||
</div>
|
||||
</NavigationArrows>
|
||||
{totalSelected > 1 && (
|
||||
<div style={{ textAlign: 'center', marginTop: 8, fontSize: 12, color: 'var(--text-color-muted)' }}>
|
||||
{`${previewIndex + 1} of ${totalSelected}`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
executeButton: {
|
||||
text: t('adjustContrast.confirm', 'Confirm'),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user