This commit is contained in:
Anthony Stirling 2025-11-01 12:37:09 +00:00
parent efaec14e08
commit d79cd76b56
3 changed files with 145 additions and 55 deletions

View File

@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -2002,17 +2003,27 @@ public class PdfJsonConversionService {
if (stream == null) { if (stream == null) {
return null; return null;
} }
return serializeStream(stream.getCOSObject()); return serializeStream(
stream.getCOSObject(), Collections.newSetFromMap(new IdentityHashMap<>()));
} }
private PdfJsonStream serializeStream(COSStream cosStream) throws IOException { private PdfJsonStream serializeStream(COSStream cosStream) throws IOException {
if (cosStream == null) { if (cosStream == null) {
return null; return null;
} }
return serializeStream(
cosStream, Collections.newSetFromMap(new IdentityHashMap<>()));
}
private PdfJsonStream serializeStream(COSStream cosStream, Set<COSBase> visited)
throws IOException {
if (cosStream == null) {
return null;
}
Map<String, PdfJsonCosValue> dictionary = new LinkedHashMap<>(); Map<String, PdfJsonCosValue> dictionary = new LinkedHashMap<>();
for (COSName key : cosStream.keySet()) { for (COSName key : cosStream.keySet()) {
COSBase value = cosStream.getDictionaryObject(key); COSBase value = cosStream.getDictionaryObject(key);
PdfJsonCosValue serialized = serializeCosValue(value); PdfJsonCosValue serialized = serializeCosValue(value, visited);
if (serialized != null) { if (serialized != null) {
dictionary.put(key.getName(), serialized); dictionary.put(key.getName(), serialized);
} }
@ -2032,6 +2043,11 @@ public class PdfJsonConversionService {
} }
private PdfJsonCosValue serializeCosValue(COSBase base) throws IOException { private PdfJsonCosValue serializeCosValue(COSBase base) throws IOException {
return serializeCosValue(
base, Collections.newSetFromMap(new IdentityHashMap<>()));
}
private PdfJsonCosValue serializeCosValue(COSBase base, Set<COSBase> visited) throws IOException {
if (base == null) { if (base == null) {
return null; return null;
} }
@ -2041,55 +2057,76 @@ public class PdfJsonConversionService {
return null; return null;
} }
} }
PdfJsonCosValue.PdfJsonCosValueBuilder builder = PdfJsonCosValue.builder();
if (base instanceof COSNull) { boolean complex =
builder.type(PdfJsonCosValue.Type.NULL); base instanceof COSDictionary
return builder.build(); || base instanceof COSArray
} || base instanceof COSStream;
if (base instanceof COSBoolean booleanValue) { if (complex) {
builder.type(PdfJsonCosValue.Type.BOOLEAN).value(booleanValue.getValue()); if (!visited.add(base)) {
return builder.build(); return PdfJsonCosValue.builder()
} .type(PdfJsonCosValue.Type.NAME)
if (base instanceof COSInteger integer) { .value("__circular__")
builder.type(PdfJsonCosValue.Type.INTEGER).value(integer.longValue()); .build();
return builder.build();
}
if (base instanceof COSFloat floatValue) {
builder.type(PdfJsonCosValue.Type.FLOAT).value(floatValue.floatValue());
return builder.build();
}
if (base instanceof COSName name) {
builder.type(PdfJsonCosValue.Type.NAME).value(name.getName());
return builder.build();
}
if (base instanceof COSString cosString) {
builder.type(PdfJsonCosValue.Type.STRING)
.value(Base64.getEncoder().encodeToString(cosString.getBytes()));
return builder.build();
}
if (base instanceof COSArray array) {
List<PdfJsonCosValue> items = new ArrayList<>(array.size());
for (COSBase item : array) {
PdfJsonCosValue serialized = serializeCosValue(item);
items.add(serialized);
} }
builder.type(PdfJsonCosValue.Type.ARRAY).items(items);
return builder.build();
} }
if (base instanceof COSStream stream) {
builder.type(PdfJsonCosValue.Type.STREAM).stream(serializeStream(stream)); try {
return builder.build(); PdfJsonCosValue.PdfJsonCosValueBuilder builder = PdfJsonCosValue.builder();
} if (base instanceof COSNull) {
if (base instanceof COSDictionary dictionary) { builder.type(PdfJsonCosValue.Type.NULL);
Map<String, PdfJsonCosValue> entries = new LinkedHashMap<>(); return builder.build();
for (COSName key : dictionary.keySet()) { }
PdfJsonCosValue serialized = serializeCosValue(dictionary.getDictionaryObject(key)); if (base instanceof COSBoolean booleanValue) {
entries.put(key.getName(), serialized); builder.type(PdfJsonCosValue.Type.BOOLEAN).value(booleanValue.getValue());
return builder.build();
}
if (base instanceof COSInteger integer) {
builder.type(PdfJsonCosValue.Type.INTEGER).value(integer.longValue());
return builder.build();
}
if (base instanceof COSFloat floatValue) {
builder.type(PdfJsonCosValue.Type.FLOAT).value(floatValue.floatValue());
return builder.build();
}
if (base instanceof COSName name) {
builder.type(PdfJsonCosValue.Type.NAME).value(name.getName());
return builder.build();
}
if (base instanceof COSString cosString) {
builder.type(PdfJsonCosValue.Type.STRING)
.value(Base64.getEncoder().encodeToString(cosString.getBytes()));
return builder.build();
}
if (base instanceof COSArray array) {
List<PdfJsonCosValue> items = new ArrayList<>(array.size());
for (COSBase item : array) {
PdfJsonCosValue serialized = serializeCosValue(item, visited);
items.add(serialized);
}
builder.type(PdfJsonCosValue.Type.ARRAY).items(items);
return builder.build();
}
if (base instanceof COSStream stream) {
builder.type(PdfJsonCosValue.Type.STREAM).stream(serializeStream(stream, visited));
return builder.build();
}
if (base instanceof COSDictionary dictionary) {
Map<String, PdfJsonCosValue> entries = new LinkedHashMap<>();
for (COSName key : dictionary.keySet()) {
PdfJsonCosValue serialized =
serializeCosValue(dictionary.getDictionaryObject(key), visited);
entries.put(key.getName(), serialized);
}
builder.type(PdfJsonCosValue.Type.DICTIONARY).entries(entries);
return builder.build();
}
return null;
} finally {
if (complex) {
visited.remove(base);
} }
builder.type(PdfJsonCosValue.Type.DICTIONARY).entries(entries);
return builder.build();
} }
return null;
} }
private COSBase deserializeCosValue(PdfJsonCosValue value, PDDocument document) private COSBase deserializeCosValue(PdfJsonCosValue value, PDDocument document)

