Skip to content

Commit 2081ce4

Browse files
feat: update nmrium to version 2.0.0
1 parent fd824db commit 2081ce4

File tree

8 files changed

+412
-361
lines changed

8 files changed

+412
-361
lines changed

package-lock.json

Lines changed: 147 additions & 126 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
"@blueprintjs/select": "^6.1.3",
1919
"@emotion/styled": "^11.14.1",
2020
"@vitejs/plugin-react": "^5.1.4",
21-
"@zakodium/nmrium-core": "^0.6.5",
22-
"@zakodium/nmrium-core-plugins": "^0.6.39",
21+
"@zakodium/nmrium-core": "^0.7.1",
22+
"@zakodium/nmrium-core-plugins": "^0.7.1",
2323
"fifo-logger": "^2.0.1",
2424
"filelist-utils": "^1.11.3",
25-
"nmr-processing": "^22.5.0",
26-
"nmrium": "^1.12.0",
25+
"nmr-processing": "^22.5.2",
26+
"nmrium": "^2.0.0",
2727
"openchemlib": "^9.20.0",
2828
"react-science": "^19.10.1"
2929
},

src/Loadingindicator.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Spinner } from '@blueprintjs/core';
2+
import styled from '@emotion/styled';
3+
4+
const Overlay = styled.div`
5+
position: absolute;
6+
inset: 0;
7+
z-index: 10;
8+
display: flex;
9+
flex-direction: column;
10+
align-items: center;
11+
justify-content: center;
12+
background-color: rgba(255, 255, 255, 0.7);
13+
backdrop-filter: blur(4px);
14+
-webkit-backdrop-filter: blur(4px);
15+
gap: 12px;
16+
user-select: none;
17+
-webkit-user-select: none;
18+
`;
19+
20+
const Label = styled.span`
21+
font-size: 0.8125rem;
22+
font-weight: 500;
23+
letter-spacing: 0.08em;
24+
text-transform: uppercase;
25+
color: #6b7280;
26+
`;
27+
28+
interface LoadingIndicatorProps {
29+
label?: string;
30+
visible: boolean;
31+
}
32+
33+
export function LoadingIndicator({
34+
label = 'Loading',
35+
visible,
36+
}: LoadingIndicatorProps) {
37+
if (!visible) return null;
38+
39+
return (
40+
<Overlay>
41+
<Spinner size={36} />
42+
<Label>{label}</Label>
43+
</Overlay>
44+
);
45+
}

src/NMRiumWrapper.tsx

Lines changed: 25 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,34 @@
1-
import type { NmriumData } from '@zakodium/nmrium-core';
21
import type { NMRiumChangeCb, NMRiumRefAPI } from 'nmrium';
32
import { NMRium } from 'nmrium';
43
import type { CSSProperties } from 'react';
54
import { useCallback, useEffect, useRef } from 'react';
65
import { RootLayout } from 'react-science/ui';
76

7+
import { LoadingIndicator } from './Loadingindicator.js';
88
import events from './events/event.js';
9-
import type { NMRiumData } from './hooks/useLoadSpectra.js';
109
import { useLoadSpectra } from './hooks/useLoadSpectra.js';
1110
import { usePreferences } from './hooks/usePreferences.js';
1211
import { useWhiteList } from './hooks/useWhiteList.js';
1312
import AboutUsModal from './modal/AboutUsModal.js';
1413

