From 487484f7609b8c06f5ee894da25f55d7814730c8 Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Tue, 16 Jun 2026 11:31:57 +0200 Subject: [PATCH 1/2] feat: prepare processings UI --- package.json | 2 +- src/component/elements/Sections.tsx | 8 +- .../panels/filtersPanel/FilterPanel.tsx | 10 +++ .../Filters/base/BaseApodizationOptions.tsx | 1 + .../processings_sections_panel.tsx | 84 +++++++++++++++++++ 5 files changed, 100 insertions(+), 5 deletions(-) create mode 100644 src/component/panels/filtersPanel/processings_sections_panel.tsx diff --git a/package.json b/package.json index e17bd30f0..f0ebbac0a 100644 --- a/package.json +++ b/package.json @@ -167,4 +167,4 @@ "volta": { "node": "24.15.0" } -} \ No newline at end of file +} diff --git a/src/component/elements/Sections.tsx b/src/component/elements/Sections.tsx index 2547ee3ed..14ece4232 100644 --- a/src/component/elements/Sections.tsx +++ b/src/component/elements/Sections.tsx @@ -205,7 +205,7 @@ const InnerHeader = styled.div` `; interface BaseSectionProps { - title: string; + title: ReactNode; serial?: number; rightElement?: ReactNode | ((isOpen: boolean) => ReactNode); leftElement?: ReactNode | ((isOpen: boolean) => ReactNode); @@ -214,14 +214,14 @@ interface BaseSectionProps { } interface SectionItemProps extends BaseSectionProps { - id?: string; + id: string; index?: number; onClick?: (id: any, event?: MouseEvent) => void; children?: ReactNode | ((options: { isOpen?: boolean }) => ReactNode); isOpen: boolean; sticky?: boolean; onReorder?: (sourceId: number, targetId: number) => void; - dragLabel?: string; + dragLabel?: ReactNode; } interface SectionProps { @@ -269,7 +269,7 @@ function SectionItem(props: SectionItemProps) { const { title, dragLabel = title, - id = title, + id, onClick, serial, rightElement, diff --git a/src/component/panels/filtersPanel/FilterPanel.tsx b/src/component/panels/filtersPanel/FilterPanel.tsx index b6db5a60a..9a2da618a 100644 --- a/src/component/panels/filtersPanel/FilterPanel.tsx +++ b/src/component/panels/filtersPanel/FilterPanel.tsx @@ -2,17 +2,20 @@ import { useDispatch } from '../../context/DispatchContext.js'; import { useToaster } from '../../context/ToasterContext.js'; import type { AlertButton } from '../../elements/Alert.js'; import { useAlert } from '../../elements/Alert.js'; +import useCheckExperimentalFeature from '../../hooks/useCheckExperimentalFeature.ts'; import useSpectrum from '../../hooks/useSpectrum.js'; import { TablePanel } from '../extra/BasicPanelStyle.js'; import DefaultPanelHeader from '../header/DefaultPanelHeader.js'; import { FiltersSectionsPanel } from './Filters/FiltersSectionsPanel.js'; +import { ProcessingsSectionsPanel } from './processings_sections_panel.tsx'; export default function FiltersPanel() { const dispatch = useDispatch(); const toaster = useToaster(); const { showAlert } = useAlert(); const { filters } = useSpectrum({ filters: [] }); + const isExperimental = useCheckExperimentalFeature(); function handleDeleteFilter() { const buttons: AlertButton[] = [ @@ -46,6 +49,13 @@ export default function FiltersPanel() { />
+ + {isExperimental && ( + <> +
+ + + )}
); diff --git a/src/component/panels/filtersPanel/Filters/base/BaseApodizationOptions.tsx b/src/component/panels/filtersPanel/Filters/base/BaseApodizationOptions.tsx index 5a5e5332c..61a881ee2 100644 --- a/src/component/panels/filtersPanel/Filters/base/BaseApodizationOptions.tsx +++ b/src/component/panels/filtersPanel/Filters/base/BaseApodizationOptions.tsx @@ -330,6 +330,7 @@ function OptionsSection(options: OptionsSectionProps) { return ( (null); + + function handleDeleteFilter() { + const buttons: AlertButton[] = [ + { + text: 'Yes', + intent: 'danger', + }, + { text: 'No' }, + ]; + + showAlert({ + message: + 'You are about to delete all processing steps, Are you sure?. Experimental, not implemented yet', + buttons, + }); + } + + function toggleSection(operationId: string) { + setOpenedOperation(openedOperation === operationId ? null : operationId); + } + + return ( + <> + + + + {processings?.map((operation, index) => ( + toggleSection(operation.uid)} + > + + {renderCoreSlot( + core, + 'panels.processings.operation.expanded', + operation.settings !== null ? ( + + ) : ( + + ), + )} + + + ))} + + + ); +} From 35106020159b5abaa7aa9f6db44e803b27dbf0ef Mon Sep 17 00:00:00 2001 From: tpoisseau <22891227+tpoisseau@users.noreply.github.com> Date: Tue, 16 Jun 2026 15:02:51 +0200 Subject: [PATCH 2/2] feat: slot supports props --- src/component/header/Header.tsx | 4 +- src/component/modal/aboutUs/AboutUsModal.tsx | 8 +++- .../processings_sections_panel.tsx | 46 +++++++++++-------- src/component/utility/CoreSlot.tsx | 37 +++++++++++++++ src/component/utility/renderCoreSlot.tsx | 21 --------- 5 files changed, 71 insertions(+), 45 deletions(-) create mode 100644 src/component/utility/CoreSlot.tsx delete mode 100644 src/component/utility/renderCoreSlot.tsx diff --git a/src/component/header/Header.tsx b/src/component/header/Header.tsx index 068bc84c8..376dc7ec9 100644 --- a/src/component/header/Header.tsx +++ b/src/component/header/Header.tsx @@ -28,7 +28,7 @@ import AboutUsModal from '../modal/aboutUs/AboutUsModal.js'; import WorkspaceItem from '../modal/setting/WorkspaceItem.js'; import { GeneralSettingsToolbarItem } from '../modal/setting/general_settings.js'; import { options } from '../toolbar/ToolTypes.js'; -import { renderCoreSlot } from '../utility/renderCoreSlot.js'; +import { CoreSlot } from '../utility/CoreSlot.tsx'; import { AutoPeakPickingOptionPanel } from './AutoPeakPickingOptionPanel.js'; import { HeaderWrapper } from './HeaderWrapper.js'; @@ -149,7 +149,7 @@ function HeaderInner(props: HeaderInnerProps) { }} > - {renderCoreSlot(core, 'topbar.right')} + {!hideWorkspaces && ( diff --git a/src/component/modal/aboutUs/AboutUsModal.tsx b/src/component/modal/aboutUs/AboutUsModal.tsx index 640452b8e..7b901ce6f 100644 --- a/src/component/modal/aboutUs/AboutUsModal.tsx +++ b/src/component/modal/aboutUs/AboutUsModal.tsx @@ -7,7 +7,7 @@ import { useCore } from '../../context/CoreContext.js'; import Logo from '../../elements/Logo.js'; import { StandardDialog } from '../../elements/StandardDialog.tsx'; import { StyledDialogBody } from '../../elements/StyledDialogBody.js'; -import { renderCoreSlot } from '../../utility/renderCoreSlot.js'; +import { CoreSlot } from '../../utility/CoreSlot.tsx'; import AboutUsZakodium from './AboutUsZakodium.js'; @@ -137,7 +137,11 @@ function AboutUsModal() { title="About NMRium" > - {renderCoreSlot(core, 'topbar.about_us.modal', modalContentFallback)} + diff --git a/src/component/panels/filtersPanel/processings_sections_panel.tsx b/src/component/panels/filtersPanel/processings_sections_panel.tsx index 90d1f64a6..bc892b1ce 100644 --- a/src/component/panels/filtersPanel/processings_sections_panel.tsx +++ b/src/component/panels/filtersPanel/processings_sections_panel.tsx @@ -7,7 +7,7 @@ import { useAlert } from '../../elements/Alert.tsx'; import { EmptyText } from '../../elements/EmptyText.tsx'; import { Sections } from '../../elements/Sections.tsx'; import useSpectrum from '../../hooks/useSpectrum.ts'; -import { renderCoreSlot } from '../../utility/renderCoreSlot.tsx'; +import { CoreSlot } from '../../utility/CoreSlot.tsx'; import DefaultPanelHeader from '../header/DefaultPanelHeader.tsx'; export function ProcessingsSectionsPanel() { @@ -51,30 +51,36 @@ export function ProcessingsSectionsPanel() { + } isOpen={openedOperation === operation.uid} serial={index + 1} onClick={() => toggleSection(operation.uid)} > - {renderCoreSlot( - core, - 'panels.processings.operation.expanded', - operation.settings !== null ? ( - - ) : ( - - ), - )} + + ) : ( + + ) + } + operation={operation} + /> ))} diff --git a/src/component/utility/CoreSlot.tsx b/src/component/utility/CoreSlot.tsx new file mode 100644 index 000000000..215bd067f --- /dev/null +++ b/src/component/utility/CoreSlot.tsx @@ -0,0 +1,37 @@ +import type { + NMRiumCore, + PluginUIComponentProps, + SupportedUISlot, +} from '@zakodium/nmrium-core'; +import { castSlotProps } from '@zakodium/nmrium-core'; +import type { ReactNode } from 'react'; + +type SlotProps = + Omit, 'slot'> extends Record< + string, + never + > + ? object + : Omit, 'slot'>; + +interface CoreSlotProps { + slot: Slot; + core: NMRiumCore; + fallback?: ReactNode; +} + +/** + * Render all components registered in the given slot. + */ +export function CoreSlot( + props: CoreSlotProps & SlotProps, +) { + const { slot, core, fallback, ...slotProps } = props; + castSlotProps(slotProps, slot); + + const jsx = Array.from(core.slot(slot), ([key, Component]) => ( + + )); + + return jsx.length > 0 ? jsx : fallback; +} diff --git a/src/component/utility/renderCoreSlot.tsx b/src/component/utility/renderCoreSlot.tsx deleted file mode 100644 index 2c9169cf7..000000000 --- a/src/component/utility/renderCoreSlot.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import type { NMRiumCore, SupportedUISlot } from '@zakodium/nmrium-core'; -import type { ReactNode } from 'react'; - -/** - * Render all components registered in the given slot. - * - * @param core - * @param slot - * @param fallback - return fallback if no component is registered in the slot - */ -export function renderCoreSlot( - core: NMRiumCore, - slot: SupportedUISlot, - fallback?: ReactNode, -): ReactNode { - const jsx = Array.from(core.slot(slot), ([key, Component]) => ( - - )); - - return jsx.length > 0 ? jsx : fallback; -}