From fb35777c0e0796fff8336edd16975e7d76a0cd82 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Wed, 17 Dec 2025 11:14:28 +0100 Subject: [PATCH 1/3] refactor: adapt to new serializeNmriumArchive api --- src/component/hooks/useExport.tsx | 36 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/component/hooks/useExport.tsx b/src/component/hooks/useExport.tsx index ba592b397..ee739e78e 100644 --- a/src/component/hooks/useExport.tsx +++ b/src/component/hooks/useExport.tsx @@ -1,3 +1,4 @@ +import type { NmriumState } from '@zakodium/nmrium-core'; import { useCallback } from 'react'; import type { ExportOptions } from '../../data/SpectraManager.js'; @@ -75,28 +76,33 @@ export function useExport() { }); setTimeout(async () => { try { - const exportedData = toJSON(core, state, preferencesState, { - ...include, - exportTarget: 'nmrium', - }); - const spaceIndent = pretty || exportArchive ? 2 : 0; - const blob = await exportAsJsonBlob( - exportedData, - name, - spaceIndent, - compressed && !exportArchive, - ); - if (!exportArchive) { + const exportedData = toJSON(core, state, preferencesState, { + ...include, + serialize: true, + exportTarget: 'nmrium', + }); + const spaceIndent = pretty ? 2 : 0; + const blob = await exportAsJsonBlob( + exportedData, + name, + spaceIndent, + compressed, + ); + return saveAs({ blob, name, extension: '.nmrium' }); } + const nmriumState = toJSON(core, state, preferencesState, { + serialize: false, + exportTarget: 'nmrium', + }) as NmriumState; const archive = await core.serializeNmriumArchive({ - molecules: state.molecules, - spectra: state.data, + state: nmriumState, aggregator: state.aggregator, includeData: options.include.dataType === 'SELF_CONTAINED', - serializedState: blob, + includeSettings: options.include.settings, + includeView: options.include.view, }); const zipBlob = new Blob([archive], { type: 'chemical/x-nmrium+zip', From 6934ce712eafec6e1794cc602289f81587007d76 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Wed, 17 Dec 2025 12:49:10 +0100 Subject: [PATCH 2/3] feat: check nmrium archive by file header mimetype refactor: `useLoadFiles` to iterative instead recursive * split `loadFiles` into smaller functions * fix unmemoized `loadFiles` --- src/component/loader/useLoadFiles.ts | 243 +++++++++++++++------------ 1 file changed, 132 insertions(+), 111 deletions(-) diff --git a/src/component/loader/useLoadFiles.ts b/src/component/loader/useLoadFiles.ts index 2d7e4482e..7affbb9f9 100644 --- a/src/component/loader/useLoadFiles.ts +++ b/src/component/loader/useLoadFiles.ts @@ -1,16 +1,19 @@ -import type { NmriumState, ParsingOptions } from '@zakodium/nmrium-core'; +import type { ParsingOptions } from '@zakodium/nmrium-core'; +import type { FileItem } from 'file-collection'; import { FileCollection } from 'file-collection'; -import type { ParseResult } from 'papaparse'; import { useCallback } from 'react'; import { isMetaFile, parseMetaFile } from '../../data/parseMeta/index.js'; import { useCore } from '../context/CoreContext.js'; +import type { Action } from '../context/DispatchContext.js'; import { useDispatch } from '../context/DispatchContext.js'; import { useLogger } from '../context/LoggerContext.js'; import { usePreferences } from '../context/PreferencesContext.js'; import { useToaster } from '../context/ToasterContext.js'; import useCheckExperimentalFeature from '../hooks/useCheckExperimentalFeature.js'; +type Payload = Extract['payload']; + export function useLoadFiles(onOpenMetaInformation?: (file: File) => void) { const dispatch = useDispatch(); const preferences = usePreferences(); @@ -21,110 +24,114 @@ export function useLoadFiles(onOpenMetaInformation?: (file: File) => void) { const experimentalFeatures = useCheckExperimentalFeature(); const core = useCore(); - return useCallback( - (files: File[]) => { - dispatch({ type: 'SET_LOADING_FLAG', payload: { isLoading: true } }); + const dispatchPayload = useCallback( + (payload: Omit) => { + const { nmriumState } = payload; + const { spectraColors, defaultMoleculeSettings } = workspacePreferences; - async function loadFiles(files: File[]) { - if ( - onOpenMetaInformation && - files.length === 1 && - isMetaFile(files[0]) - ) { - onOpenMetaInformation(files[0]); - return; - } - - const nmriumArchiveFiles: File[] = []; - const otherFiles: File[] = []; - let nmriumState: Partial; - let containsNmrium: boolean; - let parseMetaFileResult: ParseResult | null = null; - const { onLoadProcessing, spectraColors, defaultMoleculeSettings } = - workspacePreferences; - const { nmrLoaders: selector } = preferences.current; - - const parsingOptions: Partial = { - selector, - logger: logger.child({ context: 'nmr-processing' }), - onLoadProcessing, - experimentalFeatures, - }; - - let aggregator: FileCollection | undefined; - let fileCollection: FileCollection | undefined; - let selectorRoot: string | undefined; - let resetSourceObject = false; - - if (files.length === 1 && files[0].name.endsWith('.nmrium.zip')) { - const [state, ium] = await core.readNMRiumArchive( - files[0].stream(), - parsingOptions, - ); - nmriumState = state; - containsNmrium = true; - aggregator = ium; - } else { - for (const file of files) { - if (file.name.endsWith('.nmrium.zip')) { - nmriumArchiveFiles.push(file); - } else { - otherFiles.push(file); - } - } - - if (nmriumArchiveFiles.length > 0) { - await Promise.all( - nmriumArchiveFiles.map((file) => loadFiles([file])), - ); - } - - fileCollection = await new FileCollection().appendFileList( - otherFiles, - ); - - const metaFile = Object.values(fileCollection.files).find((file) => - isMetaFile(file), - ); - - if (metaFile) { - parseMetaFileResult = await parseMetaFile(metaFile); - } - ({ nmriumState, containsNmrium, selectorRoot } = await core.read( - fileCollection, - parsingOptions, - )); - if (containsNmrium) { - resetSourceObject = true; - } - } - - if (nmriumState.settings) { - dispatchPreferences({ - type: 'SET_WORKSPACE', - payload: { - data: nmriumState.settings, - workspaceSource: 'nmriumFile', - }, - }); - } - dispatch({ - type: 'LOAD_DROP_FILES', + if (nmriumState.settings) { + dispatchPreferences({ + type: 'SET_WORKSPACE', payload: { - nmriumState, - containsNmrium, - parseMetaFileResult, - spectraColors, - aggregator, - fileCollection, - selectorRoot, - resetSourceObject, - defaultMoleculeSettings, + data: nmriumState.settings, + workspaceSource: 'nmriumFile', }, }); } + dispatch({ + type: 'LOAD_DROP_FILES', + payload: { ...payload, spectraColors, defaultMoleculeSettings }, + }); + }, + [dispatch, dispatchPreferences, workspacePreferences], + ); + + const loadNmriumArchives = useCallback( + async (file: File, parsingOptions: Partial) => { + const [nmriumState, aggregator] = await core.readNMRiumArchive( + file.stream(), + parsingOptions, + ); + + dispatchPayload({ nmriumState, aggregator, containsNmrium: true }); + }, + [core, dispatchPayload], + ); + + const loadFileCollection = useCallback( + async ( + fileCollection: FileCollection, + metaFile: FileItem | undefined, + parsingOptions: Partial, + ) => { + const parseMetaFileResult = metaFile + ? await parseMetaFile(metaFile) + : null; + const { nmriumState, containsNmrium, selectorRoot } = await core.read( + fileCollection, + parsingOptions, + ); + const resetSourceObject = containsNmrium; + + dispatchPayload({ + nmriumState, + containsNmrium, + parseMetaFileResult, + fileCollection, + selectorRoot, + resetSourceObject, + }); + }, + [core, dispatchPayload], + ); + + const currentPreferences = preferences.current; + const loadUserFiles = useCallback( + async (files: File[]) => { + if (onOpenMetaInformation && files.length === 1 && isMetaFile(files[0])) { + onOpenMetaInformation(files[0]); + return; + } + + const { onLoadProcessing } = workspacePreferences; + const { nmrLoaders: selector } = currentPreferences; + + const parsingOptions: Partial = { + selector, + logger: logger.child({ context: 'nmr-processing' }), + onLoadProcessing, + experimentalFeatures, + }; + + const groupedFiles = await groupFiles(files); + const { nmriumArchiveFiles, fileCollection, metaFile } = groupedFiles; + + if (nmriumArchiveFiles.length > 0) { + await Promise.all( + nmriumArchiveFiles.map((file) => + loadNmriumArchives(file, parsingOptions), + ), + ); + } + + await loadFileCollection(fileCollection, metaFile, parsingOptions); + }, + [ + experimentalFeatures, + loadFileCollection, + loadNmriumArchives, + logger, + onOpenMetaInformation, + currentPreferences, + workspacePreferences, + ], + ); - loadFiles(files) + return useCallback( + (files: File[]) => { + dispatch({ type: 'SET_LOADING_FLAG', payload: { isLoading: true } }); + + loadUserFiles(files) .catch((error: unknown) => { toaster.show({ message: (error as Error).message, intent: 'danger' }); logger.error(error as Error); @@ -133,16 +140,30 @@ export function useLoadFiles(onOpenMetaInformation?: (file: File) => void) { dispatch({ type: 'SET_LOADING_FLAG', payload: { isLoading: false } }); }); }, - [ - core, - dispatch, - dispatchPreferences, - experimentalFeatures, - logger, - onOpenMetaInformation, - preferences, - toaster, - workspacePreferences, - ], + [dispatch, loadUserFiles, logger, toaster], + ); +} + +async function groupFiles(files: File[]) { + const nmriumArchiveFiles: File[] = []; + const otherFiles: File[] = []; + + for (const file of files) { + // eslint-disable-next-line no-await-in-loop + const header = await file.slice(0, 128).arrayBuffer(); + + if (FileCollection.isIum(header, 'chemical/x-nmrium+zip')) { + nmriumArchiveFiles.push(file); + continue; + } + + otherFiles.push(file); + } + + const fileCollection = await new FileCollection().appendFileList(otherFiles); + const metaFile = Object.values(fileCollection.files).find((file) => + isMetaFile(file), ); + + return { nmriumArchiveFiles, fileCollection, metaFile }; } From de6ceeb206e1ccfb5d28b80c6d170af301fcd708 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Fri, 19 Dec 2025 10:27:29 +0100 Subject: [PATCH 3/3] chore: update core deps --- package-lock.json | 18 +++++++++--------- package.json | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 08ca5b117..ba83e0176 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,8 +16,8 @@ "@hookform/resolvers": "^5.2.2", "@tanstack/react-form": "^1.27.3", "@zakodium/nmr-types": "^0.5.0", - "@zakodium/nmrium-core": "^0.5.2", - "@zakodium/nmrium-core-plugins": "^0.6.21", + "@zakodium/nmrium-core": "^0.5.5", + "@zakodium/nmrium-core-plugins": "^0.6.24", "@zakodium/pdnd-esm": "^1.0.2", "@zip.js/zip.js": "^2.8.11", "cheminfo-font": "^1.13.1", @@ -3956,9 +3956,9 @@ } }, "node_modules/@zakodium/nmrium-core": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@zakodium/nmrium-core/-/nmrium-core-0.5.2.tgz", - "integrity": "sha512-lT+oiMQWTVQx+3MEakm7B9ZX3j9R88EZskr4ExuqPI1e+SvoI3LvAOHTYJeHOe9NVfQY6EP4SiHhmM/1U2je8g==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@zakodium/nmrium-core/-/nmrium-core-0.5.5.tgz", + "integrity": "sha512-kbfw3hjMAX+qterS6J48ZDpP0IDOMfdYeftw/Xs2YZyqW8cvoSuV0Vir5GoGLmw2dpBTPjCkllgGjIiMoEl9dw==", "license": "CC-BY-NC-SA-4.0", "dependencies": { "cheminfo-types": "^1.8.1", @@ -3971,13 +3971,13 @@ } }, "node_modules/@zakodium/nmrium-core-plugins": { - "version": "0.6.21", - "resolved": "https://registry.npmjs.org/@zakodium/nmrium-core-plugins/-/nmrium-core-plugins-0.6.21.tgz", - "integrity": "sha512-7O+Z6Mxa/WlhanO027no7MzsyHljTT7dJSDhRueCNGvfi3iWnw1Q24oQd9DvsCGNd9kSDQ+PX3z9R1qGXPZ//w==", + "version": "0.6.24", + "resolved": "https://registry.npmjs.org/@zakodium/nmrium-core-plugins/-/nmrium-core-plugins-0.6.24.tgz", + "integrity": "sha512-aDyzKf7JkFEpzRtdiwTEehfAWJzpR/OWwP4XRjqSyY6HUOwBHJmjQf7m5R6Qj9IaD6PE9+SDv02su/LaI9Yd8g==", "license": "CC-BY-NC-SA-4.0", "dependencies": { "@date-fns/utc": "^2.1.1", - "@zakodium/nmrium-core": "^0.5.2", + "@zakodium/nmrium-core": "^0.5.5", "cheminfo-types": "^1.8.1", "convert-to-jcamp": "^6.0.0", "date-fns": "^4.1.0", diff --git a/package.json b/package.json index a96d957a3..c9a0daeb0 100644 --- a/package.json +++ b/package.json @@ -70,8 +70,8 @@ "@hookform/resolvers": "^5.2.2", "@tanstack/react-form": "^1.27.3", "@zakodium/nmr-types": "^0.5.0", - "@zakodium/nmrium-core": "^0.5.2", - "@zakodium/nmrium-core-plugins": "^0.6.21", + "@zakodium/nmrium-core": "^0.5.5", + "@zakodium/nmrium-core-plugins": "^0.6.24", "@zakodium/pdnd-esm": "^1.0.2", "@zip.js/zip.js": "^2.8.11", "cheminfo-font": "^1.13.1",