diff --git a/frontend/src/components/tools/sign/SignSettings.tsx b/frontend/src/components/tools/sign/SignSettings.tsx index 9cf238b61..b23c3d142 100644 --- a/frontend/src/components/tools/sign/SignSettings.tsx +++ b/frontend/src/components/tools/sign/SignSettings.tsx @@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next"; import { Stack, TextInput, FileInput, Paper, Group, Button, Text, Alert, Modal, ColorSwatch, Menu, ActionIcon, Slider, Select, Combobox, useCombobox, ColorPicker, Tabs } from '@mantine/core'; import ButtonSelector from "../../shared/ButtonSelector"; import { SignParameters } from "../../../hooks/tools/sign/useSignParameters"; +import { SuggestedToolsSection } from "../shared/SuggestedToolsSection"; interface SignSettingsProps { parameters: SignParameters; @@ -14,9 +15,10 @@ interface SignSettingsProps { onUpdateDrawSettings?: (color: string, size: number) => void; onUndo?: () => void; onRedo?: () => void; + onSave?: () => void; } -const SignSettings = ({ parameters, onParameterChange, disabled = false, onActivateDrawMode, onActivateSignaturePlacement, onDeactivateSignature, onUpdateDrawSettings, onUndo, onRedo }: SignSettingsProps) => { +const SignSettings = ({ parameters, onParameterChange, disabled = false, onActivateDrawMode, onActivateSignaturePlacement, onDeactivateSignature, onUpdateDrawSettings, onUndo, onRedo, onSave }: SignSettingsProps) => { const { t } = useTranslation(); const canvasRef = useRef(null); const [isDrawing, setIsDrawing] = useState(false); @@ -439,9 +441,6 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv onChange={(value) => onParameterChange('signatureType', value as 'image' | 'text' | 'draw' | 'canvas')} > - - {t('sign.type.draw', 'Draw')} - {t('sign.type.canvas', 'Canvas')} @@ -451,6 +450,9 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv {t('sign.type.text', 'Text')} + + {t('sign.type.draw', 'Draw')} + @@ -472,6 +474,7 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv + {/* Signature Creation based on type */} {parameters.signatureType === 'canvas' && ( @@ -958,6 +961,21 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv + + {/* Save Button */} + {onSave && ( + + )} + + {/* Suggested Tools Section */} + ); }; diff --git a/frontend/src/hooks/tools/sign/useSignParameters.ts b/frontend/src/hooks/tools/sign/useSignParameters.ts index e907f7f89..da0035312 100644 --- a/frontend/src/hooks/tools/sign/useSignParameters.ts +++ b/frontend/src/hooks/tools/sign/useSignParameters.ts @@ -20,7 +20,7 @@ export interface SignParameters { } export const DEFAULT_PARAMETERS: SignParameters = { - signatureType: 'draw', + signatureType: 'canvas', reason: 'Document signing', location: 'Digital', signerName: '', diff --git a/frontend/src/tools/Sign.tsx b/frontend/src/tools/Sign.tsx index 0ea9f1ddf..2ab877575 100644 --- a/frontend/src/tools/Sign.tsx +++ b/frontend/src/tools/Sign.tsx @@ -1,4 +1,4 @@ -import { useEffect, useCallback } from "react"; +import { useEffect, useCallback, useRef } from "react"; import { useTranslation } from "react-i18next"; import { createToolFlow } from "../components/tools/shared/createToolFlow"; import { useSignParameters } from "../hooks/tools/sign/useSignParameters"; @@ -8,11 +8,22 @@ import { BaseToolProps, ToolComponent } from "../types/tool"; import SignSettings from "../components/tools/sign/SignSettings"; import { useNavigation } from "../contexts/NavigationContext"; import { useSignature } from "../contexts/SignatureContext"; +import { useFileActions, useFileContext } from "../contexts/FileContext"; +import { useViewer } from "../contexts/ViewerContext"; +import { generateThumbnailWithMetadata } from "../utils/thumbnailUtils"; +import { createNewStirlingFileStub, createStirlingFile, StirlingFileStub, StirlingFile, FileId, extractFiles } from "../types/fileContext"; +import { createProcessedFile } from "../contexts/file/fileActions"; const Sign = (props: BaseToolProps) => { const { t } = useTranslation(); const { setWorkbench } = useNavigation(); - const { setSignatureConfig, activateDrawMode, activateSignaturePlacementMode, deactivateDrawMode, updateDrawSettings, undo, redo } = useSignature(); + const { setSignatureConfig, activateDrawMode, activateSignaturePlacementMode, deactivateDrawMode, updateDrawSettings, undo, redo, isPlacementMode } = useSignature(); + const { actions } = useFileActions(); + const { consumeFiles, selectors } = useFileContext(); + const { exportActions } = useViewer(); + + // Track which signature mode was active for reactivation after save + const activeModeRef = useRef<'draw' | 'placement' | null>(null); // Manual sync function const syncSignatureConfig = () => { @@ -47,30 +58,125 @@ const Sign = (props: BaseToolProps) => { setSignatureConfig(base.params.parameters); }, [base.params.parameters, setSignatureConfig]); + // Save signed files to the system - apply signatures using EmbedPDF and replace original + const handleSaveToSystem = useCallback(async () => { + try { + console.log('Save started - attempting to get PDF from viewer...'); + + // Use EmbedPDF's saveAsCopy to apply signatures and get ArrayBuffer + const pdfArrayBuffer = await exportActions.saveAsCopy(); + console.log('Got PDF ArrayBuffer:', pdfArrayBuffer ? `${pdfArrayBuffer.byteLength} bytes` : 'null'); + + console.log('Checking conditions - ArrayBuffer exists:', !!pdfArrayBuffer, 'Selected files:', base.selectedFiles.length); + + if (pdfArrayBuffer) { + console.log('Conditions met, starting file processing...'); + + // Convert ArrayBuffer to File + const blob = new Blob([pdfArrayBuffer], { type: 'application/pdf' }); + + // Get the current file - try from base.selectedFiles first, then from all files + let originalFile = null; + if (base.selectedFiles.length > 0) { + originalFile = base.selectedFiles[0]; + } else { + const allFileIds = selectors.getAllFileIds(); + if (allFileIds.length > 0) { + const fileStub = selectors.getStirlingFileStub(allFileIds[0]); + const fileObject = selectors.getFile(allFileIds[0]); + if (fileStub && fileObject) { + originalFile = createStirlingFile(fileObject, allFileIds[0]); + } + } + } + + if (!originalFile) { + console.error('No file available to replace'); + return; + } + console.log('Original file:', originalFile.name, 'ID:', originalFile.fileId); + + const signedFile = new File([blob], originalFile.name, { type: 'application/pdf' }); + console.log('Created signed file:', signedFile.name, 'Size:', signedFile.size); + + console.log('Processing signed file...'); + + // Generate thumbnail and metadata for the signed file + const thumbnailResult = await generateThumbnailWithMetadata(signedFile); + const processedFileMetadata = createProcessedFile(thumbnailResult.pageCount, thumbnailResult.thumbnail); + + // Prepare input file data for replacement + const inputFileIds: FileId[] = [originalFile.fileId]; + const inputStirlingFileStubs: StirlingFileStub[] = []; + + console.log('Original file ID:', originalFile.fileId); + const record = selectors.getStirlingFileStub(originalFile.fileId); + if (record) { + inputStirlingFileStubs.push(record); + console.log('Found file record for replacement'); + } else { + console.error('No file record found for:', originalFile.fileId); + } + + // Create output stub and file + const outputStub = createNewStirlingFileStub(signedFile, undefined, thumbnailResult.thumbnail, processedFileMetadata); + const outputStirlingFile = createStirlingFile(signedFile, outputStub.id); + console.log('Created new file with ID:', outputStub.id); + + // Replace the original file with the signed version + console.log('Replacing file in context...'); + await consumeFiles(inputFileIds, [outputStirlingFile], [outputStub]); + console.log('File replacement complete'); + + // Reactivate the signature mode that was active before save + setTimeout(() => { + if (activeModeRef.current === 'draw') { + console.log('Reactivating draw mode'); + activateDrawMode(); + } else if (activeModeRef.current === 'placement') { + console.log('Reactivating placement mode'); + handleSignaturePlacement(); + } + }, 200); + } else { + console.log('Save aborted - conditions not met'); + if (!pdfArrayBuffer) console.log('No PDF ArrayBuffer received'); + if (base.selectedFiles.length === 0) console.log('No selected files'); + } + } catch (error) { + console.error('Error saving signed document:', error); + } + }, [exportActions, base.selectedFiles, selectors, consumeFiles]); + const getSteps = () => { const steps = []; - // Step 1: Signature Configuration - if (base.selectedFiles.length > 0 || base.operation.files.length > 0) { - steps.push({ - title: t('sign.steps.configure', 'Configure Signature'), - isCollapsed: base.operation.files.length > 0, - onCollapsedClick: base.operation.files.length > 0 ? base.handleSettingsReset : undefined, - content: ( - activateDrawMode()} - onActivateSignaturePlacement={handleSignaturePlacement} - onDeactivateSignature={deactivateDrawMode} - onUpdateDrawSettings={updateDrawSettings} - onUndo={undo} - onRedo={redo} - /> - ), - }); - } + // Step 1: Signature Configuration - Always visible + steps.push({ + title: t('sign.steps.configure', 'Configure Signature'), + isCollapsed: false, + onCollapsedClick: undefined, + content: ( + { + activeModeRef.current = 'draw'; + activateDrawMode(); + }} + onActivateSignaturePlacement={() => { + activeModeRef.current = 'placement'; + handleSignaturePlacement(); + }} + onDeactivateSignature={deactivateDrawMode} + onUpdateDrawSettings={updateDrawSettings} + onUndo={undo} + onRedo={redo} + onSave={handleSaveToSystem} + /> + ), + }); return steps; }; @@ -81,19 +187,12 @@ const Sign = (props: BaseToolProps) => { isCollapsed: base.operation.files.length > 0, }, steps: getSteps(), - executeButton: { - text: t('sign.submit', 'Sign Document'), - isVisible: false, // Hide the execute button - signatures are placed directly - loadingText: t('loading'), - onClick: base.handleExecute, - disabled: !base.params.validateParameters() || base.selectedFiles.length === 0 || !base.endpointEnabled, - }, review: { - isVisible: base.operation.files.length > 0, + isVisible: false, // Hide review section - save moved to configure section operation: base.operation, title: t('sign.results.title', 'Signature Results'), onFileClick: base.handleThumbnailClick, - onUndo: base.handleUndo, + onUndo: () => {}, }, forceStepNumbers: true, }); @@ -102,7 +201,7 @@ const Sign = (props: BaseToolProps) => { // Add the required static methods for automation Sign.tool = () => useSignOperation; Sign.getDefaultParameters = () => ({ - signatureType: 'draw', + signatureType: 'canvas', reason: 'Document signing', location: 'Digital', signerName: '',