diff --git a/package-lock.json b/package-lock.json
index 4ca238d67f..c8beabc0d2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,9 +17,9 @@
"@tanstack/react-form": "^1.33.0",
"@tanstack/react-store": "^0.11.0",
"@tanstack/react-table": "^8.21.3",
- "@zakodium/nmr-types": "^0.5.12",
- "@zakodium/nmrium-core": "^0.7.24",
- "@zakodium/nmrium-core-plugins": "^0.7.32",
+ "@zakodium/nmr-types": "^0.5.14",
+ "@zakodium/nmrium-core": "^0.7.31",
+ "@zakodium/nmrium-core-plugins": "^0.7.40",
"@zakodium/pdnd-esm": "^1.0.2",
"@zip.js/zip.js": "^2.8.26",
"cheminfo-font": "^1.27.0",
@@ -108,7 +108,7 @@
"stylelint": "^17.13.0",
"stylelint-config-standard": "^40.0.0",
"typescript": "~6.0.3",
- "vite": "^8.0.16",
+ "vite": "=8.0.16",
"vitest": "^4.1.9"
},
"peerDependencies": {
@@ -4016,9 +4016,9 @@
}
},
"node_modules/@zakodium/nmrium-core": {
- "version": "0.7.30",
- "resolved": "https://registry.npmjs.org/@zakodium/nmrium-core/-/nmrium-core-0.7.30.tgz",
- "integrity": "sha512-uds3UysKrAgm38Yt2swNnt8nmf5SHqt1LuVnewwDTolztl3Lead0RtgOYsRjnL30k6gpvGrgaoENkIMxSY0/kQ==",
+ "version": "0.7.31",
+ "resolved": "https://registry.npmjs.org/@zakodium/nmrium-core/-/nmrium-core-0.7.31.tgz",
+ "integrity": "sha512-DVhxwBTY5ftXGJ+HahdDwRJFXfRHipVijf8QLOduIYnru+f+c+axYalCFjmE0rvKTgo7vPekBVpRdcvg8u4lrQ==",
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"cheminfo-types": "^1.15.0",
@@ -4034,24 +4034,24 @@
}
},
"node_modules/@zakodium/nmrium-core-plugins": {
- "version": "0.7.39",
- "resolved": "https://registry.npmjs.org/@zakodium/nmrium-core-plugins/-/nmrium-core-plugins-0.7.39.tgz",
- "integrity": "sha512-WFO8UmUB4ZGwO+eCTjC1RfUBeCtv+raQmsuVx6L7zf1/BuD0N3Lq0q+JpKkdn85sfFGckyejJr1yo2qd91EVMQ==",
+ "version": "0.7.40",
+ "resolved": "https://registry.npmjs.org/@zakodium/nmrium-core-plugins/-/nmrium-core-plugins-0.7.40.tgz",
+ "integrity": "sha512-ZepZd+0TPgwTpGbJPH99VSBFlNrhqDjtr4dcvYo5Gy+VDz0gf0DeSmjOCnHeFwkuPnTTG/YHWG8/6BSQHScyzw==",
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"@date-fns/utc": "^2.1.1",
- "@zakodium/nmrium-core": "^0.7.30",
+ "@zakodium/nmrium-core": "^0.7.31",
"cheminfo-types": "^1.15.0",
"convert-to-jcamp": "^7.0.0",
"date-fns": "^4.1.0",
"file-collection": "^6.6.1",
"gyromagnetic-ratio": "^2.0.0",
"is-any-array": "^3.0.0",
- "jcampconverter": "^12.3.3",
+ "jcampconverter": "^12.4.0",
"linear-sum-assignment": "^1.0.9",
"lodash.merge": "^4.6.2",
"ml-spectra-processing": "^14.28.1",
- "nmr-processing": "^22.13.0",
+ "nmr-processing": "^22.14.0",
"openchemlib": "^9.22.0",
"openchemlib-utils": "^8.15.0",
"sdf-parser": "^8.0.0",
@@ -8482,9 +8482,9 @@
}
},
"node_modules/jcampconverter": {
- "version": "12.3.3",
- "resolved": "https://registry.npmjs.org/jcampconverter/-/jcampconverter-12.3.3.tgz",
- "integrity": "sha512-Ayu6hbf5AM1eahyMp0EmnuDQnG2KRKYODq1SE6ITwcv3no2YCvbeQOvoRqdxwhV3t11PtESr57jvWvfttagvdw==",
+ "version": "12.4.0",
+ "resolved": "https://registry.npmjs.org/jcampconverter/-/jcampconverter-12.4.0.tgz",
+ "integrity": "sha512-7sIYUsBW2ZWST702MbvUqoh5lkB2ud/5sexzshvSzpBIgHEw/rO4TIzMQit2WloJbl0jVFh9xH4D8o2kALAMtw==",
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"cheminfo-types": "^1.15.0",
@@ -9739,9 +9739,9 @@
}
},
"node_modules/nmr-processing": {
- "version": "22.13.0",
- "resolved": "https://registry.npmjs.org/nmr-processing/-/nmr-processing-22.13.0.tgz",
- "integrity": "sha512-udS0fxLjJgojI382/6LZvBGLs+z+edoiULrqNjHYCQdFCgsEp6JYBNnHABY29myvXGN9ml17Clhc/qLaUJDe3w==",
+ "version": "22.14.0",
+ "resolved": "https://registry.npmjs.org/nmr-processing/-/nmr-processing-22.14.0.tgz",
+ "integrity": "sha512-MD5Y8aDqY5NRCAWf6cvnqpgVlVlIkbM1DZ5Zf+pyw411UolQ970mtRWxHTr0RUB8wsB57f+UXniZVKqtwANbiA==",
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"binary-search": "^1.3.6",
diff --git a/package.json b/package.json
index 0c92d8a4f9..740bfea213 100644
--- a/package.json
+++ b/package.json
@@ -71,9 +71,9 @@
"@tanstack/react-form": "^1.33.0",
"@tanstack/react-store": "^0.11.0",
"@tanstack/react-table": "^8.21.3",
- "@zakodium/nmr-types": "^0.5.12",
- "@zakodium/nmrium-core": "^0.7.24",
- "@zakodium/nmrium-core-plugins": "^0.7.32",
+ "@zakodium/nmr-types": "^0.5.14",
+ "@zakodium/nmrium-core": "^0.7.31",
+ "@zakodium/nmrium-core-plugins": "^0.7.40",
"@zakodium/pdnd-esm": "^1.0.2",
"@zip.js/zip.js": "^2.8.26",
"cheminfo-font": "^1.27.0",
diff --git a/src/component/1d/FloatingRanges.tsx b/src/component/1d/FloatingRanges.tsx
deleted file mode 100644
index 601c651a1d..0000000000
--- a/src/component/1d/FloatingRanges.tsx
+++ /dev/null
@@ -1,378 +0,0 @@
-import styled from '@emotion/styled';
-import type { Ranges } from '@zakodium/nmr-types';
-import type { BoundingBox } from '@zakodium/nmrium-core';
-import { checkMultiplicity } from 'nmr-processing';
-import { memo, useEffect, useState } from 'react';
-import { BsArrowsMove } from 'react-icons/bs';
-import { FaTimes } from 'react-icons/fa';
-import { Rnd } from 'react-rnd';
-
-import { isSpectrum1D } from '../../data/data1d/Spectrum1D/index.js';
-import { isSignalRange } from '../../data/utilities/RangeUtilities.js';
-import type { SVGTableColumn } from '../SVGTable.js';
-import { SVGTable } from '../SVGTable.js';
-import { useChartData } from '../context/ChartContext.js';
-import { useDispatch } from '../context/DispatchContext.js';
-import { useGlobal } from '../context/GlobalContext.js';
-import type { ActionsButtonsPopoverProps } from '../elements/ActionsButtonsPopover.js';
-import { ActionsButtonsPopover } from '../elements/ActionsButtonsPopover.js';
-import { useActiveNucleusTab } from '../hooks/useActiveNucleusTab.js';
-import { usePanelPreferences } from '../hooks/usePanelPreferences.js';
-import { useSVGUnitConverter } from '../hooks/useSVGUnitConverter.js';
-import useSpectraByActiveNucleus from '../hooks/useSpectraPerNucleus.js';
-import { useCheckExportStatus } from '../hooks/useViewportSize.js';
-import { extractChemicalElement } from '../utility/extractChemicalElement.js';
-import { formatNumber } from '../utility/formatNumber.js';
-
-const ReactRnd = styled(Rnd)`
- border: 1px solid transparent;
-
- :hover {
- background-color: white;
- border: 1px solid #ebecf1;
-
- button {
- visibility: visible;
- }
- }
-`;
-
-interface RangesTableProps {
- ranges: Ranges['values'];
-}
-
-interface RangeItem {
- id: string;
- delta: string;
- multiplicity: string;
- integration: string;
- coupling: string;
-}
-
-function useMapRanges(ranges: Ranges['values']) {
- const output: RangeItem[] = [];
- const activeTab = useActiveNucleusTab();
- const preferences = usePanelPreferences('ranges', activeTab);
- for (const range of ranges) {
- const { id, from, to, integration, signals = [] } = range;
- const relativeFlag = isSignalRange(range);
- const formattedValue = formatNumber(
- integration,
- preferences.relative.format,
- );
- const integrationValue = relativeFlag
- ? formattedValue
- : `[ ${formattedValue} ]`;
-
- const rangeText = `${formatNumber(from, preferences.from.format)} - ${formatNumber(
- to,
- preferences.to.format,
- )}`;
- if (signals.length > 0) {
- for (const signal of signals) {
- const { multiplicity, delta, js = [] } = signal;
- const coupling = js
- .map((jsItem) =>
- !Number.isNaN(jsItem.coupling)
- ? formatNumber(jsItem.coupling, preferences.coupling.format)
- : '',
- )
- .join(',');
- const signalDelta = !checkMultiplicity(multiplicity, ['m'])
- ? rangeText
- : formatNumber(delta, preferences.deltaPPM.format);
- output.push({
- id,
- delta: signalDelta,
- multiplicity,
- integration: integrationValue,
- coupling,
- });
- }
- } else {
- output.push({
- id,
- delta: rangeText,
- multiplicity: 'm',
- integration: integrationValue,
- coupling: '',
- });
- }
- }
- return output;
-}
-function InnerSVGRangesTable(props: RangesTableProps) {
- const { ranges } = props;
- const {
- view: {
- spectra: { activeTab },
- },
- } = useChartData();
- const data = useMapRanges(ranges);
-
- if (!data) return null;
-
- const element = extractChemicalElement(activeTab);
-
- const columns: Array> = [
- {
- accessorKey: 'delta',
- header: 'δ (ppm)',
- width: 100,
- rowSpanGroupKey: 'id',
- headerTextProps: { fontWeight: 'bold' },
- cellBoxProps: { stroke: '#dedede', fill: 'white', fillOpacity: 0.8 },
- headerBoxProps: { stroke: '#dedede', fill: '#E5E8EB' },
- },
- {
- accessorKey: 'integration',
- header: `Rel. ${element}`,
- width: 60,
- rowSpanGroupKey: 'id',
- headerTextProps: { fontWeight: 'bold' },
- cellBoxProps: { stroke: '#dedede', fill: 'white', fillOpacity: 0.8 },
- headerBoxProps: { stroke: '#dedede', fill: '#E5E8EB' },
- },
- {
- accessorKey: 'multiplicity',
- header: 'Mult.',
- width: 60,
- rowSpanGroupKey: 'id',
- headerTextProps: { fontWeight: 'bold' },
- cellBoxProps: { stroke: '#dedede', fill: 'white', fillOpacity: 0.8 },
- headerBoxProps: { stroke: '#dedede', fill: '#E5E8EB' },
- },
- {
- accessorKey: 'coupling',
- header: 'J (Hz)',
- width: 120,
- headerTextProps: { fontWeight: 'bold' },
- cellBoxProps: { stroke: '#dedede', fill: 'white', fillOpacity: 0.8 },
- headerBoxProps: { stroke: '#dedede', fill: '#E5E8EB' },
- },
- ];
-
- return data={data} columns={columns} />;
-}
-
-const SVGRangesTable = memo(InnerSVGRangesTable);
-
-interface DraggablePublicationStringProps {
- ranges: Ranges['values'] | undefined;
- bonding: BoundingBox;
- spectrumKey: string;
-}
-
-function DraggableRanges(props: DraggablePublicationStringProps) {
- const { ranges = [], bonding: externalBounding, spectrumKey } = props;
- const dispatch = useDispatch();
- const { viewerRef } = useGlobal();
- const [bounding, setBounding] = useState(externalBounding);
- const [isMoveActive, setIsMoveActive] = useState(false);
- const { percentToPixel, pixelToPercent } = useSVGUnitConverter();
- const isExportProcessStart = useCheckExportStatus();
-
- useEffect(() => {
- setBounding({ ...externalBounding });
- }, [externalBounding]);
-
- function handleResize(
- internalBounding: Pick,
- ) {
- const { width = 0, height = 0 } = convertToPixel(externalBounding);
- internalBounding.width += width;
- internalBounding.height += height;
- setBounding((prevBounding) => ({
- ...prevBounding,
- ...convertToPercent(internalBounding),
- }));
- }
-
- function handleDrag(internalBounding: Pick) {
- setBounding((prevBounding) => ({
- ...prevBounding,
- ...convertToPercent(internalBounding),
- }));
- }
- function handleChangeInsetBounding(bounding: Partial) {
- if (
- typeof bounding?.width === 'number' &&
- typeof bounding?.height === 'number'
- ) {
- const { width, height } = externalBounding;
- bounding.width += width;
- bounding.height += height;
- }
-
- dispatch({
- type: 'CHANGE_RANGES_VIEW_FLOATING_BOX_BOUNDING',
- payload: {
- spectrumKey,
- bounding: convertToPercent(bounding),
- target: 'rangesBounding',
- },
- });
- }
-
- function convertToPixel(bounding: Partial) {
- const { x, y, height, width } = bounding;
- const output: Partial = {};
-
- if (x) {
- output.x = percentToPixel(x, 'x');
- }
- if (y) {
- output.y = percentToPixel(y, 'y');
- }
- if (width) {
- output.width = width;
- }
- if (height) {
- output.height = height;
- }
-
- return output;
- }
- function convertToPercent(bounding: Partial) {
- const { x, y, height, width } = bounding;
- const output: Partial = {};
-
- if (x) {
- output.x = pixelToPercent(x, 'x');
- }
- if (y) {
- output.y = pixelToPercent(y, 'y');
- }
- if (width) {
- output.width = width;
- }
- if (height) {
- output.height = height;
- }
-
- return output;
- }
-
- function handleRemove() {
- dispatch({
- type: 'TOGGLE_RANGES_VIEW_PROPERTY',
- payload: { key: 'showRanges', spectrumKey },
- });
- }
-
- const actionButtons: ActionsButtonsPopoverProps['buttons'] = [
- {
- icon: ,
-
- intent: 'none',
- title: 'Move ranges table',
- style: { cursor: 'move' },
- className: 'handle',
- },
- {
- icon: ,
- intent: 'danger',
- title: 'Hide ranges table',
- onClick: handleRemove,
- },
- ];
- if (!viewerRef || ranges.length === 0) return null;
-
- const { x: xInPercent, y: yInPercent } = bounding;
-
- const x = percentToPixel(xInPercent, 'x');
- const y = percentToPixel(yInPercent, 'y');
-
- if (isExportProcessStart) {
- return (
-
-
-
- );
- }
-
- return (
- setIsMoveActive(true)}
- onResize={(e, dir, eRef, size, position) =>
- handleResize({ ...size, ...position })
- }
- onResizeStop={(e, dir, eRef, size, position) =>
- handleChangeInsetBounding({ ...size, ...position })
- }
- onDrag={(e, { x, y }) => {
- handleDrag({ x, y });
- }}
- onDragStop={(e, { x, y }) => {
- handleChangeInsetBounding({ x, y });
- setIsMoveActive(false);
- }}
- resizeHandleWrapperStyle={{ backgroundColor: 'white' }}
- >
-
-
-
-
- );
-}
-
-function useSpectraRanges() {
- const spectra = useSpectraByActiveNucleus();
- const output: Record = {};
-
- for (const spectrum of spectra) {
- if (!isSpectrum1D(spectrum)) {
- continue;
- }
- const { id: spectrumKey, ranges } = spectrum;
-
- if (!Array.isArray(ranges?.values) || ranges.values.length === 0) {
- continue;
- }
-
- output[spectrumKey] = ranges.values;
- }
-
- return output;
-}
-
-export function FloatingRanges() {
- const spectraRanges = useSpectraRanges();
- const {
- view: { ranges },
- } = useChartData();
-
- const options = Object.entries(ranges);
-
- return options.map(([spectrumKey, viewOptions]) => {
- const { showRanges, rangesBounding } = viewOptions;
- if (!showRanges) return null;
-
- return (
-
- );
- });
-}
diff --git a/src/component/1d/peaks/Peaks.tsx b/src/component/1d/peaks/Peaks.tsx
index 49300d0029..64c89a9d95 100644
--- a/src/component/1d/peaks/Peaks.tsx
+++ b/src/component/1d/peaks/Peaks.tsx
@@ -188,9 +188,12 @@ export default function Peaks(props: PeaksProps) {
const spectrum = useSpectrum(emptyData) as Spectrum1D;
const peaksViewState = useActiveSpectrumPeaksViewState();
const rangesViewState = useActiveSpectrumRangesViewState();
+ const { tablePreferences } = usePanelPreferences(
+ peaksSource === 'peaks' ? 'peaks' : 'ranges',
+ nucleus,
+ );
const { deltaPPM: { format: peakFormat } = { format: '0.0' } } =
- usePanelPreferences(peaksSource === 'peaks' ? 'peaks' : 'ranges', nucleus);
-
+ tablePreferences;
const canDisplaySpectrumPeaks =
!spectrum.display.isVisible || spectrum.info?.isFid;
let mode: PeaksMode = 'spread';
diff --git a/src/component/1d/peaks/usePeakShapesPath.ts b/src/component/1d/peaks/usePeakShapesPath.ts
index 6c935f8971..544fd5dcd6 100644
--- a/src/component/1d/peaks/usePeakShapesPath.ts
+++ b/src/component/1d/peaks/usePeakShapesPath.ts
@@ -29,7 +29,6 @@ export function usePeakShapesPath(spectrum: Spectrum1D) {
const frequency = spectrum.info.originFrequency;
let pathSeries: DataXY | null = null;
-
switch (target) {
case 'peakShape': {
const { peak } = options;
diff --git a/src/component/1d/ranges/FloatingRangeTablePreferencesModal.tsx b/src/component/1d/ranges/FloatingRangeTablePreferencesModal.tsx
new file mode 100644
index 0000000000..6bcf29de0c
--- /dev/null
+++ b/src/component/1d/ranges/FloatingRangeTablePreferencesModal.tsx
@@ -0,0 +1,238 @@
+import { DialogBody, DialogFooter, MenuItem } from '@blueprintjs/core';
+import { MultiSelect } from '@blueprintjs/select';
+import type { SignalKind } from '@zakodium/nmr-types';
+import { useMemo } from 'react';
+import type { Control } from 'react-hook-form';
+import { useController, useForm } from 'react-hook-form';
+import { Button } from 'react-science/ui';
+
+import type { SignalKindItem } from '../../../data/constants/signalsKinds.ts';
+import { SIGNAL_KINDS } from '../../../data/constants/signalsKinds.ts';
+import { usePreferences } from '../../context/PreferencesContext.js';
+import { fieldLabelStyle } from '../../elements/FormatField.tsx';
+import Label from '../../elements/Label.tsx';
+import { StandardDialog } from '../../elements/StandardDialog.tsx';
+import useNucleus from '../../hooks/useNucleus.js';
+import { usePanelPreferencesByNuclei } from '../../hooks/usePanelPreferences.js';
+import type { NucleusPreferenceField } from '../../panels/extra/preferences/NucleusPreferences.tsx';
+import { NucleusPreferences } from '../../panels/extra/preferences/NucleusPreferences.tsx';
+import { getUniqueNuclei } from '../../utility/getUniqueNuclei.js';
+
+const formatFields: NucleusPreferenceField[] = [
+ {
+ id: 1,
+ label: 'Serial number :',
+ checkFieldName: 'floatingTablePreferences.showSerialNumber',
+ hideFormatField: true,
+ },
+ {
+ id: 2,
+ label: 'Assignment label :',
+ checkFieldName: 'floatingTablePreferences.showAssignmentLabel',
+ hideFormatField: true,
+ },
+ {
+ id: 3,
+ label: 'From (ppm) :',
+ checkFieldName: 'floatingTablePreferences.from.show',
+ formatFieldName: 'floatingTablePreferences.from.format',
+ },
+ {
+ id: 4,
+ label: 'To (ppm) :',
+ checkFieldName: 'floatingTablePreferences.to.show',
+ formatFieldName: 'floatingTablePreferences.to.format',
+ },
+ {
+ id: 5,
+ label: 'Absolute integration :',
+ checkFieldName: 'floatingTablePreferences.absolute.show',
+ formatFieldName: 'floatingTablePreferences.absolute.format',
+ },
+ {
+ id: 6,
+ label: 'Relative integration :',
+ checkFieldName: 'floatingTablePreferences.relative.show',
+ formatFieldName: 'floatingTablePreferences.relative.format',
+ },
+ {
+ id: 7,
+ label: 'δ (ppm) :',
+ checkFieldName: 'floatingTablePreferences.deltaPPM.show',
+ formatFieldName: 'floatingTablePreferences.deltaPPM.format',
+ },
+ {
+ id: 8,
+ label: 'δ (Hz) :',
+ checkFieldName: 'floatingTablePreferences.deltaHz.show',
+ formatFieldName: 'floatingTablePreferences.deltaHz.format',
+ },
+ {
+ id: 9,
+ label: 'Coupling (Hz) :',
+ checkFieldName: 'floatingTablePreferences.coupling.show',
+ formatFieldName: 'floatingTablePreferences.coupling.format',
+ },
+ {
+ id: 10,
+ label: 'Kind :',
+ checkFieldName: 'floatingTablePreferences.showKind',
+ hideFormatField: true,
+ },
+ {
+ id: 11,
+ label: 'Multiplicity :',
+ checkFieldName: 'floatingTablePreferences.showMultiplicity',
+ hideFormatField: true,
+ },
+ {
+ id: 12,
+ label: 'Assignment :',
+ checkFieldName: 'floatingTablePreferences.showAssignment',
+ hideFormatField: true,
+ },
+];
+
+interface InnerFloatingRangeTablePreferencesModalProps {
+ onCloseDialog: () => void;
+}
+
+interface FloatingRangeTablePreferencesModalProps extends InnerFloatingRangeTablePreferencesModalProps {
+ isOpen: boolean;
+}
+
+export function FloatingRangeTablePreferencesModal(
+ props: FloatingRangeTablePreferencesModalProps,
+) {
+ const { onCloseDialog, isOpen } = props;
+
+ if (!isOpen) return;
+
+ return (
+
+
+
+ );
+}
+
+function InnerFloatingRangeTablePreferencesModal(
+ props: InnerFloatingRangeTablePreferencesModalProps,
+) {
+ const { onCloseDialog } = props;
+ const preferences = usePreferences();
+ const nucleus = useNucleus();
+ const nuclei = useMemo(() => getUniqueNuclei(nucleus), [nucleus]);
+ const preferencesByNuclei = usePanelPreferencesByNuclei('ranges', nuclei);
+ const { handleSubmit, control } = useForm({
+ defaultValues: preferencesByNuclei,
+ });
+ function saveHandler() {
+ void handleSubmit((values) => {
+ preferences.dispatch({
+ type: 'SET_PANELS_PREFERENCES',
+ payload: { key: 'ranges', value: values },
+ });
+ onCloseDialog();
+ })();
+ }
+
+ return (
+ <>
+
+ {nuclei?.map((n) => (
+ (
+
+ )}
+ />
+ ))}
+
+
+
+
+
+
+ >
+ );
+}
+
+interface SignalKindFilterProps {
+ control: Control;
+ nucleus: string;
+}
+
+function SignalKindFilter({ control, nucleus }: SignalKindFilterProps) {
+ const { field } = useController({
+ control,
+ name: `nuclei.${nucleus}.floatingTablePreferences.signalKinds`,
+ defaultValue: [],
+ });
+
+ const selectedKinds: SignalKind[] = field.value ?? [];
+
+ function handleSelect(item: SignalKindItem) {
+ const already = selectedKinds.includes(item.value);
+ field.onChange(
+ already
+ ? selectedKinds.filter((k) => k !== item.value)
+ : [...selectedKinds, item.value],
+ );
+ }
+
+ function handleRemove(item: SignalKindItem, index: number) {
+ field.onChange(selectedKinds.filter((_, i) => i !== index));
+ }
+
+ function handleClear() {
+ field.onChange([]);
+ }
+
+ const selectedItems = SIGNAL_KINDS.filter((k) =>
+ selectedKinds.includes(k.value),
+ );
+
+ return (
+
+ );
+}
diff --git a/src/component/1d/ranges/FloatingRanges.tsx b/src/component/1d/ranges/FloatingRanges.tsx
new file mode 100644
index 0000000000..620607455b
--- /dev/null
+++ b/src/component/1d/ranges/FloatingRanges.tsx
@@ -0,0 +1,529 @@
+import styled from '@emotion/styled';
+import type { Info1D, Range, Ranges, Signal1D } from '@zakodium/nmr-types';
+import type {
+ BaseRangesTablePreferences,
+ BoundingBox,
+ FloatingRangesTablePreferences,
+} from '@zakodium/nmrium-core';
+import { checkMultiplicity } from 'nmr-processing';
+import { memo, useEffect, useMemo, useState } from 'react';
+import { BsArrowsMove } from 'react-icons/bs';
+import { FaCog, FaLink, FaTimes } from 'react-icons/fa';
+import { Rnd } from 'react-rnd';
+
+import { isSpectrum1D } from '../../../data/data1d/Spectrum1D/index.js';
+import { isSignalRange } from '../../../data/utilities/RangeUtilities.js';
+import type { SVGTableColumn } from '../../SVGTable.js';
+import { SVGTable } from '../../SVGTable.js';
+import { useChartData } from '../../context/ChartContext.js';
+import { useDispatch } from '../../context/DispatchContext.js';
+import { useGlobal } from '../../context/GlobalContext.js';
+import type { ActionsButtonsPopoverProps } from '../../elements/ActionsButtonsPopover.js';
+import { ActionsButtonsPopover } from '../../elements/ActionsButtonsPopover.js';
+import { useActiveNucleusTab } from '../../hooks/useActiveNucleusTab.js';
+import { useDialogToggle } from '../../hooks/useDialogToggle.ts';
+import { usePanelPreferences } from '../../hooks/usePanelPreferences.js';
+import { useSVGUnitConverter } from '../../hooks/useSVGUnitConverter.js';
+import useSpectraByActiveNucleus from '../../hooks/useSpectraPerNucleus.js';
+import { useCheckExportStatus } from '../../hooks/useViewportSize.js';
+import { extractChemicalElement } from '../../utility/extractChemicalElement.js';
+import { formatNumber } from '../../utility/formatNumber.js';
+
+import { FloatingRangeTablePreferencesModal } from './FloatingRangeTablePreferencesModal.tsx';
+
+const ReactRnd = styled(Rnd)`
+ border: 1px solid transparent;
+
+ :hover {
+ background-color: white;
+ border: 1px solid #ebecf1;
+
+ button {
+ visibility: visible;
+ }
+ }
+`;
+interface SpectrumRangesInfo {
+ ranges: Ranges['values'];
+ info: Info1D;
+}
+
+interface RangesTableProps {
+ data: SpectrumRangesInfo;
+}
+
+interface RangeItem {
+ id: string;
+ delta: string;
+ deltaHz: string;
+ multiplicity: string;
+ integration: string;
+ coupling: string;
+ from: string;
+ to: string;
+ absolute: string;
+ kind: string;
+ assignment: string;
+ nbAssignment: string;
+}
+
+function buildRangeBaseItem(
+ range: Range,
+ info: Info1D,
+ preferences: FloatingRangesTablePreferences,
+) {
+ const { id, from, to, integration, absolute } = range;
+ const formatHz = preferences.deltaHz.format;
+ return {
+ id,
+ from: formatNumber(from, preferences.from.format),
+ to: formatNumber(to, preferences.to.format),
+ absolute: formatNumber(absolute, preferences.absolute.format),
+ integration: isSignalRange(range)
+ ? formatNumber(integration, preferences.relative.format)
+ : `[ ${formatNumber(integration, preferences.relative.format)} ]`,
+ deltaText: `${formatNumber(from, preferences.from.format)} - ${formatNumber(to, preferences.to.format)}`,
+ deltaHzText: `${formatNumber(from * info.originFrequency, formatHz)} - ${formatNumber(to * info.originFrequency, formatHz)}`,
+ formatHz,
+ };
+}
+
+function buildSignalItem(
+ signal: Signal1D,
+ base: ReturnType,
+ info: Info1D,
+ preferences: FloatingRangesTablePreferences,
+): RangeItem {
+ const {
+ multiplicity,
+ delta,
+ kind = '',
+ assignment = '',
+ js = [],
+ diaIDs,
+ } = signal;
+ const isNotMultiplet = !checkMultiplicity(multiplicity, ['m']);
+ return {
+ id: base.id,
+ from: base.from,
+ to: base.to,
+ absolute: base.absolute,
+ integration: base.integration,
+ multiplicity,
+ kind,
+ assignment,
+ coupling: js
+ .map((j) =>
+ !Number.isNaN(j.coupling)
+ ? formatNumber(j.coupling, preferences.coupling.format)
+ : '',
+ )
+ .join(','),
+ delta: isNotMultiplet
+ ? base.deltaText
+ : formatNumber(delta, preferences.deltaPPM.format),
+ deltaHz: isNotMultiplet
+ ? base.deltaHzText
+ : info?.originFrequency
+ ? formatNumber(delta * info.originFrequency, base.formatHz)
+ : '',
+ nbAssignment: diaIDs?.length ? String(diaIDs.length) : '',
+ };
+}
+
+function useMapRanges(data: SpectrumRangesInfo): RangeItem[] {
+ const { ranges, info } = data;
+ const activeTab = useActiveNucleusTab();
+ const { floatingTablePreferences: preferences } = usePanelPreferences(
+ 'ranges',
+ activeTab,
+ );
+
+ return ranges.flatMap((range) => {
+ const base = buildRangeBaseItem(range, info, preferences);
+ const { signals = [] } = range;
+
+ if (signals.length === 0) {
+ return [
+ {
+ ...base,
+ delta: base.deltaText,
+ deltaHz: '',
+ multiplicity: 'm',
+ coupling: '',
+ kind: '',
+ assignment: '',
+ nbAssignment: '',
+ },
+ ];
+ }
+
+ const result = signals.map((signal) =>
+ buildSignalItem(signal, base, info, preferences),
+ );
+ if (preferences.signalKinds.length === 0) {
+ return result;
+ }
+
+ return result.filter((signal) =>
+ preferences.signalKinds.includes(signal.kind || ''),
+ );
+ });
+}
+
+function isColumnVisible(
+ pref: BaseRangesTablePreferences[keyof BaseRangesTablePreferences],
+): boolean {
+ if (typeof pref === 'boolean') return pref;
+ return pref?.show;
+}
+
+function InnerSVGRangesTable(props: RangesTableProps) {
+ const { data } = props;
+ const {
+ view: {
+ spectra: { activeTab },
+ },
+ } = useChartData();
+ const rangesData = useMapRanges(data);
+ const { floatingTablePreferences } = usePanelPreferences('ranges', activeTab);
+
+ const element = extractChemicalElement(activeTab);
+
+ const columns = useMemo((): Array> => {
+ const allColumns: Array<
+ { prefKey: keyof BaseRangesTablePreferences } & SVGTableColumn
+ > = [
+ {
+ prefKey: 'showSerialNumber',
+ accessorFun: (row) => row.index + 1,
+ header: '#',
+ width: 40,
+ },
+ {
+ prefKey: 'showAssignmentLabel',
+ accessorKey: 'assignment',
+ header: 'Assignment',
+ width: 120,
+ },
+ {
+ prefKey: 'deltaPPM',
+ accessorKey: 'delta',
+ header: 'δ (ppm)',
+ width: 100,
+ rowSpanGroupKey: 'id',
+ },
+ {
+ prefKey: 'deltaHz',
+ accessorKey: 'deltaHz',
+ header: 'δ (Hz)',
+ width: 110,
+ rowSpanGroupKey: 'id',
+ },
+ {
+ prefKey: 'from',
+ accessorKey: 'from',
+ header: `From`,
+ width: 60,
+ },
+ {
+ prefKey: 'to',
+ accessorKey: 'to',
+ header: `To`,
+ width: 60,
+ },
+ {
+ prefKey: 'relative',
+ accessorKey: 'integration',
+ header: `Rel. ${element}`,
+ width: 60,
+ rowSpanGroupKey: 'id',
+ },
+ {
+ prefKey: 'absolute',
+ accessorKey: 'absolute',
+ header: 'Absolute',
+ width: 80,
+ rowSpanGroupKey: 'id',
+ },
+ {
+ prefKey: 'showMultiplicity',
+ accessorKey: 'multiplicity',
+ header: 'Mult.',
+ width: 60,
+ rowSpanGroupKey: 'id',
+ },
+ {
+ prefKey: 'coupling',
+ accessorKey: 'coupling',
+ header: 'J (Hz)',
+ width: 120,
+ },
+
+ {
+ prefKey: 'showAssignment',
+ accessorKey: 'nbAssignment',
+ header: ,
+ width: 40,
+ },
+ {
+ prefKey: 'showKind',
+ accessorKey: 'kind',
+ header: 'Kind',
+ width: 120,
+ },
+ ];
+
+ return allColumns
+ .filter(({ prefKey }) =>
+ isColumnVisible(floatingTablePreferences[prefKey]),
+ )
+ .map(({ prefKey: _, ...col }) => ({
+ ...col,
+ headerTextProps: { fontWeight: 'bold' },
+ cellBoxProps: { stroke: '#dedede', fill: 'white', fillOpacity: 0.8 },
+ headerBoxProps: { stroke: '#dedede', fill: '#E5E8EB' },
+ }));
+ }, [floatingTablePreferences, element]);
+ if (!data) return null;
+
+ return data={rangesData} columns={columns} />;
+}
+
+const SVGRangesTable = memo(InnerSVGRangesTable);
+
+interface DraggablePublicationStringProps {
+ data: SpectrumRangesInfo | undefined;
+ bonding: BoundingBox;
+ spectrumKey: string;
+}
+
+function DraggableRanges(props: DraggablePublicationStringProps) {
+ const { data, bonding: externalBounding, spectrumKey } = props;
+ const dispatch = useDispatch();
+ const { viewerRef } = useGlobal();
+ const [bounding, setBounding] = useState(externalBounding);
+ const [isMoveActive, setIsMoveActive] = useState(false);
+ const { percentToPixel, pixelToPercent } = useSVGUnitConverter();
+ const isExportProcessStart = useCheckExportStatus();
+ const { dialog, closeDialog, openDialog } = useDialogToggle({
+ settingModal: false,
+ });
+
+ useEffect(() => {
+ setBounding({ ...externalBounding });
+ }, [externalBounding]);
+
+ function handleResize(
+ internalBounding: Pick,
+ ) {
+ const { width = 0, height = 0 } = convertToPixel(externalBounding);
+ internalBounding.width += width;
+ internalBounding.height += height;
+ setBounding((prevBounding) => ({
+ ...prevBounding,
+ ...convertToPercent(internalBounding),
+ }));
+ }
+
+ function handleDrag(internalBounding: Pick) {
+ setBounding((prevBounding) => ({
+ ...prevBounding,
+ ...convertToPercent(internalBounding),
+ }));
+ }
+ function handleChangeInsetBounding(bounding: Partial) {
+ if (
+ typeof bounding?.width === 'number' &&
+ typeof bounding?.height === 'number'
+ ) {
+ const { width, height } = externalBounding;
+ bounding.width += width;
+ bounding.height += height;
+ }
+
+ dispatch({
+ type: 'CHANGE_RANGES_VIEW_FLOATING_BOX_BOUNDING',
+ payload: {
+ spectrumKey,
+ bounding: convertToPercent(bounding),
+ target: 'rangesBounding',
+ },
+ });
+ }
+
+ function convertToPixel(bounding: Partial) {
+ const { x, y, height, width } = bounding;
+ const output: Partial = {};
+
+ if (x) {
+ output.x = percentToPixel(x, 'x');
+ }
+ if (y) {
+ output.y = percentToPixel(y, 'y');
+ }
+ if (width) {
+ output.width = width;
+ }
+ if (height) {
+ output.height = height;
+ }
+
+ return output;
+ }
+ function convertToPercent(bounding: Partial) {
+ const { x, y, height, width } = bounding;
+ const output: Partial = {};
+
+ if (x) {
+ output.x = pixelToPercent(x, 'x');
+ }
+ if (y) {
+ output.y = pixelToPercent(y, 'y');
+ }
+ if (width) {
+ output.width = width;
+ }
+ if (height) {
+ output.height = height;
+ }
+
+ return output;
+ }
+
+ function handleRemove() {
+ dispatch({
+ type: 'TOGGLE_RANGES_VIEW_PROPERTY',
+ payload: { key: 'showRanges', spectrumKey },
+ });
+ }
+
+ const actionButtons: ActionsButtonsPopoverProps['buttons'] = [
+ {
+ icon: ,
+ intent: 'none',
+ title: 'Move ranges table',
+ style: { cursor: 'move' },
+ className: 'handle',
+ },
+ {
+ icon: ,
+ intent: 'none',
+ title: 'Table preferences',
+ onClick: () => {
+ openDialog('settingModal');
+ },
+ },
+ {
+ icon: ,
+ intent: 'danger',
+ title: 'Hide ranges table',
+ onClick: handleRemove,
+ },
+ ];
+ if (!viewerRef || !data || data?.ranges?.length === 0) return null;
+
+ const { x: xInPercent, y: yInPercent } = bounding;
+
+ const x = percentToPixel(xInPercent, 'x');
+ const y = percentToPixel(yInPercent, 'y');
+
+ if (isExportProcessStart) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+ <>
+
+ setIsMoveActive(true)}
+ onResize={(e, dir, eRef, size, position) =>
+ handleResize({ ...size, ...position })
+ }
+ onResizeStop={(e, dir, eRef, size, position) =>
+ handleChangeInsetBounding({ ...size, ...position })
+ }
+ onDrag={(e, { x, y }) => {
+ handleDrag({ x, y });
+ }}
+ onDragStop={(e, { x, y }) => {
+ handleChangeInsetBounding({ x, y });
+ setIsMoveActive(false);
+ }}
+ resizeHandleWrapperStyle={{ backgroundColor: 'white' }}
+ >
+
+
+
+
+ >
+ );
+}
+
+function useSpectraRanges() {
+ const spectra = useSpectraByActiveNucleus();
+ const output: Record = {};
+
+ for (const spectrum of spectra) {
+ if (!isSpectrum1D(spectrum)) {
+ continue;
+ }
+ const { id: spectrumKey, ranges, info } = spectrum;
+
+ if (!Array.isArray(ranges?.values) || ranges.values.length === 0) {
+ continue;
+ }
+
+ output[spectrumKey] = { ranges: ranges.values, info };
+ }
+
+ return output;
+}
+
+export function FloatingRanges() {
+ const spectraRanges = useSpectraRanges();
+ const {
+ view: { ranges },
+ } = useChartData();
+
+ const options = Object.entries(ranges);
+
+ return options.map(([spectrumKey, viewOptions]) => {
+ const { showRanges, rangesBounding } = viewOptions;
+ if (!showRanges) return null;
+
+ return (
+
+ );
+ });
+}
diff --git a/src/component/1d/ranges/Ranges.tsx b/src/component/1d/ranges/Ranges.tsx
index 3f9258c681..6a7e2f8cf9 100644
--- a/src/component/1d/ranges/Ranges.tsx
+++ b/src/component/1d/ranges/Ranges.tsx
@@ -100,7 +100,7 @@ export default function Ranges() {
toolOptions: { selectedTool },
} = useChartData();
const spectrum = useSpectrum(emptyData) as Spectrum1D;
- const rangesPreferences = usePanelPreferences('ranges', activeTab);
+ const { tablePreferences } = usePanelPreferences('ranges', activeTab);
if (
!spectrum.ranges?.values ||
@@ -114,7 +114,7 @@ export default function Ranges() {
);
}
diff --git a/src/component/SVGTable.tsx b/src/component/SVGTable.tsx
index 2d7af78e0b..397eb850c7 100644
--- a/src/component/SVGTable.tsx
+++ b/src/component/SVGTable.tsx
@@ -1,5 +1,5 @@
-import type { SVGAttributes } from 'react';
-import { createContext, useContext, useMemo } from 'react';
+import type { ReactElement, SVGAttributes } from 'react';
+import { createContext, isValidElement, useContext, useMemo } from 'react';
type CellTextProps = Omit<
SVGAttributes,
@@ -15,7 +15,7 @@ interface StyleCell {
cellBoxProps?: CellBoxProps;
}
interface BaseSVGTableColumn extends StyleCell {
- header: string;
+ header: string | ReactElement;
width: number;
minWidth?: number;
rowSpanGroupKey?: keyof T;
@@ -27,7 +27,7 @@ interface AccessorKeyColumn extends BaseSVGTableColumn {
}
interface AccessorFuncColumn extends BaseSVGTableColumn {
- accessorFun: (row: T) => number | string;
+ accessorFun: (row: T & { index: number }) => number | string;
}
export type SVGTableColumn = AccessorKeyColumn | AccessorFuncColumn;
@@ -69,14 +69,14 @@ function mapColumns(columns: Array>) {
}
interface FormatKeyOptions {
- rowIndex: number;
+ index: number;
columnKey: string;
groupKey: string;
}
function formatKey(options: FormatKeyOptions) {
- const { rowIndex, columnKey, groupKey } = options;
- return `GroupKey[${groupKey || null}]-ColumnKey[${columnKey}]-RowIndex[${rowIndex}]`;
+ const { index, columnKey, groupKey } = options;
+ return `GroupKey[${groupKey || null}]-ColumnKey[${columnKey}]-RowIndex[${index}]`;
}
function mapRowsSpan(data: T[], columns: Array>) {
@@ -89,14 +89,14 @@ function mapRowsSpan(data: T[], columns: Array>) {
let lastRowIndex = 0;
let lastGroupKey: string | null = null;
- for (let rowIndex = 0; rowIndex < data.length; rowIndex++) {
- const row = data[rowIndex];
+ for (let index = 0; index < data.length; index++) {
+ const row = data[index];
const groupKey = String(row?.[col.rowSpanGroupKey] || '');
const key = col._columnOptions.key;
const value = isAccessorKeyColumn(col)
? (row as any)[col.accessorKey]
- : col.accessorFun(row);
+ : col.accessorFun({ ...row, index });
if (
value === lastValue &&
@@ -107,16 +107,16 @@ function mapRowsSpan(data: T[], columns: Array>) {
const prevKey = formatKey({
groupKey,
columnKey: key,
- rowIndex: lastRowIndex,
+ index: lastRowIndex,
});
rowSpanMap.set(prevKey, (rowSpanMap.get(prevKey) || 1) + 1);
- skipColumns.add(formatKey({ groupKey, columnKey: key, rowIndex }));
+ skipColumns.add(formatKey({ groupKey, columnKey: key, index }));
} else {
// Start a new rowspan
lastValue = value;
- lastRowIndex = rowIndex;
+ lastRowIndex = index;
lastGroupKey = groupKey;
- rowSpanMap.set(formatKey({ groupKey, columnKey: key, rowIndex }), 1);
+ rowSpanMap.set(formatKey({ groupKey, columnKey: key, index }), 1);
}
}
}
@@ -170,11 +170,11 @@ export function SVGTable(props: SVGTableProps) {
);
})}
- {data.map((row, rowIndex) => {
+ {data.map((row, index) => {
return (
{columns.map((col) => {
const {
@@ -187,7 +187,7 @@ export function SVGTable(props: SVGTableProps) {
const cellKey = formatKey({
groupKey: String(groupKey),
columnKey,
- rowIndex,
+ index,
});
if (skipColumns.has(cellKey)) {
return null; // Skip merged row
@@ -199,7 +199,7 @@ export function SVGTable(props: SVGTableProps) {
extends StyleCell {
}
interface ColumnProps extends BaseColumnProps {
- value: string | number;
+ value: string | number | ReactElement;
}
interface ValueColumnProps extends BaseColumnProps {
- row: T;
+ row: T & { index: number };
}
function ValueColumn(props: ValueColumnProps) {
@@ -250,10 +250,8 @@ function Column(props: ColumnProps) {
const { rowHeight } = tableOptions;
return (
-
+
(props: ColumnProps) {
strokeWidth="1"
{...cellBoxProps}
/>
-
- {value}
-
+ {isValidElement(value) ? (
+ value
+ ) : (
+
+ {value}
+
+ )}
);
}
diff --git a/src/component/main/NMRiumViewer.tsx b/src/component/main/NMRiumViewer.tsx
index 303ebfa8fd..5795f34ad7 100644
--- a/src/component/main/NMRiumViewer.tsx
+++ b/src/component/main/NMRiumViewer.tsx
@@ -2,9 +2,9 @@ import type { CSSProperties, RefObject } from 'react';
import { useDeferredValue, useEffect } from 'react';
import { FloatPublicationString } from '../1d/FloatPublicationString.js';
-import { FloatingRanges } from '../1d/FloatingRanges.js';
import { Viewer1D } from '../1d/Viewer1D.js';
import { SpectraInsets } from '../1d/inset/SpectraInsets.js';
+import { FloatingRanges } from '../1d/ranges/FloatingRanges.tsx';
import { FloatMolecules } from '../1d-2d/components/FloatMoleculeStructures/FloatMolecules.js';
import { SVGRootContainer } from '../1d-2d/components/SVGRootContainer.js';
import Viewer2D from '../2d/Viewer2D.js';
diff --git a/src/component/modal/EditPeakShapeModal.tsx b/src/component/modal/EditPeakShapeModal.tsx
index d0fd7e9abb..7f8f319697 100644
--- a/src/component/modal/EditPeakShapeModal.tsx
+++ b/src/component/modal/EditPeakShapeModal.tsx
@@ -97,7 +97,7 @@ function InnerEditPeakShapeModal(props: Required) {
const { peak, onCloseDialog } = props;
const dispatch = useDispatch();
const activeTab = useActiveNucleusTab();
- const peaksPreferences = usePanelPreferences('peaks', activeTab);
+ const { tablePreferences } = usePanelPreferences('peaks', activeTab);
const [kind, setKind] = useState(peak.shape?.kind || 'gaussian');
const { handleSubmit, control, reset } = useForm({
@@ -125,7 +125,7 @@ function InnerEditPeakShapeModal(props: Required) {
setKind(value);
}
- const valuePPM = formatNumber(peak.x, peaksPreferences.deltaPPM.format);
+ const valuePPM = formatNumber(peak.x, tablePreferences.deltaPPM.format);
return (
diff --git a/src/component/modal/editRange/forms/components/NewSignalTab.tsx b/src/component/modal/editRange/forms/components/NewSignalTab.tsx
index acecf27469..3d728c32bd 100644
--- a/src/component/modal/editRange/forms/components/NewSignalTab.tsx
+++ b/src/component/modal/editRange/forms/components/NewSignalTab.tsx
@@ -42,7 +42,7 @@ export function NewSignalTab(props: NewSignalTabProps) {
const { selectedTabId: signalIndex, selectTab } = useTabsController();
const { signals } = useWatch();
const activeTab = useActiveNucleusTab();
- const rangesPreferences = usePanelPreferences('ranges', activeTab);
+ const { tablePreferences } = usePanelPreferences('ranges', activeTab);
function saveHandler(val: any) {
const newSignal = {
@@ -95,8 +95,8 @@ export function NewSignalTab(props: NewSignalTabProps) {
Edit or select a delta value of new signal in range [
{`${formatNumber(
range.from,
- rangesPreferences.from.format,
- )} ppm - ${formatNumber(range.to, rangesPreferences.to.format)} ppm`}
+ tablePreferences.from.format,
+ )} ppm - ${formatNumber(range.to, tablePreferences.to.format)} ppm`}
]:
();
const deletePeakHandler = useCallback(
@@ -64,7 +64,7 @@ export function usePeaksTableColumns(activeTab: string) {
saveDeltaPPMRefsHandler(value, row.original)}
type="number"
@@ -78,7 +78,7 @@ export function usePeaksTableColumns(activeTab: string) {
Header: 'δ (Hz)',
accessor: 'xHz',
Cell: ({ row }: CellProps) =>
- formatNumber(row.original.xHz, peaksPreferences.deltaHz.format),
+ formatNumber(row.original.xHz, tablePreferences.deltaHz.format),
},
{
showWhen: 'intensity.show',
@@ -87,7 +87,7 @@ export function usePeaksTableColumns(activeTab: string) {
style: { maxWidth: '80px' },
accessor: 'y',
Cell: ({ row }: CellProps) =>
- formatNumber(row.original.y, peaksPreferences.intensity.format),
+ formatNumber(row.original.y, tablePreferences.intensity.format),
},
{
showWhen: 'peakWidth.show',
@@ -95,7 +95,7 @@ export function usePeaksTableColumns(activeTab: string) {
Header: 'Width (Hz)',
accessor: 'width',
Cell: ({ row }: CellProps) =>
- formatNumber(row.original.width, peaksPreferences.peakWidth.format),
+ formatNumber(row.original.width, tablePreferences.peakWidth.format),
},
{
showWhen: 'showKind',
@@ -111,7 +111,7 @@ export function usePeaksTableColumns(activeTab: string) {
Cell: ({ row }: CellProps) => {
const fwhm = row.original?.shape?.fwhm;
if (fwhm) {
- return formatNumber(fwhm, peaksPreferences.fwhm.format);
+ return formatNumber(fwhm, tablePreferences.fwhm.format);
}
return '';
},
@@ -127,7 +127,7 @@ export function usePeaksTableColumns(activeTab: string) {
row.original?.shape?.kind === 'pseudoVoigt' &&
row.original?.shape?.mu;
if (mu) {
- return formatNumber(mu, peaksPreferences.mu.format);
+ return formatNumber(mu, tablePreferences.mu.format);
}
return '';
},
@@ -144,7 +144,7 @@ export function usePeaksTableColumns(activeTab: string) {
row.original?.shape?.kind === 'generalizedLorentzian' &&
row.original?.shape?.gamma;
if (gamma) {
- return formatNumber(gamma, peaksPreferences.gamma.format);
+ return formatNumber(gamma, tablePreferences.gamma.format);
}
return '';
},
@@ -170,7 +170,7 @@ export function usePeaksTableColumns(activeTab: string) {
},
],
[
- peaksPreferences,
+ tablePreferences,
saveDeltaPPMRefsHandler,
deletePeakHandler,
editPeakHandler,
@@ -181,14 +181,14 @@ export function usePeaksTableColumns(activeTab: string) {
const columns: Array> = [];
for (const col of COLUMNS) {
const { showWhen, ...colParams } = col;
- if (dlv(peaksPreferences, showWhen)) {
+ if (dlv(tablePreferences, showWhen)) {
addCustomColumn(columns, colParams);
}
}
columns.sort((object1, object2) => object1.index - object2.index);
return columns;
- }, [COLUMNS, peaksPreferences]);
+ }, [COLUMNS, tablePreferences]);
return { tableColumns, peak, setEditedPeak };
}
diff --git a/src/component/panels/RangesPanel/RangesPreferences.tsx b/src/component/panels/RangesPanel/RangesPreferences.tsx
index 85e2c09a5c..90f80dca1e 100644
--- a/src/component/panels/RangesPanel/RangesPreferences.tsx
+++ b/src/component/panels/RangesPanel/RangesPreferences.tsx
@@ -18,91 +18,91 @@ const formatFields: NucleusPreferenceField[] = [
{
id: 1,
label: 'Serial number :',
- checkFieldName: 'showSerialNumber',
+ checkFieldName: 'tablePreferences.showSerialNumber',
hideFormatField: true,
},
{
id: 2,
label: 'Assignment label :',
- checkFieldName: 'showAssignmentLabel',
+ checkFieldName: 'tablePreferences.showAssignmentLabel',
hideFormatField: true,
},
{
id: 3,
label: 'From (ppm) :',
- checkFieldName: 'from.show',
- formatFieldName: 'from.format',
+ checkFieldName: 'tablePreferences.from.show',
+ formatFieldName: 'tablePreferences.from.format',
},
{
id: 4,
label: 'To (ppm) :',
- checkFieldName: 'to.show',
- formatFieldName: 'to.format',
+ checkFieldName: 'tablePreferences.to.show',
+ formatFieldName: 'tablePreferences.to.format',
},
{
id: 5,
label: 'Absolute integration :',
- checkFieldName: 'absolute.show',
- formatFieldName: 'absolute.format',
+ checkFieldName: 'tablePreferences.absolute.show',
+ formatFieldName: 'tablePreferences.absolute.format',
},
{
id: 6,
label: 'Relative integration :',
- checkFieldName: 'relative.show',
- formatFieldName: 'relative.format',
+ checkFieldName: 'tablePreferences.relative.show',
+ formatFieldName: 'tablePreferences.relative.format',
},
{
id: 7,
label: 'δ (ppm) :',
- checkFieldName: 'deltaPPM.show',
- formatFieldName: 'deltaPPM.format',
+ checkFieldName: 'tablePreferences.deltaPPM.show',
+ formatFieldName: 'tablePreferences.deltaPPM.format',
},
{
id: 8,
label: 'δ (Hz) :',
- checkFieldName: 'deltaHz.show',
- formatFieldName: 'deltaHz.format',
+ checkFieldName: 'tablePreferences.deltaHz.show',
+ formatFieldName: 'tablePreferences.deltaHz.format',
},
{
id: 9,
label: 'Coupling (Hz) :',
- checkFieldName: 'coupling.show',
- formatFieldName: 'coupling.format',
+ checkFieldName: 'tablePreferences.coupling.show',
+ formatFieldName: 'tablePreferences.coupling.format',
},
{
id: 10,
label: 'Kind :',
- checkFieldName: 'showKind',
+ checkFieldName: 'tablePreferences.showKind',
hideFormatField: true,
},
{
id: 11,
label: 'Multiplicity :',
- checkFieldName: 'showMultiplicity',
+ checkFieldName: 'tablePreferences.showMultiplicity',
hideFormatField: true,
},
{
id: 12,
label: 'Assignment links :',
- checkFieldName: 'showAssignment',
+ checkFieldName: 'tablePreferences.showAssignment',
hideFormatField: true,
},
{
id: 13,
label: 'Delete action :',
- checkFieldName: 'showDeleteAction',
+ checkFieldName: 'tablePreferences.showDeleteAction',
hideFormatField: true,
},
{
id: 14,
label: 'Zoom action :',
- checkFieldName: 'showZoomAction',
+ checkFieldName: 'tablePreferences.showZoomAction',
hideFormatField: true,
},
{
id: 15,
label: 'Edit action :',
- checkFieldName: 'showEditAction',
+ checkFieldName: 'tablePreferences.showEditAction',
hideFormatField: true,
},
];
diff --git a/src/component/panels/RangesPanel/RangesTable.tsx b/src/component/panels/RangesPanel/RangesTable.tsx
index 25a43bcc2a..022ed517d2 100644
--- a/src/component/panels/RangesPanel/RangesTable.tsx
+++ b/src/component/panels/RangesPanel/RangesTable.tsx
@@ -79,6 +79,8 @@ export default function RangesTable(props: RangesTableProps) {
info,
} = props;
+ const { tablePreferences } = preferences;
+
const element = extractChemicalElement(activeTab);
const { items: sortedData, isSortedDesc, onSort } = useTableSortBy(tableData);
const data = useMapRanges(sortedData);
@@ -92,9 +94,9 @@ export default function RangesTable(props: RangesTableProps) {
}
const showActions =
- preferences.showDeleteAction ||
- preferences.showEditAction ||
- preferences.showZoomAction;
+ tablePreferences.showDeleteAction ||
+ tablePreferences.showEditAction ||
+ tablePreferences.showZoomAction;
return (
<>
@@ -102,43 +104,43 @@ export default function RangesTable(props: RangesTableProps) {
- {preferences.showSerialNumber && | # | }
- {preferences.showAssignmentLabel && (
+ {tablePreferences.showSerialNumber && # | }
+ {tablePreferences.showAssignmentLabel && (
Assignment |
)}
- {preferences.from.show && (
+ {tablePreferences.from.show && (
From
{isSortedDesc('from').content}
|
)}
- {preferences.to.show && (
+ {tablePreferences.to.show && (
To {isSortedDesc('to').content}
|
)}
- {preferences.deltaPPM.show && (
+ {tablePreferences.deltaPPM.show && (
δ (ppm) {isSortedDesc('from').content}
|
)}
- {preferences.deltaHz.show && δ (Hz) | }
+ {tablePreferences.deltaHz.show && δ (Hz) | }
- {preferences.relative.show && (
+ {tablePreferences.relative.show && (
Rel. {element} {isSortedDesc('integration').content}
|
)}
- {preferences.absolute.show && Absolute | }
- {preferences.showMultiplicity && Mult. | }
- {preferences.coupling.show && J (Hz) | }
+ {tablePreferences.absolute.show && Absolute | }
+ {tablePreferences.showMultiplicity && Mult. | }
+ {tablePreferences.coupling.show && J (Hz) | }
- {preferences.showAssignment && (
+ {tablePreferences.showAssignment && (
|
)}
- {preferences.showKind && Kind | }
+ {tablePreferences.showKind && Kind | }
{showActions && {''} | }
diff --git a/src/component/panels/RangesPanel/RangesTableRow.tsx b/src/component/panels/RangesPanel/RangesTableRow.tsx
index f11ac95a5a..01471d5a01 100644
--- a/src/component/panels/RangesPanel/RangesTableRow.tsx
+++ b/src/component/panels/RangesPanel/RangesTableRow.tsx
@@ -67,7 +67,7 @@ export default function RangesTableRow(props: RangesTableRowProps) {
rowData,
onContextMenuSelect,
contextMenu = [],
- preferences,
+ preferences: { tablePreferences },
info,
} = props;
const dispatch = useDispatch();
@@ -181,12 +181,12 @@ export default function RangesTableRow(props: RangesTableRowProps) {
as="tr"
style={trStyle}
>
- {preferences.showSerialNumber && (
+ {tablePreferences.showSerialNumber && (
{rowData.tableMetaInfo.rowIndex + 1}
|
)}
- {preferences.showAssignmentLabel && (
+ {tablePreferences.showAssignmentLabel && (
)}
- {preferences.from.show && (
+ {tablePreferences.from.show && (
)}
- {preferences.to.show && (
+ {tablePreferences.to.show && (
)}
- {preferences.deltaPPM.show && (
+ {tablePreferences.deltaPPM.show && (
)}
- {preferences.deltaHz.show && (
+ {tablePreferences.deltaHz.show && (
)}
- {preferences.relative.show && (
+ {tablePreferences.relative.show && (
)}
- {preferences.absolute.show && (
+ {tablePreferences.absolute.show && (
)}
- {preferences.showMultiplicity && (
+ {tablePreferences.showMultiplicity && (
{rowData.tableMetaInfo.signal?.multiplicity ?? 'm'}
|
)}
- {preferences.coupling.show && (
+ {tablePreferences.coupling.show && (
)}
- {preferences.showAssignment && (
+ {tablePreferences.showAssignment && (
);
diff --git a/src/component/reducer/preferences/panelsPreferencesDefaultValues.ts b/src/component/reducer/preferences/panelsPreferencesDefaultValues.ts
index 4bb5f7d31e..ae1a8e3eac 100644
--- a/src/component/reducer/preferences/panelsPreferencesDefaultValues.ts
+++ b/src/component/reducer/preferences/panelsPreferencesDefaultValues.ts
@@ -1,10 +1,12 @@
import type {
BaseNucleus1DPreferences,
BaseNucleus2DPreferences,
+ FloatingRangesTablePreferences,
MatrixGenerationOptions,
MultipleSpectraAnalysisPreferences,
PanelsPreferences,
PeaksNucleusPreferences,
+ RangesNucleusPreferences,
SpectraPreferences,
Zones1DNucleusPreferences,
Zones2DNucleusPreferences,
@@ -124,10 +126,8 @@ const getZoneDefaultValues = (nucleus?: string): PanelsPreferences['zones'] => {
}
};
-const getRangeDefaultValues = (
- nucleus?: string,
-): PanelsPreferences['ranges'] => {
- const preferences = {
+const createBaseRangesTablePreferences =
+ (): FloatingRangesTablePreferences => ({
showSerialNumber: true,
from: { show: false, format: '0.00' },
to: { show: false, format: '0.00' },
@@ -136,14 +136,25 @@ const getRangeDefaultValues = (
deltaPPM: { show: true, format: '0.00' },
deltaHz: { show: false, format: '0.00' },
coupling: { show: true, format: '0.00' },
- jGraphTolerance: isProton(nucleus || '') ? 0.2 : nucleus === '13C' ? 2 : 0, //J Graph tolerance for: 1H: 0.2Hz 13C: 2Hz
showKind: true,
showMultiplicity: true,
showAssignment: true,
- showDeleteAction: true,
- showZoomAction: true,
- showEditAction: true,
showAssignmentLabel: true,
+ signalKinds: ['signal'],
+ });
+
+const getRangeDefaultValues = (
+ nucleus?: string,
+): PanelsPreferences['ranges'] => {
+ const preferences: RangesNucleusPreferences = {
+ floatingTablePreferences: createBaseRangesTablePreferences(),
+ tablePreferences: {
+ ...createBaseRangesTablePreferences(),
+ showDeleteAction: true,
+ showZoomAction: true,
+ showEditAction: true,
+ },
+ jGraphTolerance: isProton(nucleus || '') ? 0.2 : nucleus === '13C' ? 2 : 0, //J Graph tolerance for: 1H: 0.2Hz 13C: 2Hz
isSumConstant: true,
};
@@ -154,17 +165,19 @@ const getPeaksDefaultValues = (
nucleus?: string,
): PanelsPreferences['peaks'] => {
const preferences: PeaksNucleusPreferences = {
- showSerialNumber: true,
- deltaPPM: { show: true, format: '0.00' },
- deltaHz: { show: false, format: '0.00' },
- peakWidth: { show: false, format: '0.00' },
- intensity: { show: true, format: '0.00' },
- fwhm: { show: true, format: '0.00000' },
- mu: { show: false, format: '0.00000' },
- gamma: { show: false, format: '0.000' },
- showDeleteAction: true,
- showEditPeakShapeAction: true,
- showKind: true,
+ tablePreferences: {
+ showSerialNumber: true,
+ deltaPPM: { show: true, format: '0.00' },
+ deltaHz: { show: false, format: '0.00' },
+ peakWidth: { show: false, format: '0.00' },
+ intensity: { show: true, format: '0.00' },
+ fwhm: { show: true, format: '0.00000' },
+ mu: { show: false, format: '0.00000' },
+ gamma: { show: false, format: '0.000' },
+ showKind: true,
+ showDeleteAction: true,
+ showEditPeakShapeAction: true,
+ },
defaultPeakShape: DEFAULT_PEAK_SHAPE,
};
return getPreferences(preferences, nucleus);
diff --git a/src/data/constants/signalsKinds.ts b/src/data/constants/signalsKinds.ts
index 9d17a6260e..f509964b4a 100644
--- a/src/data/constants/signalsKinds.ts
+++ b/src/data/constants/signalsKinds.ts
@@ -1,7 +1,7 @@
import type { SignalKind } from '@zakodium/nmr-types';
import { signalKindLabelMapping } from 'nmr-processing';
-interface SignalKindItem {
+export interface SignalKindItem {
value: SignalKind;
label: string;
}
diff --git a/src/demo/views/SnapshotView.tsx b/src/demo/views/SnapshotView.tsx
index 7bd5188fbe..d86997233b 100644
--- a/src/demo/views/SnapshotView.tsx
+++ b/src/demo/views/SnapshotView.tsx
@@ -329,84 +329,161 @@ const customWorkspaces: CustomWorkspaces = {
ranges: {
nuclei: {
'1H': {
- isSumConstant: false,
- showAssignment: false,
- showAssignmentLabel: false,
- showSerialNumber: false,
- showDeleteAction: false,
- showEditAction: false,
- showMultiplicity: false,
- showZoomAction: false,
- from: {
- show: false,
- format: '0.00',
- },
- to: {
- show: false,
- format: '0.00',
- },
- absolute: {
- show: false,
- format: '0.00',
- },
- relative: {
- show: true,
- format: '0.00',
- },
- deltaPPM: {
- show: true,
- format: '0.00',
- },
- deltaHz: {
- show: false,
- format: '0.00',
+ tablePreferences: {
+ showAssignment: false,
+ showAssignmentLabel: false,
+ showSerialNumber: false,
+ showMultiplicity: false,
+ from: {
+ show: false,
+ format: '0.00',
+ },
+ to: {
+ show: false,
+ format: '0.00',
+ },
+ absolute: {
+ show: false,
+ format: '0.00',
+ },
+ relative: {
+ show: true,
+ format: '0.00',
+ },
+ deltaPPM: {
+ show: true,
+ format: '0.00',
+ },
+ deltaHz: {
+ show: false,
+ format: '0.00',
+ },
+ coupling: {
+ show: false,
+ format: '0.00',
+ },
+ showKind: false,
+ showDeleteAction: false,
+ showEditAction: false,
+ showZoomAction: false,
},
- coupling: {
- show: false,
- format: '0.00',
+ floatingTablePreferences: {
+ signalKinds: ['signal'],
+ showAssignment: false,
+ showAssignmentLabel: false,
+ showSerialNumber: false,
+ showMultiplicity: false,
+ from: {
+ show: false,
+ format: '0.00',
+ },
+ to: {
+ show: false,
+ format: '0.00',
+ },
+ absolute: {
+ show: false,
+ format: '0.00',
+ },
+ relative: {
+ show: true,
+ format: '0.00',
+ },
+ deltaPPM: {
+ show: true,
+ format: '0.00',
+ },
+ deltaHz: {
+ show: false,
+ format: '0.00',
+ },
+ coupling: {
+ show: false,
+ format: '0.00',
+ },
+ showKind: false,
},
+ isSumConstant: false,
jGraphTolerance: 0.2,
- showKind: false,
},
'13C': {
- isSumConstant: false,
- showAssignment: false,
- showAssignmentLabel: false,
- showSerialNumber: false,
- showDeleteAction: false,
- showEditAction: false,
- showMultiplicity: false,
- showZoomAction: false,
- from: {
- show: false,
- format: '0.00',
- },
- to: {
- show: false,
- format: '0.00',
- },
- absolute: {
- show: false,
- format: '0.00',
- },
- relative: {
- show: true,
- format: '0.00',
- },
- deltaPPM: {
- show: true,
- format: '0.00',
- },
- deltaHz: {
- show: false,
- format: '0.00',
+ tablePreferences: {
+ showAssignment: false,
+ showAssignmentLabel: false,
+ showSerialNumber: false,
+ showMultiplicity: false,
+ from: {
+ show: false,
+ format: '0.00',
+ },
+ to: {
+ show: false,
+ format: '0.00',
+ },
+ absolute: {
+ show: false,
+ format: '0.00',
+ },
+ relative: {
+ show: true,
+ format: '0.00',
+ },
+ deltaPPM: {
+ show: true,
+ format: '0.00',
+ },
+ deltaHz: {
+ show: false,
+ format: '0.00',
+ },
+ coupling: {
+ show: true,
+ format: '0.00',
+ },
+ showKind: true,
+
+ showDeleteAction: false,
+ showEditAction: false,
+ showZoomAction: false,
},
- coupling: {
- show: true,
- format: '0.00',
+ floatingTablePreferences: {
+ signalKinds: ['signal'],
+ showAssignment: false,
+ showAssignmentLabel: false,
+ showSerialNumber: false,
+ showMultiplicity: false,
+ from: {
+ show: false,
+ format: '0.00',
+ },
+ to: {
+ show: false,
+ format: '0.00',
+ },
+ absolute: {
+ show: false,
+ format: '0.00',
+ },
+ relative: {
+ show: true,
+ format: '0.00',
+ },
+ deltaPPM: {
+ show: true,
+ format: '0.00',
+ },
+ deltaHz: {
+ show: false,
+ format: '0.00',
+ },
+ coupling: {
+ show: true,
+ format: '0.00',
+ },
+ showKind: true,
},
+ isSumConstant: false,
jGraphTolerance: 2,
- showKind: true,
},
},
},