View File

@ -4628,6 +4628,5 @@
"passwordMustBeDifferent": "New password must be different from current password", "passwordMustBeDifferent": "New password must be different from current password",
"passwordChangedSuccess": "Password changed successfully! Please log in again.", "passwordChangedSuccess": "Password changed successfully! Please log in again.",
"passwordChangeFailed": "Failed to change password. Please check your current password." "passwordChangeFailed": "Failed to change password. Please check your current password."
>>>>>>> refs/remotes/origin/V2
} }
} }

View File

@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
import DescriptionIcon from '@mui/icons-material/DescriptionOutlined'; import DescriptionIcon from '@mui/icons-material/DescriptionOutlined';
import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext'; import { useToolWorkflow } from '@app/contexts/ToolWorkflowContext';
import { useFileSelection } from '@app/contexts/FileContext';
import { useNavigationActions, useNavigationState } from '@app/contexts/NavigationContext'; import { useNavigationActions, useNavigationState } from '@app/contexts/NavigationContext';
import { BaseToolProps, ToolComponent } from '@app/types/tool'; import { BaseToolProps, ToolComponent } from '@app/types/tool';
import { CONVERSION_ENDPOINTS } from '@app/constants/convertConstants'; import { CONVERSION_ENDPOINTS } from '@app/constants/convertConstants';
@ -36,6 +37,17 @@ const sanitizeBaseName = (name?: string | null): string => {
return name.replace(/\.[^.]+$/u, ''); return name.replace(/\.[^.]+$/u, '');
}; };
const getAutoLoadKey = (file: File): string => {
const withId = file as File & { fileId?: string; quickKey?: string };
if (withId.fileId && typeof withId.fileId === 'string') {
return withId.fileId;
}
if (withId.quickKey && typeof withId.quickKey === 'string') {
return withId.quickKey;
}
return `${file.name}|${file.size}|${file.lastModified}`;
};
const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => { const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
@ -58,6 +70,9 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
const [isConverting, setIsConverting] = useState(false); const [isConverting, setIsConverting] = useState(false);
const originalImagesRef = useRef<PdfJsonImageElement[][]>([]); const originalImagesRef = useRef<PdfJsonImageElement[][]>([]);
const autoLoadKeyRef = useRef<string | null>(null);
const loadRequestIdRef = useRef(0);
const latestPdfRequestIdRef = useRef<number | null>(null);
const dirtyPages = useMemo( const dirtyPages = useMemo(
() => getDirtyPages(groupsByPage, imagesByPage, originalImagesRef.current), () => getDirtyPages(groupsByPage, imagesByPage, originalImagesRef.current),
@ -66,6 +81,7 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
const hasChanges = useMemo(() => dirtyPages.some(Boolean), [dirtyPages]); const hasChanges = useMemo(() => dirtyPages.some(Boolean), [dirtyPages]);
const hasDocument = loadedDocument !== null; const hasDocument = loadedDocument !== null;
const viewLabel = useMemo(() => t('pdfJsonEditor.viewLabel', 'PDF Editor'), [t]); const viewLabel = useMemo(() => t('pdfJsonEditor.viewLabel', 'PDF Editor'), [t]);
const { selectedFiles } = useFileSelection();
const resetToDocument = useCallback((document: PdfJsonDocument | null) => { const resetToDocument = useCallback((document: PdfJsonDocument | null) => {
if (!document) { if (!document) {
@ -90,15 +106,20 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
return; return;
} }
const requestId = loadRequestIdRef.current + 1;
loadRequestIdRef.current = requestId;
const fileKey = getAutoLoadKey(file);
const isPdf = file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf'); const isPdf = file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf');
try { try {
let parsed: PdfJsonDocument; let parsed: PdfJsonDocument;
setErrorMessage(null);
if (isPdf) { if (isPdf) {
// Convert PDF to JSON first latestPdfRequestIdRef.current = requestId;
setIsConverting(true); setIsConverting(true);
setErrorMessage(null);
const formData = new FormData(); const formData = new FormData();
formData.append('fileInput', file); formData.append('fileInput', file);
@ -110,21 +131,28 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
const jsonText = await response.data.text(); const jsonText = await response.data.text();
parsed = JSON.parse(jsonText) as PdfJsonDocument; parsed = JSON.parse(jsonText) as PdfJsonDocument;
} else { } else {
// Load JSON directly
const content = await file.text(); const content = await file.text();
parsed = JSON.parse(content) as PdfJsonDocument; parsed = JSON.parse(content) as PdfJsonDocument;
} }
if (loadRequestIdRef.current !== requestId) {
return;
}
setLoadedDocument(parsed); setLoadedDocument(parsed);
resetToDocument(parsed); resetToDocument(parsed);
setFileName(file.name); setFileName(file.name);
setErrorMessage(null); setErrorMessage(null);
autoLoadKeyRef.current = fileKey;
} catch (error) { } catch (error) {
console.error('Failed to load file', error); console.error('Failed to load file', error);
if (loadRequestIdRef.current !== requestId) {
return;
}
setLoadedDocument(null); setLoadedDocument(null);
setGroupsByPage([]); resetToDocument(null);
setImagesByPage([]);
originalImagesRef.current = [];
if (isPdf) { if (isPdf) {
setErrorMessage( setErrorMessage(
@ -139,7 +167,9 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
); );
} }
} finally { } finally {
setIsConverting(false); if (isPdf && latestPdfRequestIdRef.current === requestId) {
setIsConverting(false);
}
} }
}, },
[resetToDocument, t] [resetToDocument, t]
@ -367,6 +397,30 @@ const PdfJsonEditor = ({ onComplete, onError }: BaseToolProps) => {
const latestViewDataRef = useRef<PdfJsonEditorViewData>(viewData); const latestViewDataRef = useRef<PdfJsonEditorViewData>(viewData);
latestViewDataRef.current = viewData; latestViewDataRef.current = viewData;
useEffect(() => {
if (selectedFiles.length === 0) {
autoLoadKeyRef.current = null;
return;
}
if (navigationState.selectedTool !== 'pdfJsonEditor') {
return;
}
const file = selectedFiles[0];
if (!file) {
return;
}
const fileKey = getAutoLoadKey(file);
if (autoLoadKeyRef.current === fileKey) {
return;
}
autoLoadKeyRef.current = fileKey;
void handleLoadFile(file);
}, [selectedFiles, navigationState.selectedTool, handleLoadFile]);
useEffect(() => { useEffect(() => {
registerCustomWorkbenchView({ registerCustomWorkbenchView({
id: VIEW_ID, id: VIEW_ID,