15-
const styles: Record<'container' | 'loadingContainer', CSSProperties> = {
16-
container: {
17-
height: '100%',
18-
width: '100%',
19-
position: 'relative',
20-
},
21-
22-
loadingContainer: {
23-
position: 'absolute',
24-
top: 0,
25-
left: 0,
26-
right: 0,
27-
bottom: 0,
28-
zIndex: 1,
29-
display: 'flex',
30-
alignItems: 'center',
31-
justifyContent: 'center',
32-
backgroundColor: '#ffffffc9',
33-
fontSize: '1.4em',
34-
userSelect: 'none',
35-
WebkitUserSelect: 'none',
36-
},
14+
const containerStyle: CSSProperties = {
15+
height: '100%',
16+
width: '100%',
17+
position: 'relative',
3718
};
3819

3920
export default function NMRiumWrapper() {
4021
const { allowedOrigins, isFetchAllowedOriginsPending } = useWhiteList();
4122
const nmriumRef = useRef<NMRiumRefAPI>(null);
4223
const { workspace, preferences, defaultEmptyMessage, customWorkspaces } =
4324
usePreferences();
25+
26+
const { load: loadSpectra, data, isLoading } = useLoadSpectra();
27+
4428
const dataChangeHandler = useCallback<NMRiumChangeCb>((state, source) => {
45-
events.trigger('data-change', {
46-
state,
47-
source,
48-
});
29+
events.trigger('data-change', { state, source });
4930
}, []);
5031

51-
const { load: loadSpectra, data, setData } = useLoadSpectra();
52-
5332
useEffect(() => {
5433
const clearActionListener = events.on(
5534
'action-request',
@@ -67,33 +46,36 @@ export default function NMRiumWrapper() {
6746
}
6847
default: {
6948
throw new Error(
70-
`ERROR! Property 'type' accept only 'exportViewerAsBlob'.`,
49+
`ERROR! Property 'type' accepts only 'exportViewerAsBlob'.`,
7150
);
7251
}
7352
}
7453
},
7554
{ allowedOrigins },
7655
);
56+
7757
const clearLoadListener = events.on(
7858
'load',
7959
(loadData) => {
8060
switch (loadData.type) {
81-
case 'nmrium':
82-
setData(loadData.data as unknown as NMRiumData);
61+
case 'nmrium': {
62+
const { data, activeTab = '' } = loadData;
63+
void loadSpectra({ nmrium: data, activeTab });
8364
break;
65+
}
8466
case 'file': {
8567
const { data: files, activeTab = '' } = loadData;
86-
loadSpectra({ files, activeTab });
68+
void loadSpectra({ files, activeTab });
8769
break;
8870
}
8971
case 'url': {
9072
const { data: urls, activeTab = '' } = loadData;
91-
loadSpectra({ urls, activeTab });
73+
void loadSpectra({ urls, activeTab });
9274
break;
9375
}
9476
default: {
9577
throw new Error(
96-
`ERROR! Property 'type' accept only nmrium, url or file.`,
78+
`ERROR! Property 'type' accepts only 'nmrium', 'url', or 'file'.`,
9779
);
9880
}
9981
}
@@ -106,16 +88,16 @@ export default function NMRiumWrapper() {
10688
clearActionListener();
10789
};
10890
});
91+
92+
const isShowingOverlay = isFetchAllowedOriginsPending || isLoading;
93+
10994
return (
110-
<RootLayout style={styles.container}>
111-
{isFetchAllowedOriginsPending && (
112-
<div style={styles.loadingContainer}>
113-
<span>Loading .... </span>
114-
</div>
115-
)}
95+
<RootLayout style={containerStyle}>
96+
<LoadingIndicator visible={isShowingOverlay} />
11697
<NMRium
11798
ref={nmriumRef}
118-
data={data as unknown as NmriumData}
99+
state={data?.state}
100+
aggregator={data?.aggregator}
119101
onChange={dataChangeHandler}
120102
preferences={preferences}
121103
workspace={workspace}

src/events/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type LoadData =
2626
| {
2727
data: NMRiumData;
2828
type: 'nmrium';
29+
activeTab?: string;
2930
};
3031

3132
interface ActionRequest {

src/hooks/useLoadSpectra.ts

Lines changed: 70 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,46 @@
1-
import type { ParsingOptions, ViewState } from '@zakodium/nmrium-core';
1+
import type {
2+
CoreReadReturn,
3+
ParsingOptions,
4+
ViewState,
5+
} from '@zakodium/nmrium-core';
26
import { CURRENT_EXPORT_VERSION } from '@zakodium/nmrium-core';
37
import init from '@zakodium/nmrium-core-plugins';
48
import { FifoLogger } from 'fifo-logger';
59
import { FileCollection } from 'file-collection';
6-
import type { NMRiumState } from 'nmrium';
710
import { useCallback, useMemo, useState } from 'react';
811

912
import events from '../events/event.js';
1013
import { getFileNameFromURL } from '../utilities/getFileNameFromURL.js';
1114
import { isArrayOfString } from '../utilities/isArrayOfString.js';
1215

13-
type DeepPartial<T> = {
14-
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
16+
type LoadOptions =
17+
| { nmrium: object; activeTab?: string }
18+
| { urls: string[]; activeTab?: string }
19+
| { files: File[]; activeTab?: string };
20+
21+
// CoreReadReturn with `state.view` made optional to allow partial injection.
22+
export type NMRiumData = Omit<CoreReadReturn, 'state'> & {
23+
state: Omit<CoreReadReturn['state'], 'view'> & { view?: ViewState };
1524
};
1625

17-
const core = init();
26+
interface UseLoadSpectraResult {
27+
data: NMRiumData | null;
28+
load: (options: LoadOptions) => Promise<void>;
29+
isLoading: boolean;
30+
}
1831

32+
const core = init();
1933
const logger = new FifoLogger();
2034

21-
function handleLogger({ detail: { logs } }) {
35+
logger.addEventListener('change', ({ detail: { logs } }) => {
2236
const log = logs.at(-1);
23-
if (log && ['error', 'fatal', 'warn'].includes(log.levelLabel)) {
24-
const error = log?.error || new Error(log?.message);
25-
events.trigger('error', error);
26-
// eslint-disable-next-line no-console
27-
console.log(error);
28-
}
29-
}
37+
if (!log || !['error', 'fatal', 'warn'].includes(log.levelLabel)) return;
3038

31-
logger.addEventListener('change', handleLogger);
39+
const error = log.error ?? new Error(log.message);
40+
events.trigger('error', error);
41+
// eslint-disable-next-line no-console
42+
console.log(error);
43+
});
3244

3345
const PARSING_OPTIONS: Partial<ParsingOptions> = {
3446
onLoadProcessing: { autoProcessing: true },
@@ -37,73 +49,57 @@ const PARSING_OPTIONS: Partial<ParsingOptions> = {
3749
logger,
3850
};
3951

40-
async function loadSpectraFromFiles(files: File[]) {
52+
async function loadSpectraFromNMRium(nmrium: object): Promise<CoreReadReturn> {
53+
return core.readNMRiumObject(nmrium, PARSING_OPTIONS);
54+
}
55+
56+
async function loadSpectraFromFiles(files: File[]): Promise<CoreReadReturn> {
4157
const fileCollection = await new FileCollection().appendFileList(files);
42-
const {
43-
nmriumState: { data, version },
44-
} = await core.read(fileCollection, PARSING_OPTIONS);
45-
return { data, version } as NMRiumData;
58+
return core.read(fileCollection, PARSING_OPTIONS);
4659
}
4760

48-
async function loadSpectraFromURLs(urls: string[]) {
61+
async function loadSpectraFromURLs(urls: string[]): Promise<CoreReadReturn> {
4962
const entries = urls.map((url) => {
5063
const refURL = new URL(url);
5164
const name = getFileNameFromURL(url);
5265
let path = refURL.pathname;
53-
const hasExtension = name?.includes('.');
54-
if (!hasExtension) {
66+
67+
if (!name?.includes('.')) {
5568
path = `${path}.zip`;
5669
}
57-
return { relativePath: path, baseURL: refURL.origin };
58-
}, []);
59-
60-
const [{ data, version }] = await core.readFromWebSource(
61-
{ entries },
62-
PARSING_OPTIONS,
63-
);
64-
return { data, version } as NMRiumData;
65-
}
66-
67-
type LoadOptions =
68-
| { urls: string[]; activeTab?: string }
69-
| { files: File[]; activeTab?: string };
7070

71-
export type NMRiumData = Pick<NMRiumState, 'data' | 'version'>;
71+
return { relativePath: path, baseURL: refURL.origin };
72+
});
7273

73-
interface UseLoadSpectraResult {
74-
data: NMRiumData;
75-
load: (options: LoadOptions) => void;
76-
setData: (data: NMRiumData) => void;
77-
isLoading: boolean;
74+
return core.readFromWebSource({ entries }, PARSING_OPTIONS);
7875
}
7976

8077
export function useLoadSpectra(): UseLoadSpectraResult {
81-
const [data, setData] = useState<NMRiumData>({
82-
data: { spectra: [], molecules: [] },
83-
version: CURRENT_EXPORT_VERSION,
84-
});
85-
const [activeTab, setActiveTab] = useState<string>();
86-
const [isLoading, setLoading] = useState<boolean>(false);
78+
const [result, setResult] = useState<CoreReadReturn | null>(null);
79+
const [activeTab, setActiveTab] = useState<string | undefined>();
80+
const [isLoading, setLoading] = useState(false);
8781

8882
const load = useCallback(async (options: LoadOptions) => {
8983
setLoading(true);
9084
try {
91-
if ('urls' in options) {
92-
if (isArrayOfString(options.urls)) {
93-
const result = await loadSpectraFromURLs(options.urls);
94-
setData(result);
95-
setActiveTab(options?.activeTab);
96-
} else {
85+
if ('nmrium' in options) {
86+
const nmriumResult = await loadSpectraFromNMRium(options.nmrium);
87+
setResult(nmriumResult);
88+
setActiveTab(
89+
options.activeTab ?? nmriumResult.state.view?.spectra?.activeTab,
90+
);
91+
} else if ('urls' in options) {
92+
if (!isArrayOfString(options.urls)) {
9793
throw new Error('The input must be a valid urls array of string[]');
9894
}
99-
} else if ('files' in options) {
100-
const result = await loadSpectraFromFiles(options.files);
101-
setData(result);
102-
setActiveTab(options?.activeTab);
95+
setResult(await loadSpectraFromURLs(options.urls));
96+
setActiveTab(options.activeTab);
97+
} else {
98+
setResult(await loadSpectraFromFiles(options.files));
99+
setActiveTab(options.activeTab);
103100
}
104101
} catch (error: unknown) {
105-
const loadError = error as Error;
106-
events.trigger('error', loadError);
102+
events.trigger('error', error as Error);
107103
// eslint-disable-next-line no-console
108104
console.log(error);
109105
} finally {
@@ -112,14 +108,19 @@ export function useLoadSpectra(): UseLoadSpectraResult {
112108
}, []);
113109

114110
return useMemo(() => {
115-
let view: DeepPartial<ViewState> = {};
116-
view = { spectra: { activeTab } };
117-
const { version, ...other } = data;
118-
return {
119-
data: { version: version ?? CURRENT_EXPORT_VERSION, ...other, view },
120-
load,
121-
isLoading,
122-
setData,
123-
};
124-
}, [activeTab, data, isLoading, load]);
111+
const view = { spectra: { activeTab } } as unknown as ViewState;
112+
113+
const data: NMRiumData | null = result
114+
? {
115+
...result,
116+
state: {
117+
version: result.state.version ?? CURRENT_EXPORT_VERSION,
118+
...result.state,
119+
view,
120+
},
121+
}
122+
: null;
123+
124+
return { data, load, isLoading };
125+
}, [activeTab, result, isLoading, load]);
125126
}

0 commit comments

Comments
 (0)