Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@
"@hookform/resolvers": "^5.2.2",
"@tanstack/react-form": "^1.27.7",
"@zakodium/nmr-types": "^0.5.0",
"@zakodium/nmrium-core": "^0.5.8",
"@zakodium/nmrium-core-plugins": "^0.6.27",
"@zakodium/nmrium-core": "^0.6.0",
"@zakodium/nmrium-core-plugins": "^0.6.29",
"@zakodium/pdnd-esm": "^1.0.2",
"@zip.js/zip.js": "^2.8.15",
"cheminfo-font": "^1.13.1",
Expand Down
6 changes: 3 additions & 3 deletions src/component/EventsTrackers/KeysListenerTracker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function KeysListenerTracker(props: KeysListenerTrackerProps) {
changeDisplayViewModeHandler,
} = useToolsFunctions();

const { saveAsJSONHandler } = useExport();
const { defaultSaveAsHandler } = useExport();
const isToolVisible = useCheckToolsVisibility();

const { highlight, remove } = useHighlightData();
Expand Down Expand Up @@ -428,7 +428,7 @@ function KeysListenerTracker(props: KeysListenerTrackerProps) {
break;
case 's':
if (isToolVisible('exportAs')) {
saveAsJSONHandler();
defaultSaveAsHandler();
e.preventDefault();
}
break;
Expand Down Expand Up @@ -488,7 +488,7 @@ function KeysListenerTracker(props: KeysListenerTrackerProps) {
nuclei,
openLoader,
openSaveAsDialog,
saveAsJSONHandler,
defaultSaveAsHandler,
toaster,
],
);
Expand Down
94 changes: 29 additions & 65 deletions src/component/hooks/useExport.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { NmriumState } from '@zakodium/nmrium-core';
import { useCallback } from 'react';

import type { ExportOptions } from '../../data/SpectraManager.js';
import { toJSON } from '../../data/SpectraManager.js';
import { useChartData } from '../context/ChartContext.js';
import { useCore } from '../context/CoreContext.js';
Expand All @@ -10,17 +9,21 @@ import { useToaster } from '../context/ToasterContext.js';
import {
browserNotSupportedErrorToast,
copyPNGToClipboard,
exportAsJsonBlob,
exportAsPng,
exportAsSVG,
} from '../utility/export.js';
import { saveAs } from '../utility/save_as.ts';

