diff --git a/src/component/panels/SpectraPanel/base/setting/ApplyToAllSelected.tsx b/src/component/panels/SpectraPanel/base/setting/ApplyToAllSelected.tsx new file mode 100644 index 000000000..3b5c84cc1 --- /dev/null +++ b/src/component/panels/SpectraPanel/base/setting/ApplyToAllSelected.tsx @@ -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 ( + + + !isDisabled && setValue('applyToAll', !applyToAllSelected) + } + > + + + + Apply to all selected + + + {label} + + + { + const { value, onChange } = field; + return ( + { + e.stopPropagation(); + }} + onChange={(e) => { + onChange(!e.currentTarget.checked); + e.stopPropagation(); + }} + /> + ); + }} + /> + + + ); +} diff --git a/src/component/panels/SpectraPanel/base/setting/Spectrum1DHistogram.tsx b/src/component/panels/SpectraPanel/base/setting/Spectrum1DHistogram.tsx index 7520c2b7c..3e64733ee 100644 --- a/src/component/panels/SpectraPanel/base/setting/Spectrum1DHistogram.tsx +++ b/src/component/panels/SpectraPanel/base/setting/Spectrum1DHistogram.tsx @@ -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'; @@ -17,6 +18,10 @@ function Spectrum1DHistogram({ return processSnapPlot('1D', data, yLogBase); }, [data]); + const isApplyToAllSelected = useWatch({ name: 'applyToAll' }); + + if (isApplyToAllSelected) return null; + return (
-
- { - const { value, onChange } = field; - return ( - { - onChange(colorToHexWithAlpha(color)); - void handleSubmit(onSubmit)(); - }} - color={{ hex: value || '#000' }} - presetColors={COLORS} - style={{ boxShadow: 'none' }} - /> - ); - }} - /> + +
+ + +
+ { + const { value, onChange } = field; + return ( + { + onChange(colorToHexWithAlpha(color)); + void handleSubmit(onSubmit)(); + }} + color={{ hex: value || '#000' }} + presetColors={COLORS} + style={{ boxShadow: 'none', width: 250 }} + /> + ); + }} + /> +
+
- -
+ ); } diff --git a/src/component/panels/SpectraPanel/base/setting/Spectrum2DHistogram.tsx b/src/component/panels/SpectraPanel/base/setting/Spectrum2DHistogram.tsx index 9cf78e280..862a3c994 100644 --- a/src/component/panels/SpectraPanel/base/setting/Spectrum2DHistogram.tsx +++ b/src/component/panels/SpectraPanel/base/setting/Spectrum2DHistogram.tsx @@ -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'; @@ -18,6 +19,10 @@ function Spectrum2DHistogram({ return processSnapPlot('2D', data.rr, yLogBase); }, [data]); + const isApplyToAllSelected = useWatch({ name: 'applyToAll' }); + + if (isApplyToAllSelected) return null; + return (
diff --git a/src/component/panels/SpectraPanel/base/setting/Spectrum2DSetting.tsx b/src/component/panels/SpectraPanel/base/setting/Spectrum2DSetting.tsx index 92c3e7f2f..20fccfd1d 100644 --- a/src/component/panels/SpectraPanel/base/setting/Spectrum2DSetting.tsx +++ b/src/component/panels/SpectraPanel/base/setting/Spectrum2DSetting.tsx @@ -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)<{ @@ -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 ( -
- - Positive - - - - Negative - - - +
+ +
+ + Positive + + + + Negative + + + +
); diff --git a/src/component/panels/SpectraPanel/base/setting/SpectrumSetting.tsx b/src/component/panels/SpectraPanel/base/setting/SpectrumSetting.tsx index 8642d6faa..14727f3b9 100644 --- a/src/component/panels/SpectraPanel/base/setting/SpectrumSetting.tsx +++ b/src/component/panels/SpectraPanel/base/setting/SpectrumSetting.tsx @@ -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 { @@ -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 }, }); } diff --git a/src/component/reducer/actions/SpectraActions.ts b/src/component/reducer/actions/SpectraActions.ts index 9a7d8b129..023694852 100644 --- a/src/component/reducer/actions/SpectraActions.ts +++ b/src/component/reducer/actions/SpectraActions.ts @@ -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; } @@ -445,16 +445,22 @@ function handleChangeSpectrumSetting( draft: Draft, 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(); + } } } diff --git a/test-e2e/panels/spectra.test.ts b/test-e2e/panels/spectra.test.ts index 477682a8d..711efa18d 100644 --- a/test-e2e/panels/spectra.test.ts +++ b/test-e2e/panels/spectra.test.ts @@ -68,11 +68,21 @@ test('Check change spectrum color, Should be white', async ({ page }) => { // Open Change color modal await nmrium.page.click('_react=ColorIndicator'); - - // Click on the top-left of the color picker (white) - await nmrium.page.click('_react=Saturation', { - position: { x: 0, y: 0 }, - }); + await nmrium.page.hover('_react=Saturation'); + // Change color by move to the top-left of the color picker (white) + const saturationBox = nmrium.page.locator('_react=Saturation'); + const box = await saturationBox.boundingBox(); + + if (box) { + const startX = box.x + box.width - 1; + const startY = box.y + box.height - 1; + const targetX = box.x; + const targetY = box.y; + await nmrium.page.mouse.move(startX, startY, { steps: 15 }); + await nmrium.page.mouse.down(); + await nmrium.page.mouse.move(targetX, targetY, { steps: 15 }); + await nmrium.page.mouse.up(); + } // The line should now be white. await expect(whiteSpectrumLine).toBeVisible(); @@ -127,20 +137,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