import type { Range as RangeType } from '@zakodium/nmr-types'; import { useRef } from 'react'; import { isSpectrum1D, resizeRange, } from '../../../data/data1d/Spectrum1D/index.js'; import { isRangeAssigned } from '../../../data/data1d/Spectrum1D/isRangeAssigned.js'; import { getOpacityBasedOnSignalKind } from '../../../data/utilities/RangeUtilities.js'; import { useAssignment, useAssignmentContext, } from '../../assignment/AssignmentsContext.js'; import { filterAssignedIDs } from '../../assignment/utilities/filterAssignedIDs.js'; import { useChartData } from '../../context/ChartContext.js'; import { useDispatch } from '../../context/DispatchContext.js'; import { useLogger } from '../../context/LoggerContext.js'; import { ActionsButtonsPopover } from '../../elements/ActionsButtonsPopover.js'; import { useDialogData } from '../../elements/DialogManager.js'; import { ResizerWithScale } from '../../elements/ResizerWithScale.js'; import type { Position } from '../../elements/resizer/SVGResizer.js'; import { useHighlight } from '../../highlight/index.js'; import { useActiveSpectrumRangesViewState } from '../../hooks/useActiveSpectrumRangesViewState.js'; import { useHighlightColor } from '../../hooks/useHighlightColor.js'; import { useResizerStatus } from '../../hooks/useResizerStatus.js'; import useSpectrum from '../../hooks/useSpectrum.js'; import { EditRangeModal } from '../../modal/editRange/EditRangeModal.js'; import type { StackOverlappingLabelsMapReturnType } from '../../utility/stackOverlappingLabels.js'; import { useIsInset } from '../inset/InsetProvider.js'; import { IntegralIndicator } from '../integral/IntegralIndicator.js'; import { useScaleX } from '../utilities/scale.js'; import { AssignmentLabel } from './AssignmentLabel.js'; import { Atoms } from './Atoms.js'; import { useAssignmentsPopoverActionsButtons } from './useAssignmentsPopoverActionsButtons.js'; interface RangeProps { selectedTool: string; range: RangeType; relativeFormat: string; signalsStackIndexes: StackOverlappingLabelsMapReturnType; } const minWidth = 10; function Range(options: RangeProps) { const { range, selectedTool, relativeFormat, signalsStackIndexes } = options; const { id, integration, signals, from, to } = range; const isInset = useIsInset(); const spectrum = useSpectrum(); const isAssignBtnTrigged = useRef(false); const { margin } = useChartData(); const signal = signals?.[0]; const highlightColor = useHighlightColor(); const assignmentData = useAssignmentContext(); const signalAssignment = useAssignment(signal?.id || ''); const highlightRange = useHighlight( [id].concat(signalAssignment.assignedDiaIds?.x || []).concat( filterAssignedIDs( assignmentData.data, signals.map((_signal) => _signal.id), ), ), { type: 'RANGE', extra: { id, spectrumID: spectrum.id }, }, ); const scaleX = useScaleX(); const dispatch = useDispatch(); const { logger } = useLogger(); const { showIntegralsValues } = useActiveSpectrumRangesViewState(); const { isDialogOpen } = useDialogData(); const isBlockedByEditing = selectedTool && isDialogOpen(EditRangeModal); function handleOnStopResizing(position: Position) { if (!spectrum || !isSpectrum1D(spectrum)) return; const from = scaleX().invert(position.x2); const to = scaleX().invert(position.x1); const newRange = resizeRange(spectrum, { from, to, range, logger }); if (!newRange) return; dispatch({ type: 'RESIZE_RANGE', payload: { range: newRange, spectrumKey: spectrum.id, }, }); } function mouseEnterHandler() { signalAssignment.highlight('x'); highlightRange.show(); } function mouseLeaveHandler() { signalAssignment.clearHighlight(); highlightRange.hide(); } function assignHandler() { if (!isBlockedByEditing) { isAssignBtnTrigged.current = true; signalAssignment.activate('x'); } } function unAssignHandler() { dispatch({ type: 'UNASSIGN_1D_SIGNAL', payload: { rangeKey: range.id, signalIndex: 0, }, }); } const isAssignmentActive = signalAssignment.isActive; const opacity = getOpacityBasedOnSignalKind(range); const isHighlighted = isBlockedByEditing || highlightRange.isActive || isAssignmentActive; const isSignalAssigned = isRangeAssigned(range); const isResizingActive = useResizerStatus('rangePicking'); const hasOnlyOneSignal = signals.length === 1; const isAssigned = hasOnlyOneSignal && signal.diaIDs ? signal.diaIDs.length > 0 : false; const actionsButtons = useAssignmentsPopoverActionsButtons({ isToggleMultiplicityTreeButtonVisible: !hasOnlyOneSignal, isAssignButtonVisible: hasOnlyOneSignal, isUnAssignButtonVisible: isAssignmentActive || isSignalAssigned, isAssignLabelButtonVisible: hasOnlyOneSignal && !signal.assignment, isUnAssignLabelButtonVisible: hasOnlyOneSignal && !!signal.assignment, onAssign: assignHandler, onUnAssign: unAssignHandler, rangeId: range.id, }); const isOpen = isAssignBtnTrigged.current ? isAssignmentActive : undefined; return ( { isAssignBtnTrigged.current = false; }} anchorTo="cursor-y" > {({ x1, x2 }, isActive) => { const rangeWidth = x2 - x1; let offsetX = 0; let highlightRectWidth = rangeWidth; if (rangeWidth < minWidth) { offsetX = Math.abs(rangeWidth - minWidth) / 2; highlightRectWidth = minWidth; } return ( {isAssigned && !isHighlighted && !isActive && ( )} {range.signals.map((signal) => ( ))} {showIntegralsValues && ( )} ); }} ); } export default Range;