mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 11:23:10 +02:00
Fix export (#5782)
This commit is contained in:
@@ -4,6 +4,7 @@ import '@app/components/shared/rightRail/RightRail.css';
|
|||||||
import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext';
|
import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext';
|
||||||
import { useRightRail } from '@app/contexts/RightRailContext';
|
import { useRightRail } from '@app/contexts/RightRailContext';
|
||||||
import { useFileState, useFileSelection, useFileActions } from '@app/contexts/FileContext';
|
import { useFileState, useFileSelection, useFileActions } from '@app/contexts/FileContext';
|
||||||
|
import { isStirlingFile } from '@app/types/fileContext';
|
||||||
import { useNavigationState } from '@app/contexts/NavigationContext';
|
import { useNavigationState } from '@app/contexts/NavigationContext';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
|
import { useFileActionTerminology } from '@app/hooks/useFileActionTerminology';
|
||||||
@@ -13,7 +14,6 @@ import LanguageSelector from '@app/components/shared/LanguageSelector';
|
|||||||
import { useRainbowThemeContext } from '@app/components/shared/RainbowThemeProvider';
|
import { useRainbowThemeContext } from '@app/components/shared/RainbowThemeProvider';
|
||||||
import { Tooltip } from '@app/components/shared/Tooltip';
|
import { Tooltip } from '@app/components/shared/Tooltip';
|
||||||
import { ViewerContext } from '@app/contexts/ViewerContext';
|
import { ViewerContext } from '@app/contexts/ViewerContext';
|
||||||
import { useSignature } from '@app/contexts/SignatureContext';
|
|
||||||
import LocalIcon from '@app/components/shared/LocalIcon';
|
import LocalIcon from '@app/components/shared/LocalIcon';
|
||||||
import { RightRailFooterExtensions } from '@app/components/rightRail/RightRailFooterExtensions';
|
import { RightRailFooterExtensions } from '@app/components/rightRail/RightRailFooterExtensions';
|
||||||
import DarkModeIcon from '@mui/icons-material/DarkMode';
|
import DarkModeIcon from '@mui/icons-material/DarkMode';
|
||||||
@@ -61,12 +61,9 @@ export default function RightRail() {
|
|||||||
const { selectors } = useFileState();
|
const { selectors } = useFileState();
|
||||||
const { selectedFiles, selectedFileIds } = useFileSelection();
|
const { selectedFiles, selectedFileIds } = useFileSelection();
|
||||||
const { actions: fileActions } = useFileActions();
|
const { actions: fileActions } = useFileActions();
|
||||||
const { signaturesApplied } = useSignature();
|
|
||||||
|
|
||||||
const activeFiles = selectors.getFiles();
|
const activeFiles = selectors.getFiles();
|
||||||
const pageEditorTotalPages = pageEditorFunctions?.totalPages ?? 0;
|
const pageEditorTotalPages = pageEditorFunctions?.totalPages ?? 0;
|
||||||
const pageEditorSelectedCount = pageEditorFunctions?.selectedPageIds?.length ?? 0;
|
const pageEditorSelectedCount = pageEditorFunctions?.selectedPageIds?.length ?? 0;
|
||||||
const exportState = viewerContext?.getExportState?.();
|
|
||||||
|
|
||||||
const totalItems = useMemo(() => {
|
const totalItems = useMemo(() => {
|
||||||
if (currentView === 'pageEditor') return pageEditorTotalPages;
|
if (currentView === 'pageEditor') return pageEditorTotalPages;
|
||||||
@@ -139,11 +136,26 @@ export default function RightRail() {
|
|||||||
|
|
||||||
const handleExportAll = useCallback(async () => {
|
const handleExportAll = useCallback(async () => {
|
||||||
if (currentView === 'viewer') {
|
if (currentView === 'viewer') {
|
||||||
if (!signaturesApplied) {
|
const buffer = await viewerContext?.exportActions?.saveAsCopy?.();
|
||||||
alert('You have unapplied signatures. Please use "Apply Signatures" first before exporting.');
|
if (!buffer) return;
|
||||||
return;
|
const fileToExport = selectedFiles.length > 0 ? selectedFiles[0] : activeFiles[0];
|
||||||
|
if (!fileToExport) return;
|
||||||
|
const stub = isStirlingFile(fileToExport) ? selectors.getStirlingFileStub(fileToExport.fileId) : undefined;
|
||||||
|
try {
|
||||||
|
const result = await downloadFile({
|
||||||
|
data: new Blob([buffer], { type: 'application/pdf' }),
|
||||||
|
filename: fileToExport.name,
|
||||||
|
localPath: stub?.localFilePath,
|
||||||
|
});
|
||||||
|
if (!result.cancelled && stub && result.savedPath) {
|
||||||
|
fileActions.updateStirlingFileStub(stub.id, {
|
||||||
|
localFilePath: stub.localFilePath ?? result.savedPath,
|
||||||
|
isDirty: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[RightRail] Failed to export viewer file:', error);
|
||||||
}
|
}
|
||||||
viewerContext?.exportActions?.download?.();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,30 +165,25 @@ export default function RightRail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filesToExport = selectedFiles.length > 0 ? selectedFiles : activeFiles;
|
const filesToExport = selectedFiles.length > 0 ? selectedFiles : activeFiles;
|
||||||
const stubsToExport = selectedFiles.length > 0
|
|
||||||
? selectors.getSelectedStirlingFileStubs()
|
|
||||||
: selectors.getStirlingFileStubs();
|
|
||||||
|
|
||||||
if (filesToExport.length > 0) {
|
if (filesToExport.length > 0) {
|
||||||
for (let i = 0; i < filesToExport.length; i++) {
|
for (const file of filesToExport) {
|
||||||
const file = filesToExport[i];
|
const stub = isStirlingFile(file) ? selectors.getStirlingFileStub(file.fileId) : undefined;
|
||||||
const stub = stubsToExport[i];
|
try {
|
||||||
console.log('[RightRail] Exporting file:', { fileName: file.name, stubId: stub?.id, localFilePath: stub?.localFilePath, isDirty: stub?.isDirty });
|
|
||||||
const result = await downloadFile({
|
const result = await downloadFile({
|
||||||
data: file,
|
data: file,
|
||||||
filename: file.name,
|
filename: file.name,
|
||||||
localPath: stub?.localFilePath
|
localPath: stub?.localFilePath
|
||||||
});
|
});
|
||||||
console.log('[RightRail] Export complete, checking dirty state:', { localFilePath: stub?.localFilePath, isDirty: stub?.isDirty, savedPath: result.savedPath });
|
if (result.cancelled) continue;
|
||||||
// Mark file as clean after successful save to disk
|
|
||||||
if (stub && result.savedPath) {
|
if (stub && result.savedPath) {
|
||||||
console.log('[RightRail] Marking file as clean:', stub.id);
|
|
||||||
fileActions.updateStirlingFileStub(stub.id, {
|
fileActions.updateStirlingFileStub(stub.id, {
|
||||||
localFilePath: stub.localFilePath ?? result.savedPath,
|
localFilePath: stub.localFilePath ?? result.savedPath,
|
||||||
isDirty: false
|
isDirty: false
|
||||||
});
|
});
|
||||||
} else {
|
}
|
||||||
console.log('[RightRail] Skipping clean mark:', { savedPath: result.savedPath, isDirty: stub?.isDirty });
|
} catch (error) {
|
||||||
|
console.error('[RightRail] Failed to export file:', file.name, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -186,7 +193,6 @@ export default function RightRail() {
|
|||||||
activeFiles,
|
activeFiles,
|
||||||
pageEditorFunctions,
|
pageEditorFunctions,
|
||||||
viewerContext,
|
viewerContext,
|
||||||
signaturesApplied,
|
|
||||||
selectors,
|
selectors,
|
||||||
fileActions,
|
fileActions,
|
||||||
]);
|
]);
|
||||||
@@ -261,7 +267,7 @@ export default function RightRail() {
|
|||||||
onClick={handleExportAll}
|
onClick={handleExportAll}
|
||||||
disabled={
|
disabled={
|
||||||
disableForFullscreen ||
|
disableForFullscreen ||
|
||||||
(currentView === 'viewer' ? !exportState?.canExport : totalItems === 0 || allButtonsDisabled)
|
(currentView !== 'viewer' && (totalItems === 0 || allButtonsDisabled))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<LocalIcon icon={icons.downloadIconName} width="1.5rem" height="1.5rem" />
|
<LocalIcon icon={icons.downloadIconName} width="1.5rem" height="1.5rem" />
|
||||||
|
|||||||
@@ -59,8 +59,6 @@ import { ActiveDocumentProvider } from '@app/components/viewer/ActiveDocumentCon
|
|||||||
import { absoluteWithBasePath } from '@app/constants/app';
|
import { absoluteWithBasePath } from '@app/constants/app';
|
||||||
import { FormFieldOverlay } from '@app/tools/formFill/FormFieldOverlay';
|
import { FormFieldOverlay } from '@app/tools/formFill/FormFieldOverlay';
|
||||||
|
|
||||||
const DOCUMENT_NAME = 'stirling-pdf-viewer';
|
|
||||||
|
|
||||||
interface LocalEmbedPDFProps {
|
interface LocalEmbedPDFProps {
|
||||||
file?: File | Blob;
|
file?: File | Blob;
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
@@ -118,7 +116,7 @@ export function LocalEmbedPDF({ file, url, fileName, enableAnnotations = false,
|
|||||||
createPluginRegistration(DocumentManagerPluginPackage, {
|
createPluginRegistration(DocumentManagerPluginPackage, {
|
||||||
initialDocuments: [{
|
initialDocuments: [{
|
||||||
url: pdfUrl,
|
url: pdfUrl,
|
||||||
name: DOCUMENT_NAME,
|
name: exportFileName,
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
createPluginRegistration(ViewportPluginPackage, {
|
createPluginRegistration(ViewportPluginPackage, {
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ export interface SearchActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ExportActions {
|
export interface ExportActions {
|
||||||
download: () => void;
|
|
||||||
saveAsCopy: () => Promise<ArrayBuffer | null>;
|
saveAsCopy: () => Promise<ArrayBuffer | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,12 +335,6 @@ export function createViewerActions({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const exportActions: ExportActions = {
|
const exportActions: ExportActions = {
|
||||||
download: () => {
|
|
||||||
const api = registry.current.export?.api;
|
|
||||||
if (api?.download) {
|
|
||||||
api.download();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
saveAsCopy: async () => {
|
saveAsCopy: async () => {
|
||||||
const api = registry.current.export?.api;
|
const api = registry.current.export?.api;
|
||||||
if (api?.saveAsCopy) {
|
if (api?.saveAsCopy) {
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ export interface ThumbnailAPIWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ExportAPIWrapper {
|
export interface ExportAPIWrapper {
|
||||||
download: () => void;
|
|
||||||
saveAsCopy: () => { toPromise: () => Promise<ArrayBuffer> };
|
saveAsCopy: () => { toPromise: () => Promise<ArrayBuffer> };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo, useEffect } from 'react';
|
||||||
import { isFileObject } from '@app/types/fileContext';
|
import { isFileObject } from '@app/types/fileContext';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to convert a File object to { file: File; url: string } format
|
* Hook to convert a File object to { file: File; url: string } format
|
||||||
* Creates blob URL on-demand and handles cleanup
|
* Creates blob URL on-demand and revokes it when file changes or component unmounts.
|
||||||
*/
|
*/
|
||||||
export function useFileWithUrl(file: File | Blob | null): { file: File | Blob; url: string } | null {
|
export function useFileWithUrl(file: File | Blob | null): { file: File | Blob; url: string } | null {
|
||||||
return useMemo(() => {
|
const result = useMemo(() => {
|
||||||
if (!file) return null;
|
if (!file) return null;
|
||||||
|
|
||||||
// Validate that file is a proper File, StirlingFile, or Blob object
|
// Validate that file is a proper File, StirlingFile, or Blob object
|
||||||
@@ -17,19 +17,21 @@ export function useFileWithUrl(file: File | Blob | null): { file: File | Blob; u
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const url = URL.createObjectURL(file);
|
const url = URL.createObjectURL(file);
|
||||||
|
return { file, url };
|
||||||
// Return object with cleanup function
|
|
||||||
const result = { file, url };
|
|
||||||
|
|
||||||
// Store cleanup function for later use
|
|
||||||
(result as any)._cleanup = () => URL.revokeObjectURL(url);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('useFileWithUrl: Failed to create object URL:', error, file);
|
console.error('useFileWithUrl: Failed to create object URL:', error, file);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}, [file]);
|
}, [file]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const url = result?.url;
|
||||||
|
return () => {
|
||||||
|
if (url) URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
}, [result]);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user