mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 22:29:24 +00:00
Redesign Merge around new major merges into V2
This commit is contained in:
parent
dd20a3c0a3
commit
5ed6cc3cfc
@ -104,12 +104,18 @@ export function fileContextReducer(state: FileContextState, action: FileContextA
|
||||
|
||||
// Validate that all IDs exist in current state
|
||||
const validIds = orderedFileIds.filter(id => state.files.byId[id]);
|
||||
// Reorder selected files by passed order
|
||||
const selectedFileIds = orderedFileIds.filter(id => state.ui.selectedFileIds.includes(id));
|
||||
|
||||
return {
|
||||
...state,
|
||||
files: {
|
||||
...state.files,
|
||||
ids: validIds
|
||||
},
|
||||
ui: {
|
||||
...state.ui,
|
||||
selectedFileIds,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -135,13 +135,13 @@ export function useAllFiles(): { files: File[]; records: FileRecord[]; fileIds:
|
||||
/**
|
||||
* Hook for selected files (optimized for selection-based UI)
|
||||
*/
|
||||
export function useSelectedFiles(): { files: File[]; records: FileRecord[]; fileIds: FileId[] } {
|
||||
export function useSelectedFiles(): { selectedFiles: File[]; selectedRecords: FileRecord[]; selectedFileIds: FileId[] } {
|
||||
const { state, selectors } = useFileState();
|
||||
|
||||
return useMemo(() => ({
|
||||
files: selectors.getSelectedFiles(),
|
||||
records: selectors.getSelectedFileRecords(),
|
||||
fileIds: state.ui.selectedFileIds
|
||||
selectedFiles: selectors.getSelectedFiles(),
|
||||
selectedRecords: selectors.getSelectedFileRecords(),
|
||||
selectedFileIds: state.ui.selectedFileIds
|
||||
}), [state.ui.selectedFileIds, selectors]);
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ import { ocrOperationConfig } from '../hooks/tools/ocr/useOCROperation';
|
||||
import { convertOperationConfig } from '../hooks/tools/convert/useConvertOperation';
|
||||
import { removeCertificateSignOperationConfig } from '../hooks/tools/removeCertificateSign/useRemoveCertificateSignOperation';
|
||||
import { changePermissionsOperationConfig } from '../hooks/tools/changePermissions/useChangePermissionsOperation';
|
||||
import { mergeOperationConfig } from '../hooks/tools/merge/useMergeOperation';
|
||||
import CompressSettings from '../components/tools/compress/CompressSettings';
|
||||
import SplitSettings from '../components/tools/split/SplitSettings';
|
||||
import AddPasswordSettings from '../components/tools/addPassword/AddPasswordSettings';
|
||||
@ -39,6 +40,7 @@ import AddWatermarkSingleStepSettings from '../components/tools/addWatermark/Add
|
||||
import OCRSettings from '../components/tools/ocr/OCRSettings';
|
||||
import ConvertSettings from '../components/tools/convert/ConvertSettings';
|
||||
import ChangePermissionsSettings from '../components/tools/changePermissions/ChangePermissionsSettings';
|
||||
import MergeSettings from '../components/tools/merge/MergeSettings';
|
||||
|
||||
const showPlaceholderTools = false; // For development purposes. Allows seeing the full list of tools, even if they're unimplemented
|
||||
|
||||
@ -636,6 +638,8 @@ export function useFlatToolRegistry(): ToolRegistry {
|
||||
subcategoryId: SubcategoryId.GENERAL,
|
||||
maxFiles: -1,
|
||||
endpoints: ["merge-pdfs"],
|
||||
operationConfig: mergeOperationConfig,
|
||||
settingsComponent: MergeSettings
|
||||
},
|
||||
"multi-tool": {
|
||||
icon: <span className="material-symbols-rounded">dashboard_customize</span>,
|
||||
|
@ -20,12 +20,12 @@ vi.mock('../../../utils/toolErrorHandler', () => ({
|
||||
}));
|
||||
|
||||
// Import the mocked function
|
||||
import { ToolOperationConfig, ToolOperationHook, useToolOperation } from '../shared/useToolOperation';
|
||||
import { ToolOperationHook, useToolOperation } from '../shared/useToolOperation';
|
||||
|
||||
describe('useMergeOperation', () => {
|
||||
const mockUseToolOperation = vi.mocked(useToolOperation);
|
||||
const mockUseToolOperation = vi.mocked(useToolOperation<MergeParameters>);
|
||||
|
||||
const getToolConfig = (): ToolOperationConfig<MergeParameters> => mockUseToolOperation.mock.calls[0][0];
|
||||
const getToolConfig = () => mockUseToolOperation.mock.calls[0][0];
|
||||
|
||||
const mockToolOperationReturn: ToolOperationHook<unknown> = {
|
||||
files: [],
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useToolOperation, ResponseHandler } from '../shared/useToolOperation';
|
||||
import { useToolOperation, ResponseHandler, ToolOperationConfig } from '../shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '../../../utils/toolErrorHandler';
|
||||
import { MergeParameters } from './useMergeParameters';
|
||||
|
||||
@ -21,16 +21,21 @@ const mergeResponseHandler: ResponseHandler = (blob: Blob, originalFiles: File[]
|
||||
return [new File([blob], filename, { type: 'application/pdf' })];
|
||||
};
|
||||
|
||||
// Operation configuration for automation
|
||||
export const mergeOperationConfig: ToolOperationConfig<MergeParameters> = {
|
||||
operationType: 'merge',
|
||||
endpoint: '/api/v1/general/merge-pdfs',
|
||||
buildFormData,
|
||||
filePrefix: 'merged_',
|
||||
multiFileEndpoint: true,
|
||||
responseHandler: mergeResponseHandler,
|
||||
};
|
||||
|
||||
export const useMergeOperation = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return useToolOperation<MergeParameters>({
|
||||
operationType: 'merge',
|
||||
endpoint: '/api/v1/general/merge-pdfs',
|
||||
buildFormData,
|
||||
filePrefix: 'merged_',
|
||||
multiFileEndpoint: true, // Single API call with all files
|
||||
responseHandler: mergeResponseHandler, // Handle single PDF response
|
||||
...mergeOperationConfig,
|
||||
getErrorMessage: createStandardErrorHandler(t('merge.error.failed', 'An error occurred while merging the PDFs.'))
|
||||
});
|
||||
};
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useEndpointEnabled } from "../hooks/useEndpointConfig";
|
||||
import { useFileContext } from "../contexts/FileContext";
|
||||
import { useToolFileSelection, useFileSelectionActions } from "../contexts/FileSelectionContext";
|
||||
import { useFileSelection, useFileManagement, useSelectedFiles, useAllFiles } from "../contexts/FileContext";
|
||||
|
||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||
import MergeSettings from "../components/tools/merge/MergeSettings";
|
||||
@ -14,9 +13,10 @@ import { BaseToolProps } from "../types/tool";
|
||||
|
||||
const Merge = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { setCurrentMode } = useFileContext();
|
||||
const { selectedFiles } = useToolFileSelection();
|
||||
const { setSelectedFiles } = useFileSelectionActions();
|
||||
const { selectedFiles, selectedFileIds } = useFileSelection();
|
||||
const { fileIds } = useAllFiles()
|
||||
const { selectedRecords } = useSelectedFiles()
|
||||
const { reorderFiles } = useFileManagement();
|
||||
|
||||
const mergeParams = useMergeParameters();
|
||||
const mergeOperation = useMergeOperation();
|
||||
@ -45,36 +45,35 @@ const Merge = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
const handleThumbnailClick = (file: File) => {
|
||||
onPreviewFile?.(file);
|
||||
sessionStorage.setItem("previousMode", "merge");
|
||||
setCurrentMode("viewer");
|
||||
};
|
||||
|
||||
const handleSettingsReset = () => {
|
||||
mergeOperation.resetResults();
|
||||
onPreviewFile?.(null);
|
||||
setCurrentMode("merge");
|
||||
};
|
||||
|
||||
// TODO: Move to more general place so other tools can use it
|
||||
const sortFiles = useCallback((sortType: 'filename' | 'dateModified', ascending: boolean = true) => {
|
||||
setSelectedFiles(((prevFiles: File[]) => {
|
||||
const sortedFiles = [...prevFiles].sort((a, b) => {
|
||||
let comparison = 0;
|
||||
// Sort the FileIds based on their corresponding File properties
|
||||
const sortedRecords = [...selectedRecords].sort((recordA, recordB) => {
|
||||
let comparison = 0;
|
||||
switch (sortType) {
|
||||
case 'filename':
|
||||
comparison = recordA.name.localeCompare(recordB.name);
|
||||
break;
|
||||
case 'dateModified':
|
||||
comparison = recordA.lastModified - recordB.lastModified;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (sortType) {
|
||||
case 'filename':
|
||||
comparison = a.name.localeCompare(b.name);
|
||||
break;
|
||||
case 'dateModified':
|
||||
comparison = a.lastModified - b.lastModified;
|
||||
break;
|
||||
}
|
||||
return ascending ? comparison : -comparison;
|
||||
});
|
||||
|
||||
return ascending ? comparison : -comparison;
|
||||
});
|
||||
const selectedIds = sortedRecords.map(record => record.id);
|
||||
const deselectedIds = fileIds.filter(id => !selectedIds.includes(id));
|
||||
|
||||
return sortedFiles;
|
||||
}) as any /* FIX ME: Parameter type is wrong on setSelectedFiles */);
|
||||
}, []);
|
||||
reorderFiles([...selectedIds, ...deselectedIds]); // Move all sorted IDs to the front of the workbench
|
||||
}, [selectedFiles, selectedFileIds, reorderFiles]);
|
||||
|
||||
const minFiles = 2; // Merging one file doesn't make sense
|
||||
const hasFiles = selectedFiles.length >= minFiles;
|
||||
@ -83,7 +82,7 @@ const Merge = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
|
||||
|
||||
return createToolFlow({
|
||||
files: {
|
||||
selectedFiles,
|
||||
selectedFiles: selectedFiles,
|
||||
isCollapsed: hasFiles && !hasResults,
|
||||
placeholder: "Select multiple PDF files to merge",
|
||||
minFiles: minFiles,
|
||||
|
Loading…
x
Reference in New Issue
Block a user