change requests + allow transitioning between previews when multiple files are selected

This commit is contained in:
EthanHealy01 2025-10-01 21:42:23 +01:00
parent 1ccc1ac11a
commit 2fc9cd9724
4 changed files with 105 additions and 12 deletions

View File

@ -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>
);
}

View File

@ -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: {

View File

@ -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 = () => {

View File

@ -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'),