Skip to content
Open
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
112 changes: 112 additions & 0 deletions src/component/panels/SpectraPanel/base/setting/ApplyToAllSelected.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Icon, Switch, Tag, Tooltip } from '@blueprintjs/core';
import styled from '@emotion/styled';
import { Controller, useFormContext, useWatch } from 'react-hook-form';

import { useSelectedSpectra } from '../../../../hooks/useSelectedSpectra.ts';

const Container = styled.div<{ isActive: boolean; isDisabled: boolean }>`
display: flex;
align-items: center;
justify-content: space-between;
gap: 5px;
padding: 5px 10px;
background: ${({ isActive }) =>
isActive ? 'rgba(232, 98, 26, 0.07)' : 'rgba(0,0,0,0.03)'};
opacity: ${({ isDisabled }) => (isDisabled ? 0.4 : 1)};
pointer-events: ${({ isDisabled }) => (isDisabled ? 'none' : 'auto')};
cursor: ${({ isDisabled }) => (isDisabled ? 'not-allowed' : 'default')};
user-select: none;
`;

const LabelGroup = styled.div`
display: flex;
align-items: center;
gap: 5px;
flex: 1;
min-width: 0;
`;

const LabelIcon = styled(Icon)<{ isActive: boolean }>`
color: ${({ isActive }) => (isActive ? '#E8621A' : '#888')};
flex-shrink: 0;
`;

const LabelText = styled.span<{ isActive: boolean }>`
font-size: 13px;
color: ${({ isActive }) => (isActive ? '#C05215' : '#555')};
white-space: nowrap;
`;

const StyledSwitch = styled(Switch)`
margin: 0;
`;

const CountTag = styled(Tag)<{ isActive: boolean }>`
color: ${({ isActive }) => (isActive ? '#E8621A' : '#666')} !important;
`;

