From b166e408f8e1f901a2176d09b83ea8e1865bacad Mon Sep 17 00:00:00 2001 From: hamed musallam Date: Wed, 10 Jun 2026 15:01:33 +0200 Subject: [PATCH] refactor: support advanced tool visibility filtering --- .../hooks/useCheckToolsVisibility.ts | 143 +++++++++--------- src/component/toolbar/ToolBar.tsx | 9 +- src/component/toolbar/ToolTypes.ts | 117 ++++++-------- 3 files changed, 118 insertions(+), 151 deletions(-) diff --git a/src/component/hooks/useCheckToolsVisibility.ts b/src/component/hooks/useCheckToolsVisibility.ts index c5dd9aeb44..108de56128 100644 --- a/src/component/hooks/useCheckToolsVisibility.ts +++ b/src/component/hooks/useCheckToolsVisibility.ts @@ -1,4 +1,3 @@ -import type { Info1D, Info2D } from '@zakodium/nmr-types'; import { useCallback } from 'react'; import { useChartData } from '../context/ChartContext.js'; @@ -7,102 +6,98 @@ import type { MainTool, ToolOptionItem } from '../toolbar/ToolTypes.js'; import { options } from '../toolbar/ToolTypes.js'; import useCheckExperimentalFeature from './useCheckExperimentalFeature.js'; -import useSpectrum from './useSpectrum.js'; +import { useSelectedSpectra } from './useSelectedSpectra.ts'; +import useSpectraByActiveNucleus from './useSpectraPerNucleus.ts'; -type SpectrumInfo = Info1D | Info2D; - -export interface CheckOptions { - checkSpectrumType?: boolean; - checkMode?: boolean; - extraInfoCheckParameters?: SpectrumInfo; -} - -export function useCheckToolsVisibility(): ( - toolKey: MainTool, - checkOptions?: CheckOptions, -) => boolean { +export function useCheckToolsVisibility(): (toolKey: MainTool) => boolean { const { displayerMode } = useChartData(); const preferences = usePreferences(); - const spectrum = useSpectrum(null); + const selectedSpectra = useSelectedSpectra(); + const spectra = useSpectraByActiveNucleus(); const isExperimentalFeatureActivated = useCheckExperimentalFeature(); + const toolBarButtons = preferences?.current?.display?.toolBarButtons; return useCallback( - (toolKey: MainTool, checkOptions: CheckOptions = {}) => { + (toolKey: MainTool): boolean => { const { - checkMode = true, - checkSpectrumType = true, - extraInfoCheckParameters, - } = checkOptions; - - const { spectraOptions, mode, isExperimental } = options[toolKey]; + spectraFilter, + spectraMatch = 'all', + selectedSpectra: selectionRules = { min: 1, max: 1 }, + mode, + isExperimental, + } = options[toolKey]; - // TODO: make sure preferences are not a lie and remove the optional chaining. - const flag = - preferences?.current?.display?.toolBarButtons?.[toolKey] ?? false; - - const modeFlag = - !checkMode || (checkMode && (!mode || displayerMode === mode)); - - const spectrumCheckFlag = - !checkSpectrumType || - (checkSpectrumType && checkSpectrum(spectrum, spectraOptions)); + // 1. Tool status + const flag = toolBarButtons?.[toolKey] ?? false; const isToolActivated = (flag && !isExperimental) || (isExperimental && isExperimentalFeatureActivated); - return !!( - isToolActivated && - modeFlag && - spectrumCheckFlag && - (!extraInfoCheckParameters || - checkInfo(extraInfoCheckParameters, spectrum?.info)) - ); - }, - [displayerMode, isExperimentalFeatureActivated, preferences, spectrum], - ); -} + if (!isToolActivated) return false; -function checkSpectrum( - spectrum: any, - options: ToolOptionItem['spectraOptions'], -) { - let outerConditionResult = false; + // 2. Mode check + if (mode && displayerMode !== mode) return false; - if (!options) { - return true; - } + // 3. No spectra rules, always visible + if (!spectraFilter) return true; + + // 4. Resolve spectra source + const spectraToCheck = + selectedSpectra && selectedSpectra.length > 0 + ? selectedSpectra + : spectra; - for (const option of options) { - let innerConditionFlag = true; + // 5. Selection constraints + if (selectionRules) { + const { min, max } = selectionRules; - if (option.active) { - if (spectrum) { - for (const { key, value } of option.info || []) { - if (spectrum.info[key] !== value) { - innerConditionFlag = false; - } + if (typeof min === 'number' && spectraToCheck.length < min) { + return false; + } + + if (typeof max === 'number' && spectraToCheck.length > max) { + return false; } - } else { - innerConditionFlag = false; } - } - outerConditionResult = outerConditionResult || innerConditionFlag; - } + // 6. Evaluate spectra filters + const matchCount = spectraToCheck.filter((spectrum) => + checkSpectrum(spectrum, spectraFilter), + ).length; - return outerConditionResult; + // 7. Match strategy + if (spectraMatch === 'any') { + return matchCount > 0; + } + + return matchCount === spectraToCheck.length; + }, + [ + displayerMode, + isExperimentalFeatureActivated, + toolBarButtons, + selectedSpectra, + spectra, + ], + ); } -function checkInfo(checkParameters: SpectrumInfo, data: SpectrumInfo) { - for (const key in checkParameters) { - if ( - checkParameters[key as keyof SpectrumInfo] !== - data[key as keyof SpectrumInfo] - ) { - return false; - } +function checkSpectrum( + spectrum: any, + spectraFilter: ToolOptionItem['spectraFilter'], +): boolean { + if (!spectraFilter) return true; + + for (const option of spectraFilter) { + if (!spectrum) continue; + + const infoConditionsMet = (option.info ?? []).every( + ({ key, value }) => spectrum.info[key] === value, + ); + + if (infoConditionsMet) return true; } - return true; + return false; } diff --git a/src/component/toolbar/ToolBar.tsx b/src/component/toolbar/ToolBar.tsx index 4b99557045..4467a354b7 100644 --- a/src/component/toolbar/ToolBar.tsx +++ b/src/component/toolbar/ToolBar.tsx @@ -37,7 +37,6 @@ import { ToolbarPopoverItem } from '../elements/ToolbarPopoverItem.js'; import { useExportManagerAPI } from '../elements/export/ExportManager.js'; import { useActiveSpectrum } from '../hooks/useActiveSpectrum.js'; import useCheckExperimentalFeature from '../hooks/useCheckExperimentalFeature.js'; -import type { CheckOptions } from '../hooks/useCheckToolsVisibility.js'; import { useCheckToolsVisibility } from '../hooks/useCheckToolsVisibility.js'; import useDatumWithSpectraStatistics from '../hooks/useDatumWithSpectraStatistics.js'; import { useDialogToggle } from '../hooks/useDialogToggle.js'; @@ -58,7 +57,6 @@ import { EXPORT_MENU, IMPORT_MENU } from './toolbarMenu.js'; interface BaseToolItem extends Pick { id: MainTool; - checkOptions?: CheckOptions; isVisible?: boolean; } interface ToolItem extends BaseToolItem { @@ -303,7 +301,6 @@ export default function ToolBar() { 'Integrate multiple spectra at once and adjust integration zones by dragging their edges.', }, icon: , - checkOptions: { checkSpectrumType: false }, isVisible: ftCounter > 0, }, { @@ -398,7 +395,6 @@ export default function ToolBar() { description: `Define exclusion zones by clicking, dragging, releasing${!invert ? ' while holding SHIFT' : ''}. This option is practical for excluding large peaks like solvents.`, }, icon: , - checkOptions: { checkSpectrumType: false }, isVisible: ftCounter > 0, }, { @@ -510,10 +506,9 @@ export default function ToolBar() { {toolItems.map((item) => { - const { id, icon, tooltip, checkOptions, disabled, isVisible } = item; + const { id, icon, tooltip, disabled, isVisible } = item; const isToolVisible = - isButtonVisible(id, checkOptions) && - (isVisible === undefined || isVisible); + isButtonVisible(id) && (isVisible === undefined || isVisible); if (!isToolVisible) return null; diff --git a/src/component/toolbar/ToolTypes.ts b/src/component/toolbar/ToolTypes.ts index d892d2e686..1335d41f84 100644 --- a/src/component/toolbar/ToolTypes.ts +++ b/src/component/toolbar/ToolTypes.ts @@ -10,13 +10,22 @@ export interface ToolOptionItem { id: Tool; label: string; mode?: DisplayerMode; - spectraOptions?: Array< - | { - info?: Array<{ key: InfoKey; value: any }>; // check if the active spectrum has these info - active: true; - } - | { active: false } - >; + spectraFilter?: Array<{ + info?: Array<{ key: InfoKey; value: any }>; // check if the active spectrum has these info + }>; + /** + * Minium and Maximum number of spectra that must be selected + * @default({min:1,max:1}}) exact one spectrum selected + */ + selectedSpectra?: { + min?: number; + max?: number; + }; /** + * controls how spectraFilter is evaluated + * 'all' : every selected spectrum must meet spectraFilter. + * 'any' : at least one selected spectrum must meet the spectraFilter. + */ + spectraMatch?: 'any' | 'all'; isToggle: boolean; hasOptionPanel: boolean; isFilter: boolean; @@ -48,15 +57,13 @@ export const options: RecordOptions = { hasOptionPanel: true, isFilter: false, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFt', value: true }], - active: true, - }, - { - active: false, }, ], + selectedSpectra: { min: 1 }, + spectraMatch: 'all', isToggle: true, }, integral: { @@ -65,10 +72,9 @@ export const options: RecordOptions = { hasOptionPanel: false, isFilter: false, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFt', value: true }], - active: true, }, ], isToggle: true, @@ -79,9 +85,9 @@ export const options: RecordOptions = { hasOptionPanel: false, isFilter: false, mode: '1D', - spectraOptions: [ + spectraFilter: [ { - active: true, + info: [{ key: 'isFt', value: true }], }, ], isToggle: true, @@ -92,10 +98,9 @@ export const options: RecordOptions = { hasOptionPanel: true, isFilter: false, mode: '2D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFt', value: true }], - active: true, }, ], isToggle: true, @@ -106,10 +111,9 @@ export const options: RecordOptions = { hasOptionPanel: false, isFilter: false, mode: '2D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFt', value: true }], - active: true, }, ], isToggle: true, @@ -120,10 +124,9 @@ export const options: RecordOptions = { hasOptionPanel: true, isFilter: false, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFt', value: true }], - active: true, }, ], isToggle: true, @@ -134,13 +137,12 @@ export const options: RecordOptions = { hasOptionPanel: true, isFilter: true, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [ { key: 'isFid', value: true }, { key: 'isComplex', value: true }, ], - active: true, }, ], isToggle: true, @@ -151,13 +153,12 @@ export const options: RecordOptions = { hasOptionPanel: true, isFilter: true, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [ { key: 'isFid', value: true }, { key: 'isComplex', value: true }, ], - active: true, }, ], isToggle: true, @@ -168,13 +169,12 @@ export const options: RecordOptions = { hasOptionPanel: true, isFilter: true, mode: '2D', - spectraOptions: [ + spectraFilter: [ { info: [ { key: 'isFid', value: true }, { key: 'isComplex', value: true }, ], - active: true, }, ], isToggle: true, @@ -185,14 +185,13 @@ export const options: RecordOptions = { hasOptionPanel: true, isFilter: true, mode: '2D', - spectraOptions: [ + spectraFilter: [ { info: [ { key: 'isFid', value: true }, { key: 'isFtDimensionOne', value: true }, { key: 'isComplex', value: true }, ], - active: true, }, ], isToggle: true, @@ -203,13 +202,12 @@ export const options: RecordOptions = { hasOptionPanel: true, isFilter: true, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [ { key: 'isFt', value: true }, { key: 'isComplex', value: true }, ], - active: true, }, ], isToggle: true, @@ -220,10 +218,9 @@ export const options: RecordOptions = { hasOptionPanel: true, isFilter: true, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFt', value: true }], - active: true, }, ], isToggle: true, @@ -234,15 +231,12 @@ export const options: RecordOptions = { hasOptionPanel: false, isFilter: false, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFt', value: true }], - active: true, - }, - { - active: false, }, ], + selectedSpectra: { min: 0 }, isToggle: true, }, exclusionZones: { @@ -251,15 +245,12 @@ export const options: RecordOptions = { hasOptionPanel: false, isFilter: true, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFt', value: true }], - active: true, - }, - { - active: false, }, ], + selectedSpectra: { min: 0 }, isToggle: true, }, matrixGenerationExclusionZones: { @@ -268,15 +259,12 @@ export const options: RecordOptions = { hasOptionPanel: false, isFilter: false, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFt', value: true }], - active: true, - }, - { - active: false, }, ], + selectedSpectra: { min: 0 }, isToggle: true, }, databaseRangesSelection: { @@ -285,10 +273,9 @@ export const options: RecordOptions = { hasOptionPanel: false, isFilter: false, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFt', value: true }], - active: true, }, ], isToggle: true, @@ -306,13 +293,12 @@ export const options: RecordOptions = { hasOptionPanel: false, isFilter: true, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [ { key: 'isFid', value: true }, { key: 'isComplex', value: true }, ], - active: true, }, ], isToggle: false, @@ -323,13 +309,12 @@ export const options: RecordOptions = { hasOptionPanel: false, isFilter: true, mode: '2D', - spectraOptions: [ + spectraFilter: [ { info: [ { key: 'isFid', value: true }, { key: 'isComplex', value: true }, ], - active: true, }, ], isToggle: false, @@ -340,14 +325,13 @@ export const options: RecordOptions = { hasOptionPanel: false, isFilter: true, mode: '2D', - spectraOptions: [ + spectraFilter: [ { info: [ { key: 'isFtDimensionOne', value: true }, { key: 'isFt', value: false }, { key: 'isComplex', value: true }, ], - active: true, }, ], isToggle: false, @@ -358,10 +342,9 @@ export const options: RecordOptions = { hasOptionPanel: true, isFilter: true, mode: '2D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFt', value: true }], - active: true, }, ], isToggle: true, @@ -379,10 +362,9 @@ export const options: RecordOptions = { hasOptionPanel: false, isFilter: false, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isComplex', value: true }], - active: true, }, ], isToggle: false, @@ -401,15 +383,12 @@ export const options: RecordOptions = { hasOptionPanel: false, isFilter: false, mode: '1D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFt', value: true }], - active: true, - }, - { - active: false, }, ], + selectedSpectra: { min: 0 }, isToggle: false, }, zoomOut: { @@ -432,10 +411,9 @@ export const options: RecordOptions = { hasOptionPanel: true, isFilter: true, mode: '2D', - spectraOptions: [ + spectraFilter: [ { info: [{ key: 'isFid', value: true }], - active: true, }, ], isToggle: true, @@ -446,14 +424,13 @@ export const options: RecordOptions = { hasOptionPanel: true, isFilter: true, mode: '2D', - spectraOptions: [ + spectraFilter: [ { info: [ { key: 'isFid', value: true }, { key: 'isFtDimensionOne', value: true }, { key: 'isComplex', value: true }, ], - active: true, }, ], isToggle: true,