interface SaveOptions {
include: ExportOptions;
export interface SaveOptions {
include: {
settings: boolean;
view: boolean;
dataType:
| 'NO_DATA'
| 'SELF_CONTAINED'
| 'SELF_CONTAINED_EXTERNAL_DATASOURCE';
};
name: string;
compressed: boolean;
pretty: boolean;
}

export function useExport() {
Expand All @@ -29,80 +32,29 @@ export function useExport() {
const preferencesState = usePreferences();
const core = useCore();

const saveAsJSONHandler = useCallback(
(spaceIndent = 0, isCompressed = true) => {
const hideLoading = toaster.showLoading({
message: 'Exporting as NMRium process in progress',
});
setTimeout(async () => {
try {
const name = state.data[0]?.info?.name || 'experiment';
const exportedData = toJSON(core, state, preferencesState, {
exportTarget: 'nmrium',
view: true,
});

const blob = await exportAsJsonBlob(
exportedData,
name,
spaceIndent,
isCompressed,
);
saveAs({ blob, name, extension: '.nmrium' });
} catch (error) {
toaster.show({
intent: 'danger',
message: `Export failed due to an unexpected error: ${(error as Error)?.message || 'Unknown error'}`,
});
reportError(error);
} finally {
hideLoading();
}
}, 0);
},
[core, preferencesState, state, toaster],
);

const saveHandler = useCallback(
(options: SaveOptions) => {
async function handler() {
const { pretty, compressed, include } = options;
const name = options.name || 'experiment';
const exportArchive =
include.dataType?.startsWith('SELF_CONTAINED') ?? false;
const include = options.include;

const hideLoading = toaster.showLoading({
message: `Exporting as ${name}.nmrium process in progress`,
message: `Exporting as ${name}.nmrium.zip process in progress`,
});
setTimeout(async () => {
try {
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({
state: nmriumState,
aggregator: state.aggregator,
includeData: options.include.dataType === 'SELF_CONTAINED',
includeSettings: options.include.settings,
includeView: options.include.view,
includeData: include.dataType !== 'NO_DATA',
externalData:
include.dataType === 'SELF_CONTAINED' ? 'embedded' : 'linked',
includeSettings: include.settings,
includeView: include.view,
});
const zipBlob = new Blob([archive], {
type: 'chemical/x-nmrium+zip',
Expand All @@ -125,8 +77,20 @@ export function useExport() {
[core, preferencesState, state, toaster],
);

const defaultName = state.data[0]?.info?.name || 'experiment';
const defaultSaveAsHandler = useCallback(() => {
saveHandler({
name: defaultName,
include: {
dataType: 'SELF_CONTAINED',
view: true,
settings: false,
},
});
}, [saveHandler, defaultName]);

return {
saveAsJSONHandler,
defaultSaveAsHandler,
saveHandler,
};
}
Expand Down
55 changes: 14 additions & 41 deletions src/component/modal/SaveAsModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,23 @@ import {
import { useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';

import type { ExportOptions } from '../../data/SpectraManager.js';
import { DataExportOptions } from '../../data/SpectraManager.js';
import { useChartData } from '../context/ChartContext.js';
import ActionButtons from '../elements/ActionButtons.js';
import { Input2Controller } from '../elements/Input2Controller.js';
import type { LabelStyle } from '../elements/Label.js';
import Label from '../elements/Label.js';
import { StyledDialogBody } from '../elements/StyledDialogBody.js';
import useCheckExperimentalFeature from '../hooks/useCheckExperimentalFeature.js';
import type { SaveOptions } from '../hooks/useExport.js';
import { useExport } from '../hooks/useExport.js';

const INITIAL_VALUE = {
const INITIAL_VALUE: SaveOptions = {
name: '',
compressed: false,
pretty: false,
include: {
dataType: DataExportOptions.RAW_DATA,
dataType: DataExportOptions.SELF_CONTAINED,
view: false,
settings: false,
} satisfies ExportOptions,
},
};

export const labelStyle: LabelStyle = {
Expand Down Expand Up @@ -57,15 +54,14 @@ function SaveAsModal(props: SaveAsModalProps) {

return <InnerSaveAsModal onCloseDialog={onCloseDialog} />;
}

function InnerSaveAsModal(props: InnerSaveAsModalProps) {
const { onCloseDialog } = props;
const { sources, data, aggregator } = useChartData();
const { data, aggregator } = useChartData();
const { saveHandler } = useExport();
const experimentalFlagEnabled = useCheckExperimentalFeature();

const fileName = data[0]?.info?.name;

function submitHandler(values: any) {
function submitHandler(values: SaveOptions) {
saveHandler(values);
onCloseDialog?.();
}
Expand Down Expand Up @@ -94,12 +90,6 @@ function InnerSaveAsModal(props: InnerSaveAsModalProps) {
controllerProps={{ rules: { required: true } }}
/>
</Label>
<Label style={labelStyle} title="Compressed">
<Checkbox style={{ margin: 0 }} {...register(`compressed`)} />
</Label>
<Label style={labelStyle} title="Pretty format">
<Checkbox style={{ margin: 0 }} {...register(`pretty`)} />
</Label>
<Label style={labelStyle} title="Include view">
<Checkbox style={{ margin: 0 }} {...register(`include.view`)} />
</Label>
Expand All @@ -114,33 +104,16 @@ function InnerSaveAsModal(props: InnerSaveAsModalProps) {
const { value, ref, ...otherFieldProps } = field;
return (
<RadioGroup inline selectedValue={value} {...otherFieldProps}>
<Radio label="Raw data" value={DataExportOptions.RAW_DATA} />
<Radio
label="Data source"
value={DataExportOptions.DATA_SOURCE}
disabled={Object.keys(sources).length === 0}
label="External data embed"
value={DataExportOptions.SELF_CONTAINED}
/>
<Radio
label="External data linked"
disabled={!containsLinkedFiles}
value={DataExportOptions.SELF_CONTAINED_EXTERNAL_DATASOURCE}
/>
<Radio label="No data" value={DataExportOptions.NO_DATA} />

{/*
* Radio group works with Children.map.
* So Radio must be direct children of RadioGroup.
*/}
{experimentalFlagEnabled && (
<Radio
label="Full data (external data embed, experimental)"
value={DataExportOptions.SELF_CONTAINED}
/>
)}
{experimentalFlagEnabled && (
<Radio
label="Full data (external data linked, experimental)"
disabled={!containsLinkedFiles}
value={
DataExportOptions.SELF_CONTAINED_EXTERNAL_DATASOURCE
}
/>
)}
</RadioGroup>
);
}}
Expand Down
11 changes: 11 additions & 0 deletions src/component/reducer/actions/LoadAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,11 @@ function initData(
if (view) {
const defaultViewState = getDefaultViewState();
draft.view = lodashMerge(defaultViewState, view);
draft.view.molecules = Object.fromEntries(
Object.entries(draft.view.molecules).filter(([id]) =>
draft.molecules.some((molecule) => molecule.id === id),
),
);
}
draft.actionType = action.type;
draft.isLoading = false;
Expand Down Expand Up @@ -335,6 +340,12 @@ function handleLoadDropFiles(draft: Draft<State>, action: LoadDropFilesAction) {
draft.sources = {};
}

draft.view.molecules = Object.fromEntries(
Object.entries(draft.view.molecules).filter(([id]) =>
draft.molecules.some((molecule) => molecule.id === id),
),
);

draft.actionType = type;
draft.isLoading = false;
return undefined;
Expand Down
Loading
Loading