mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-09-26 17:52:59 +02:00
Save file
This commit is contained in:
parent
a8a0808274
commit
023fd43b72
@ -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 { 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 ButtonSelector from "../../shared/ButtonSelector";
|
||||||
import { SignParameters } from "../../../hooks/tools/sign/useSignParameters";
|
import { SignParameters } from "../../../hooks/tools/sign/useSignParameters";
|
||||||
|
import { SuggestedToolsSection } from "../shared/SuggestedToolsSection";
|
||||||
|
|
||||||
interface SignSettingsProps {
|
interface SignSettingsProps {
|
||||||
parameters: SignParameters;
|
parameters: SignParameters;
|
||||||
@ -14,9 +15,10 @@ interface SignSettingsProps {
|
|||||||
onUpdateDrawSettings?: (color: string, size: number) => void;
|
onUpdateDrawSettings?: (color: string, size: number) => void;
|
||||||
onUndo?: () => void;
|
onUndo?: () => void;
|
||||||
onRedo?: () => 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 { t } = useTranslation();
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
const [isDrawing, setIsDrawing] = useState(false);
|
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')}
|
onChange={(value) => onParameterChange('signatureType', value as 'image' | 'text' | 'draw' | 'canvas')}
|
||||||
>
|
>
|
||||||
<Tabs.List grow>
|
<Tabs.List grow>
|
||||||
<Tabs.Tab value="draw" style={{ fontSize: '0.8rem' }}>
|
|
||||||
{t('sign.type.draw', 'Draw')}
|
|
||||||
</Tabs.Tab>
|
|
||||||
<Tabs.Tab value="canvas" style={{ fontSize: '0.8rem' }}>
|
<Tabs.Tab value="canvas" style={{ fontSize: '0.8rem' }}>
|
||||||
{t('sign.type.canvas', 'Canvas')}
|
{t('sign.type.canvas', 'Canvas')}
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
@ -451,6 +450,9 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
<Tabs.Tab value="text" style={{ fontSize: '0.8rem' }}>
|
<Tabs.Tab value="text" style={{ fontSize: '0.8rem' }}>
|
||||||
{t('sign.type.text', 'Text')}
|
{t('sign.type.text', 'Text')}
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
|
<Tabs.Tab value="draw" style={{ fontSize: '0.8rem' }}>
|
||||||
|
{t('sign.type.draw', 'Draw')}
|
||||||
|
</Tabs.Tab>
|
||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
@ -472,6 +474,7 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
|
|
||||||
{/* Signature Creation based on type */}
|
{/* Signature Creation based on type */}
|
||||||
{parameters.signatureType === 'canvas' && (
|
{parameters.signatureType === 'canvas' && (
|
||||||
<Paper withBorder p="md">
|
<Paper withBorder p="md">
|
||||||
@ -958,6 +961,21 @@ const SignSettings = ({ parameters, onParameterChange, disabled = false, onActiv
|
|||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* Save Button */}
|
||||||
|
{onSave && (
|
||||||
|
<Button
|
||||||
|
onClick={onSave}
|
||||||
|
color="green"
|
||||||
|
variant="filled"
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
{t('save', 'Save')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Suggested Tools Section */}
|
||||||
|
<SuggestedToolsSection />
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -20,7 +20,7 @@ export interface SignParameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_PARAMETERS: SignParameters = {
|
export const DEFAULT_PARAMETERS: SignParameters = {
|
||||||
signatureType: 'draw',
|
signatureType: 'canvas',
|
||||||
reason: 'Document signing',
|
reason: 'Document signing',
|
||||||
location: 'Digital',
|
location: 'Digital',
|
||||||
signerName: '',
|
signerName: '',
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useCallback } from "react";
|
import { useEffect, useCallback, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
import { createToolFlow } from "../components/tools/shared/createToolFlow";
|
||||||
import { useSignParameters } from "../hooks/tools/sign/useSignParameters";
|
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 SignSettings from "../components/tools/sign/SignSettings";
|
||||||
import { useNavigation } from "../contexts/NavigationContext";
|
import { useNavigation } from "../contexts/NavigationContext";
|
||||||
import { useSignature } from "../contexts/SignatureContext";
|
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 Sign = (props: BaseToolProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setWorkbench } = useNavigation();
|
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
|
// Manual sync function
|
||||||
const syncSignatureConfig = () => {
|
const syncSignatureConfig = () => {
|
||||||
@ -47,30 +58,125 @@ const Sign = (props: BaseToolProps) => {
|
|||||||
setSignatureConfig(base.params.parameters);
|
setSignatureConfig(base.params.parameters);
|
||||||
}, [base.params.parameters, setSignatureConfig]);
|
}, [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 getSteps = () => {
|
||||||
const steps = [];
|
const steps = [];
|
||||||
|
|
||||||
// Step 1: Signature Configuration
|
// Step 1: Signature Configuration - Always visible
|
||||||
if (base.selectedFiles.length > 0 || base.operation.files.length > 0) {
|
steps.push({
|
||||||
steps.push({
|
title: t('sign.steps.configure', 'Configure Signature'),
|
||||||
title: t('sign.steps.configure', 'Configure Signature'),
|
isCollapsed: false,
|
||||||
isCollapsed: base.operation.files.length > 0,
|
onCollapsedClick: undefined,
|
||||||
onCollapsedClick: base.operation.files.length > 0 ? base.handleSettingsReset : undefined,
|
content: (
|
||||||
content: (
|
<SignSettings
|
||||||
<SignSettings
|
parameters={base.params.parameters}
|
||||||
parameters={base.params.parameters}
|
onParameterChange={base.params.updateParameter}
|
||||||
onParameterChange={base.params.updateParameter}
|
disabled={base.endpointLoading}
|
||||||
disabled={base.endpointLoading}
|
onActivateDrawMode={() => {
|
||||||
onActivateDrawMode={() => activateDrawMode()}
|
activeModeRef.current = 'draw';
|
||||||
onActivateSignaturePlacement={handleSignaturePlacement}
|
activateDrawMode();
|
||||||
onDeactivateSignature={deactivateDrawMode}
|
}}
|
||||||
onUpdateDrawSettings={updateDrawSettings}
|
onActivateSignaturePlacement={() => {
|
||||||
onUndo={undo}
|
activeModeRef.current = 'placement';
|
||||||
onRedo={redo}
|
handleSignaturePlacement();
|
||||||
/>
|
}}
|
||||||
),
|
onDeactivateSignature={deactivateDrawMode}
|
||||||
});
|
onUpdateDrawSettings={updateDrawSettings}
|
||||||
}
|
onUndo={undo}
|
||||||
|
onRedo={redo}
|
||||||
|
onSave={handleSaveToSystem}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
return steps;
|
return steps;
|
||||||
};
|
};
|
||||||
@ -81,19 +187,12 @@ const Sign = (props: BaseToolProps) => {
|
|||||||
isCollapsed: base.operation.files.length > 0,
|
isCollapsed: base.operation.files.length > 0,
|
||||||
},
|
},
|
||||||
steps: getSteps(),
|
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: {
|
review: {
|
||||||
isVisible: base.operation.files.length > 0,
|
isVisible: false, // Hide review section - save moved to configure section
|
||||||
operation: base.operation,
|
operation: base.operation,
|
||||||
title: t('sign.results.title', 'Signature Results'),
|
title: t('sign.results.title', 'Signature Results'),
|
||||||
onFileClick: base.handleThumbnailClick,
|
onFileClick: base.handleThumbnailClick,
|
||||||
onUndo: base.handleUndo,
|
onUndo: () => {},
|
||||||
},
|
},
|
||||||
forceStepNumbers: true,
|
forceStepNumbers: true,
|
||||||
});
|
});
|
||||||
@ -102,7 +201,7 @@ const Sign = (props: BaseToolProps) => {
|
|||||||
// Add the required static methods for automation
|
// Add the required static methods for automation
|
||||||
Sign.tool = () => useSignOperation;
|
Sign.tool = () => useSignOperation;
|
||||||
Sign.getDefaultParameters = () => ({
|
Sign.getDefaultParameters = () => ({
|
||||||
signatureType: 'draw',
|
signatureType: 'canvas',
|
||||||
reason: 'Document signing',
|
reason: 'Document signing',
|
||||||
location: 'Digital',
|
location: 'Digital',
|
||||||
signerName: '',
|
signerName: '',
|
||||||
|
Loading…
Reference in New Issue
Block a user