export function ApplyToAllSelected() {
const spectra = useSelectedSpectra();
const { control, setValue } = useFormContext();
const applyToAllSelected = useWatch({ control, name: 'applyToAll' });
const selectedCount = spectra?.length || 0;
const isDisabled = selectedCount < 2;
const label =
selectedCount === 0
? '0 selected'
: selectedCount === 1
? '1 spectrum'
: `${selectedCount} spectra`;

const tooltipContent = isDisabled
? 'Select at least 2 spectra to apply color to all'
: applyToAllSelected
? `Color will be applied to all ${selectedCount} selected spectra`
: 'Enable to apply the chosen color to all selected spectra';
return (
<Tooltip content={tooltipContent} placement="top">
<Container
isActive={applyToAllSelected && !isDisabled}
isDisabled={isDisabled}
onClick={() =>
!isDisabled && setValue('applyToAll', !applyToAllSelected)
}
>
<LabelGroup>
<LabelIcon
icon="tint"
size={14}
isActive={applyToAllSelected && !isDisabled}
/>
<LabelText isActive={applyToAllSelected && !isDisabled}>
Apply to all selected
</LabelText>
<CountTag minimal round isActive={applyToAllSelected && !isDisabled}>
{label}
</CountTag>
</LabelGroup>
<Controller
name="applyToAll"
control={control}
render={({ field }) => {
const { value, onChange } = field;
return (
<StyledSwitch
defaultChecked={value}
checked={value}
disabled={isDisabled}
onClick={(e) => {
e.stopPropagation();
}}
onChange={(e) => {
onChange(!e.currentTarget.checked);
e.stopPropagation();
}}
/>
);
}}
/>
</Container>
</Tooltip>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { memo, useMemo } from 'react';
import { useWatch } from 'react-hook-form';

import PlotChart from './PlotChart.js';
import { processSnapPlot } from './processSnapPlot.js';
Expand All @@ -17,6 +18,10 @@ function Spectrum1DHistogram({
return processSnapPlot('1D', data, yLogBase);
}, [data]);

const isApplyToAllSelected = useWatch({ name: 'applyToAll' });

if (isApplyToAllSelected) return null;

return (
<div
style={{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Controller, useForm } from 'react-hook-form';
import { Controller, FormProvider, useForm } from 'react-hook-form';
import { ColorPicker } from 'react-science/ui';

import { COLORS } from '../../../../../data/utilities/generateColor.js';
import { colorToHexWithAlpha } from '../../../../utility/colorToHexWithAlpha.js';

import { ApplyToAllSelected } from './ApplyToAllSelected.tsx';
import Spectrum1DHistogram from './Spectrum1DHistogram.js';

interface Spectrum1DSettingProps {
Expand All @@ -13,33 +14,40 @@ interface Spectrum1DSettingProps {

export function Spectrum1DSetting({ data, onSubmit }: Spectrum1DSettingProps) {
const { display, data: spectrumData } = data;
const { control, handleSubmit } = useForm({
defaultValues: { display },
const methods = useForm({
defaultValues: { display, applyToAll: false },
});
const { control, handleSubmit } = methods;

return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.25rem' }}>
<div style={{ display: 'block', position: 'relative' }}>
<Controller
name="display.color"
control={control}
render={({ field }) => {
const { value, onChange } = field;
return (
<ColorPicker
onChangeComplete={(color) => {
onChange(colorToHexWithAlpha(color));
void handleSubmit(onSubmit)();
}}
color={{ hex: value || '#000' }}
presetColors={COLORS}
style={{ boxShadow: 'none' }}
/>
);
}}
/>
<FormProvider {...methods}>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<ApplyToAllSelected />

<div
style={{ display: 'block', position: 'relative', margin: '0 auto' }}
>
<Controller
name="display.color"
control={control}
render={({ field }) => {
const { value, onChange } = field;
return (
<ColorPicker
onChangeComplete={(color) => {
onChange(colorToHexWithAlpha(color));
void handleSubmit(onSubmit)();
}}
color={{ hex: value || '#000' }}
presetColors={COLORS}
style={{ boxShadow: 'none', width: 250 }}
/>
);
}}
/>
</div>
<Spectrum1DHistogram color="red" data={spectrumData} />
</div>
<Spectrum1DHistogram color="red" data={spectrumData} />
</div>
</FormProvider>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { memo, useMemo } from 'react';
import { useWatch } from 'react-hook-form';

import PlotChart from './PlotChart.js';
import { processSnapPlot } from './processSnapPlot.js';
Expand All @@ -18,6 +19,10 @@ function Spectrum2DHistogram({
return processSnapPlot('2D', data.rr, yLogBase);
}, [data]);

const isApplyToAllSelected = useWatch({ name: 'applyToAll' });

if (isApplyToAllSelected) return null;

return (
<div>
<div style={{ textAlign: 'center', paddingBottom: 5, paddingTop: 5 }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { NumberInput2 } from '../../../../elements/NumberInput2.js';
import { useFormValidateField } from '../../../../elements/useFormValidateField.js';
import { colorToHexWithAlpha } from '../../../../utility/colorToHexWithAlpha.js';

import { ApplyToAllSelected } from './ApplyToAllSelected.tsx';
import Spectrum2DHistogram from './Spectrum2DHistogram.js';

const StyledRangeSlider = styled(RangeSlider)<{
Expand Down Expand Up @@ -57,22 +58,29 @@ export function Spectrum2DSetting({ data, onSubmit }: Spectrum2DSettingProps) {
view: { spectraContourLevels },
} = useChartData();
const methods = useForm({
defaultValues: { contourOptions: spectraContourLevels[id], display },
defaultValues: {
contourOptions: spectraContourLevels[id],
display,
applyToAll: false,
},
});
const { positiveColor, negativeColor } = display;

return (
<FormProvider {...methods}>
<div>
<Container color={positiveColor}>
<span style={{ padding: '0 10px' }}>Positive</span>
<Settings sign="positive" onSubmit={onSubmit} />
</Container>
<Container color={negativeColor}>
<span style={{ padding: '0 10px' }}>Negative</span>
<Settings sign="negative" onSubmit={onSubmit} />
</Container>
<Spectrum2DHistogram data={spectrumData} />
<div style={{ display: 'flex', flexDirection: 'column' }}>
<ApplyToAllSelected />
<div>
<Container color={positiveColor}>
<span style={{ padding: '0 10px' }}>Positive</span>
<Settings sign="positive" onSubmit={onSubmit} />
</Container>
<Container color={negativeColor}>
<span style={{ padding: '0 10px' }}>Negative</span>
<Settings sign="negative" onSubmit={onSubmit} />
</Container>
<Spectrum2DHistogram data={spectrumData} />
</div>
</div>
</FormProvider>
);
Expand Down
12 changes: 10 additions & 2 deletions src/component/panels/SpectraPanel/base/setting/SpectrumSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import styled from '@emotion/styled';
import type { Display1D, Display2D } from '@zakodium/nmrium-core';

import { useDispatch } from '../../../../context/DispatchContext.js';
import { useActiveSpectra } from '../../../../hooks/useActiveSpectra.ts';
import { ColorIndicator } from '../ColorIndicator.js';

import { Spectrum1DSetting } from './Spectrum1DSetting.js';
import { Spectrum2DSetting } from './Spectrum2DSetting.js';

const SpectrumSettingContent = styled.div`
max-height: 360px;
max-height: 390px;
overflow-y: auto;
`;
interface SpectrumSettingProps {
Expand All @@ -25,11 +26,18 @@ export function SpectrumSetting({
}: SpectrumSettingProps) {
const dispatch = useDispatch();
const { id, info } = data;
const activeSpectra = useActiveSpectra();

function submitHandler(values: any) {
const { applyToAll, ...other } = values;
const ids =
activeSpectra && activeSpectra.length >= 2 && applyToAll
? activeSpectra.map((s) => s.id)
: [id];

dispatch({
type: 'CHANGE_SPECTRUM_SETTING',
payload: { id, ...values },
payload: { ids, ...other },
});
}

Expand Down
26 changes: 16 additions & 10 deletions src/component/reducer/actions/SpectraActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,11 @@ type ChangeActiveSpectrumAction = ActionType<
type ChangeSpectrumSettingAction = ActionType<
'CHANGE_SPECTRUM_SETTING',
| {
id: string;
ids: string[];
display: Display1D | Display2D;
}
| {
id: string;
ids: string[];
display: Display2D;
contourOptions: ContourLevel;
}
Expand Down Expand Up @@ -445,16 +445,22 @@ function handleChangeSpectrumSetting(
draft: Draft<State>,
action: ChangeSpectrumSettingAction,
) {
const id = action.payload.id;
const ids = action.payload.ids;

const spectrum = getSpectrum(draft, id);
if (!spectrum) return;
const spectraById = new Map(draft.data.map((s) => [s.id, s]));

for (const id of ids) {
const spectrum = spectraById.get(id);
if (!spectrum) continue;

spectrum.display = action.payload.display;
if (isFt2DSpectrum(spectrum) && 'contourOptions' in action.payload) {
draft.view.spectraContourLevels[id] = action.payload.contourOptions;
const { checkLevel } = contoursManager(draft.view.spectraContourLevels[id]);
checkLevel();
spectrum.display = action.payload.display;
if (isFt2DSpectrum(spectrum) && 'contourOptions' in action.payload) {
draft.view.spectraContourLevels[id] = action.payload.contourOptions;
const { checkLevel } = contoursManager(
draft.view.spectraContourLevels[id],
);
checkLevel();
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions test-e2e/panels/spectra.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,20 +127,20 @@ test('2d spectrum', async ({ page }) => {
// Open Change color modal
await nmrium.page.click('_react=ColorIndicator');

// change the color to #ddb1c9ff
// change the color to #e4c0d3
await nmrium.page.click('_react=Saturation', {
position: { x: 40, y: 20 },
});

// Check that ColorIndicator color changed
await expect(
nmrium.page.locator('_react=ColorIndicator[display.color="#ddb1c9ff"]'),
nmrium.page.locator('_react=ColorIndicator[display.color="#e4c0d3ff"]'),
).toBeVisible();
// Check that spectrum color changed
await expect(
nmrium.page
.getByTestId('spectrum-line')
.locator('_react=Line[display.color="#ddb1c9ff"]'),
.locator('_react=Line[display.color="#e4c0d3ff"]'),
).toBeVisible();

// Close color picker
Expand Down
Loading