diff --git a/extensions/default/src/Toolbar/ToolbarModeSelector.tsx b/extensions/default/src/Toolbar/ToolbarModeSelector.tsx new file mode 100644 index 00000000000..53e90adc454 --- /dev/null +++ b/extensions/default/src/Toolbar/ToolbarModeSelector.tsx @@ -0,0 +1,374 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import PropTypes from 'prop-types'; +import { Link, useLocation } from 'react-router-dom'; + +import { + Button, + cn, + Icons, + Popover, + PopoverContent, + PopoverTrigger, + Tooltip, + TooltipContent, + TooltipTrigger, + useImageViewer, +} from '@ohif/ui-next'; +import { CommandsManager } from '@ohif/core'; +import { useAppConfig } from '@state'; +import { useTranslation } from 'react-i18next'; + +import { + evaluateModeValidity, + fetchStudyEnvelope, + getDataSourcePathSegment, + modeIsValidForOrdering, + type StudyEnvelope, + usePreservedViewerSearch, + type LoadedModeRouteHint, +} from '../utils/modeSelectorUtils'; + +const HIDDEN_MODE_IDS = new Set(['ohif-gcp-mode']); + +type ToolbarMenuRow = { + mode: LoadedModeRouteHint & { displayName?: string; hide?: boolean; isValidMode?: unknown }; + validity: Exclude, undefined>; + modeHref: { pathname: string; search: string }; + isCurrentRoute: boolean; + isDisabled: boolean; + label: string; +}; + +function ToolbarModeSelector({ commandsManager: _commandsManager, servicesManager }) { + const extensionManager = servicesManager.services.customizationService.extensionManager; + const { t } = useTranslation('ToolbarModeSelector'); + + const labels = useMemo( + () => ({ + browseModes: t('Browse modes'), + modes: t('Modes'), + loadingMetadata: t('Loading study metadata for modes…'), + unableToEvaluate: t('Unable to evaluate this mode'), + currentMode: t('Current mode'), + noModesAvailable: t('No modes available'), + }), + [t] + ); + + const location = useLocation(); + const preservedSearch = usePreservedViewerSearch(location.search); + + const [appConfig] = useAppConfig(); + const loadedModes = appConfig?.loadedModes || []; + const groupEnabledModesFirst = appConfig?.groupEnabledModesFirst === true; + + const imageViewer = useImageViewer(); + const StudyInstanceUIDs = imageViewer?.StudyInstanceUIDs; + const primaryUid = Array.isArray(StudyInstanceUIDs) ? StudyInstanceUIDs[0] : undefined; + + const [studyEnvelope, setStudyEnvelope] = useState(null); + const [metadataLoadFinished, setMetadataLoadFinished] = useState(false); + const [open, setOpen] = useState(false); + + const dataSource = useMemo( + () => + extensionManager.getActiveDataSourceOrNull?.() ?? extensionManager.getActiveDataSource?.()?.[0], + [extensionManager] + ); + + useEffect(() => { + let cancelled = false; + + setMetadataLoadFinished(false); + + async function load() { + if (!primaryUid || !dataSource) { + setStudyEnvelope(null); + setMetadataLoadFinished(true); + return; + } + + const env = await fetchStudyEnvelope(primaryUid, dataSource); + + if (!cancelled) { + setStudyEnvelope(env); + setMetadataLoadFinished(true); + } + } + + load(); + + return () => { + cancelled = true; + }; + }, [primaryUid, dataSource]); + + const modesForToolbar = useMemo( + () => loadedModes.filter(m => !m.hide && !HIDDEN_MODE_IDS.has(m.id)), + [loadedModes] + ); + + const comparableModesList = useMemo(() => { + if (!studyEnvelope || !modesForToolbar?.length) { + return [...modesForToolbar]; + } + + const list = [...modesForToolbar]; + + if (groupEnabledModesFirst && studyEnvelope) { + list.sort((a, b) => { + const validA = modeIsValidForOrdering(a, studyEnvelope); + const validB = modeIsValidForOrdering(b, studyEnvelope); + return Number(validB) - Number(validA); + }); + } + + return list; + }, [groupEnabledModesFirst, modesForToolbar, studyEnvelope]); + + const buildHrefForMode = useCallback( + (routeName: string) => { + const dsSuffix = getDataSourcePathSegment(location.pathname, loadedModes, extensionManager); + const pathname = `/${routeName}${dsSuffix ? `/${dsSuffix}` : ''}`; + return { pathname, search: preservedSearch }; + }, + [extensionManager, loadedModes, location.pathname, preservedSearch] + ); + + const closePopover = useCallback(() => { + setOpen(false); + }, []); + + const modeMenuRows = useMemo((): ToolbarMenuRow[] => { + if (!studyEnvelope) { + return []; + } + const rows: ToolbarMenuRow[] = []; + for (const mode of comparableModesList) { + const validity = evaluateModeValidity(mode, studyEnvelope, labels.unableToEvaluate); + if (!validity || validity.valid === null) { + continue; + } + const routeName = mode.routeName; + if (!routeName) { + continue; + } + const modeHref = buildHrefForMode(routeName); + const isCurrentRoute = location.pathname === modeHref.pathname; + const isDisabled = validity.valid !== true || isCurrentRoute; + const label = mode.displayName || routeName; + rows.push({ mode, validity, modeHref, isCurrentRoute, isDisabled, label }); + } + return rows; + }, [buildHrefForMode, comparableModesList, labels.unableToEvaluate, location.pathname, studyEnvelope]); + + if (modesForToolbar.length <= 1) { + return null; + } + + if (!primaryUid) { + return null; + } + + if (!studyEnvelope) { + if (!metadataLoadFinished) { + return ( + + + + + +

{labels.loadingMetadata}

+
+
+ ); + } + + return null; + } + + return ( + + + + + + + + +

{labels.browseModes}

+
+
+ +
+

{labels.modes}

+
+ +
    + {modeMenuRows.length === 0 ? +
  • +

    {labels.noModesAvailable}

    +
  • + : modeMenuRows.map( + ({ + mode: { routeName }, + validity, + modeHref, + isCurrentRoute, + isDisabled, + label, + }) => ( +
  • + {!isDisabled ? + + {label} + + + : isCurrentRoute ? +
    + + {label} + + +
    + : validity.description ? + + +
    + {label} +
    +
    + +

    {validity.description}

    +
    +
    + : ( +
    + {label} +
    + ) + } +
  • + ) + ) + } +
+
+
+ ); +} + +ToolbarModeSelector.propTypes = { + commandsManager: PropTypes.instanceOf(CommandsManager), + servicesManager: PropTypes.shape({ + services: PropTypes.shape({ + customizationService: PropTypes.object.isRequired, + }).isRequired, + }).isRequired, +}; + +export default ToolbarModeSelector; diff --git a/extensions/default/src/Toolbar/index.ts b/extensions/default/src/Toolbar/index.ts index b53d478dc39..dadbf9ae6db 100644 --- a/extensions/default/src/Toolbar/index.ts +++ b/extensions/default/src/Toolbar/index.ts @@ -5,3 +5,4 @@ export * from './ToolRowWrapper'; export * from './ToolBoxWrapper'; export * from './ToolbarDivider'; export * from './ToolbarLayoutSelector'; +export { default as ToolbarModeSelector } from './ToolbarModeSelector'; diff --git a/extensions/default/src/getToolbarModule.tsx b/extensions/default/src/getToolbarModule.tsx index 0c295accf80..a3e37b71f1c 100644 --- a/extensions/default/src/getToolbarModule.tsx +++ b/extensions/default/src/getToolbarModule.tsx @@ -1,6 +1,7 @@ import { utils } from '@ohif/ui-next'; import ToolbarLayoutSelectorWithServices from './Toolbar/ToolbarLayoutSelector'; +import ToolbarModeSelectorWithServices from './Toolbar/ToolbarModeSelector'; // legacy import { ProgressDropdownWithService } from './Components/ProgressDropdownWithService'; @@ -42,6 +43,11 @@ export default function getToolbarModule({ commandsManager, servicesManager }: w defaultComponent: props => ToolbarLayoutSelectorWithServices({ ...props, commandsManager, servicesManager }), }, + { + name: 'ohif.modeSelector', + defaultComponent: props => + ToolbarModeSelectorWithServices({ ...props, commandsManager, servicesManager }), + }, { name: 'ohif.progressDropdown', defaultComponent: ProgressDropdownWithService, diff --git a/extensions/default/src/utils/modeSelectorUtils.ts b/extensions/default/src/utils/modeSelectorUtils.ts new file mode 100644 index 00000000000..3bf70b52223 --- /dev/null +++ b/extensions/default/src/utils/modeSelectorUtils.ts @@ -0,0 +1,199 @@ +import { useMemo } from 'react'; + +import { type ExtensionManager, DicomMetadataStore, utils } from '@ohif/core'; +import { preserveQueryParameters } from '@ohif/app'; + +const { formatPN } = utils; + +/** Fields read from `query.studies.search` responses; remainder spread into `study`. */ +type StudySearchRow = { modalities?: string } & Record; + +/** Data source subset used when calling `studies.search` to populate the envelope. */ +export type DataSourceWithStudySearch = { + query?: { + studies?: { + search?: (params: { studyInstanceUid: string }) => Promise | StudySearchRow[]; + }; + }; +}; + +/** Normalized modalities string plus study fields for toolbar mode validity. */ +export type StudyEnvelope = { + modalitiesToCheck: string; + study: Record; +}; + +/** Argument for mode `isValidMode`. */ +export type ModeValidityPayload = { + modalities: string; + study: Record; +}; + +/** Result shape from mode `isValidMode`. */ +export type ModeValidityResult = { + valid: boolean; + description?: string; +}; + +/** OHIF mode object carrying an optional `isValidMode` callback. */ +export type ModeWithValidityChecker = { + isValidMode?: (this: unknown, payload: ModeValidityPayload) => ModeValidityResult; +}; + +/** App-config `loadedModes` entry (`routeName` only). */ +export type LoadedModeRouteHint = { + routeName?: string | null | undefined; +}; + +type ExtensionManagerDataSources = Pick; + +/** + * Normalizes a modalities string for comparison (e.g. path-like separators are unified). + */ +export function normalizeModalitiesString(raw?: string): string { + return (raw || '').replaceAll('/', '\\'); +} + +/** + * Loads study metadata for the mode selector: tries the active data source search, then falls back + * to `DicomMetadataStore` and builds modalities plus a study payload for validity checks. + */ +export async function fetchStudyEnvelope( + StudyInstanceUID: string, + dataSource: DataSourceWithStudySearch | null | undefined +): Promise { + try { + // Call as a method on query.studies so `this` inside search is bound (do not extract the function). + if (dataSource?.query?.studies?.search) { + const rows = await dataSource.query.studies.search({ studyInstanceUid: StudyInstanceUID }); + const row = rows?.[0]; + if (row) { + return { + modalitiesToCheck: normalizeModalitiesString(row.modalities), + study: { ...row }, + }; + } + } + } catch (_e) { + // Fallback to locally loaded metadata + } + + const meta = DicomMetadataStore.getStudy(StudyInstanceUID); + if (!meta?.series?.length) { + return null; + } + + const modalitySet = new Set(); + let numInstances = 0; + + meta.series.forEach(series => { + if (series?.instances?.length) { + const rawModality = series.instances[0].Modality; + if (rawModality != null && `${rawModality}`.trim() !== '') { + modalitySet.add(String(rawModality)); + } + numInstances += series.instances.length; + } + }); + + const modalitiesStr = [...modalitySet].sort().join('/'); + const modalitiesNormalized = normalizeModalitiesString(modalitiesStr); + const firstSeriesWithInstance = meta.series.find(s => s?.instances?.length); + const inst0 = firstSeriesWithInstance?.instances?.[0]; + const studyPayload = { + studyInstanceUid: StudyInstanceUID, + modalities: modalitiesNormalized, + mrn: inst0?.PatientID, + instances: numInstances, + description: inst0?.StudyDescription, + date: inst0?.StudyDate, + time: inst0?.StudyTime, + accession: inst0?.AccessionNumber, + patientName: inst0?.PatientName ? formatPN(inst0.PatientName) : '', + studyInstanceUID: StudyInstanceUID, + StudyInstanceUID, + }; + + return { + modalitiesToCheck: modalitiesNormalized, + study: studyPayload, + }; +} + +/** + * Whether a mode should be included when ordering/sorting mode options, using `isValidMode` when present. + * Returns true if the mode has no checker or if the checker reports valid. + */ +export function modeIsValidForOrdering(mode: ModeWithValidityChecker, studyEnvelope: StudyEnvelope): boolean { + if (typeof mode.isValidMode !== 'function') { + return true; + } + try { + return !!mode.isValidMode.call(mode, { + modalities: studyEnvelope.modalitiesToCheck, + study: studyEnvelope.study, + })?.valid; + } catch (_e) { + return false; + } +} + +/** + * Runs a mode's `isValidMode` for UI messaging; on success returns its result, on throw returns invalid with a fallback message. + */ +export function evaluateModeValidity( + mode: ModeWithValidityChecker, + studyEnvelope: StudyEnvelope, + unevaluableMessage: string +): ModeValidityResult { + if (typeof mode.isValidMode !== 'function') { + return { valid: true }; + } + try { + return mode.isValidMode.call(mode, { + modalities: studyEnvelope.modalitiesToCheck, + study: studyEnvelope.study, + }); + } catch (_e) { + return { valid: false, description: unevaluableMessage }; + } +} + +/** + * From the current URL path, returns the segment that names the data source (the piece after the mode route), if any. + */ +export function getDataSourcePathSegment( + locationPathname: string, + loadedModes: ReadonlyArray | undefined, + extensionManager: ExtensionManagerDataSources | null | undefined +): string | undefined { + const routeNames = new Set((loadedModes || []).filter(Boolean).map(m => m?.routeName).filter(Boolean)); + const segs = locationPathname.split('/').filter(Boolean); + + const modeSegmentIndex = segs.findIndex(seg => routeNames.has(seg)); + if (modeSegmentIndex === -1 || modeSegmentIndex + 1 >= segs.length) { + return undefined; + } + + const candidate = segs[modeSegmentIndex + 1]; + if ( + candidate && + !routeNames.has(candidate) && + extensionManager?.getDataSources?.(candidate)?.length + ) { + return candidate; + } + return undefined; +} + +/** + * React hook: builds the search string to append to mode-switch links while keeping viewer-wide query params. + */ +export function usePreservedViewerSearch(locationSearch: string): string { + return useMemo(() => { + const next = new URLSearchParams(locationSearch); + preserveQueryParameters(next); + const s = next.toString(); + return s ? `?${s}` : ''; + }, [locationSearch]); +} diff --git a/modes/basic-dev-mode/src/index.ts b/modes/basic-dev-mode/src/index.ts index c1663580e4c..4318b92de59 100644 --- a/modes/basic-dev-mode/src/index.ts +++ b/modes/basic-dev-mode/src/index.ts @@ -100,6 +100,7 @@ function modeFactory({ modeConfiguration }) { 'Zoom', 'WindowLevel', 'Pan', + 'Mode', 'Layout', 'MoreTools', ]); diff --git a/modes/basic-dev-mode/src/toolbarButtons.ts b/modes/basic-dev-mode/src/toolbarButtons.ts index 5742f994086..63552248d71 100644 --- a/modes/basic-dev-mode/src/toolbarButtons.ts +++ b/modes/basic-dev-mode/src/toolbarButtons.ts @@ -146,6 +146,13 @@ const toolbarButtons: Button[] = [ ], }, }, + { + id: 'Mode', + uiType: 'ohif.modeSelector', + props: { + evaluate: 'evaluate.action', + }, + }, { id: 'Layout', uiType: 'ohif.layoutSelector', diff --git a/modes/basic-test-mode/src/index.ts b/modes/basic-test-mode/src/index.ts index b8a9837572e..6a6406bc834 100644 --- a/modes/basic-test-mode/src/index.ts +++ b/modes/basic-test-mode/src/index.ts @@ -98,6 +98,7 @@ function modeFactory() { 'WindowLevelGroup', 'Pan', 'Capture', + 'Mode', 'Layout', 'MPR', 'Crosshairs', diff --git a/modes/basic-test-mode/src/toolbarButtons.ts b/modes/basic-test-mode/src/toolbarButtons.ts index 3c58935de77..a911fddff3a 100644 --- a/modes/basic-test-mode/src/toolbarButtons.ts +++ b/modes/basic-test-mode/src/toolbarButtons.ts @@ -366,6 +366,13 @@ const toolbarButtons: Button[] = [ ], }, }, + { + id: 'Mode', + uiType: 'ohif.modeSelector', + props: { + evaluate: 'evaluate.action', + }, + }, { id: 'Layout', uiType: 'ohif.layoutSelector', diff --git a/modes/basic/src/index.tsx b/modes/basic/src/index.tsx index 8fcc859ad5c..a04e8209f64 100644 --- a/modes/basic/src/index.tsx +++ b/modes/basic/src/index.tsx @@ -216,6 +216,7 @@ export const toolbarSections = { 'TrackballRotate', 'WindowLevel', 'Capture', + 'Mode', 'Layout', 'Crosshairs', 'MoreTools', diff --git a/modes/basic/src/toolbarButtons.ts b/modes/basic/src/toolbarButtons.ts index 005674e58d0..da06cb2405a 100644 --- a/modes/basic/src/toolbarButtons.ts +++ b/modes/basic/src/toolbarButtons.ts @@ -631,6 +631,13 @@ const toolbarButtons: Button[] = [ ], }, }, + { + id: 'Mode', + uiType: 'ohif.modeSelector', + props: { + evaluate: 'evaluate.action', + }, + }, { id: 'Layout', uiType: 'ohif.layoutSelector', diff --git a/modes/preclinical-4d/src/getWorkflowSettings.ts b/modes/preclinical-4d/src/getWorkflowSettings.ts index e5e1e3362b0..6c11b584022 100644 --- a/modes/preclinical-4d/src/getWorkflowSettings.ts +++ b/modes/preclinical-4d/src/getWorkflowSettings.ts @@ -14,7 +14,7 @@ function getDefaultButtons({ toolbarService }) { return [ { buttonSection: toolbarService.sections.primary, - buttons: ['MeasurementTools', 'Zoom', 'WindowLevel', 'Crosshairs', 'Pan'], + buttons: ['MeasurementTools', 'Zoom', 'Mode', 'WindowLevel', 'Crosshairs', 'Pan'], }, { buttonSection: 'MeasurementTools', diff --git a/modes/preclinical-4d/src/toolbarButtons.tsx b/modes/preclinical-4d/src/toolbarButtons.tsx index f2d06940a29..b2d63871002 100644 --- a/modes/preclinical-4d/src/toolbarButtons.tsx +++ b/modes/preclinical-4d/src/toolbarButtons.tsx @@ -201,6 +201,13 @@ const toolbarButtons = [ ], }, }, + { + id: 'Mode', + uiType: 'ohif.modeSelector', + props: { + evaluate: 'evaluate.action', + }, + }, { id: 'Layout', uiType: 'ohif.layoutSelector', diff --git a/modes/segmentation/src/index.tsx b/modes/segmentation/src/index.tsx index 51f08e2d767..5e30a76661d 100644 --- a/modes/segmentation/src/index.tsx +++ b/modes/segmentation/src/index.tsx @@ -46,6 +46,7 @@ function modeFactory({ modeConfiguration }) { 'Zoom', 'TrackballRotate', 'Capture', + 'Mode', 'Layout', 'Crosshairs', 'MoreTools', diff --git a/modes/segmentation/src/toolbarButtons.ts b/modes/segmentation/src/toolbarButtons.ts index ecf2a5b36b8..81cc2e2bb4b 100644 --- a/modes/segmentation/src/toolbarButtons.ts +++ b/modes/segmentation/src/toolbarButtons.ts @@ -266,6 +266,13 @@ export const toolbarButtons: Button[] = [ ], }, }, + { + id: 'Mode', + uiType: 'ohif.modeSelector', + props: { + evaluate: 'evaluate.action', + }, + }, { id: 'Layout', uiType: 'ohif.layoutSelector', diff --git a/modes/tmtv/src/index.ts b/modes/tmtv/src/index.ts index 33f74a7983d..890c7740f9c 100644 --- a/modes/tmtv/src/index.ts +++ b/modes/tmtv/src/index.ts @@ -92,6 +92,7 @@ function modeFactory({ modeConfiguration }) { toolbarService.updateSection(toolbarService.sections.primary, [ 'MeasurementTools', 'Zoom', + 'Mode', 'Pan', 'WindowLevel', 'Crosshairs', diff --git a/modes/tmtv/src/toolbarButtons.ts b/modes/tmtv/src/toolbarButtons.ts index c5d8633e168..766ad543731 100644 --- a/modes/tmtv/src/toolbarButtons.ts +++ b/modes/tmtv/src/toolbarButtons.ts @@ -153,6 +153,13 @@ const toolbarButtons = [ evaluate: 'evaluate.cornerstoneTool', }, }, + { + id: 'Mode', + uiType: 'ohif.modeSelector', + props: { + evaluate: 'evaluate.action', + }, + }, { id: 'WindowLevel', uiType: 'ohif.toolButton', diff --git a/modes/usAnnotation/src/index.ts b/modes/usAnnotation/src/index.ts index 5535373aba6..5c6c6c7c1d8 100644 --- a/modes/usAnnotation/src/index.ts +++ b/modes/usAnnotation/src/index.ts @@ -113,6 +113,7 @@ function modeFactory({ modeConfiguration }) { 'TrackballRotate', 'WindowLevel', 'Capture', + 'Mode', 'Layout', 'Crosshairs', 'MoreTools', diff --git a/modes/usAnnotation/src/toolbarButtons.ts b/modes/usAnnotation/src/toolbarButtons.ts index c91382648ad..e074ef58f43 100644 --- a/modes/usAnnotation/src/toolbarButtons.ts +++ b/modes/usAnnotation/src/toolbarButtons.ts @@ -648,6 +648,13 @@ const toolbarButtons: Button[] = [ ], }, }, + { + id: 'Mode', + uiType: 'ohif.modeSelector', + props: { + evaluate: 'evaluate.action', + }, + }, { id: 'Layout', uiType: 'ohif.layoutSelector', diff --git a/platform/i18n/src/locales/en-US/ToolbarModeSelector.json b/platform/i18n/src/locales/en-US/ToolbarModeSelector.json new file mode 100644 index 00000000000..1b22fc1d418 --- /dev/null +++ b/platform/i18n/src/locales/en-US/ToolbarModeSelector.json @@ -0,0 +1,8 @@ +{ + "Browse modes": "Browse modes", + "Modes": "Modes", + "Loading study metadata for modes…": "Loading study metadata for modes…", + "Current mode": "Current mode", + "Unable to evaluate this mode": "Unable to evaluate this mode", + "No modes available": "No modes available" +} diff --git a/platform/i18n/src/locales/en-US/index.js b/platform/i18n/src/locales/en-US/index.js index eab6880903f..762416cbe15 100644 --- a/platform/i18n/src/locales/en-US/index.js +++ b/platform/i18n/src/locales/en-US/index.js @@ -25,6 +25,7 @@ import WindowLevelActionMenu from './WindowLevelActionMenu.json'; import CaptureViewportModal from './CaptureViewportModal.json'; import Hps from './Hps.json'; import ToolbarLayoutSelector from './ToolbarLayoutSelector.json'; +import ToolbarModeSelector from './ToolbarModeSelector.json'; import Tools from './Tools.json'; import Onboarding from './Onboarding.json'; import Colormaps from './Colormaps.json'; @@ -61,6 +62,7 @@ export default { CaptureViewportModal, Hps, ToolbarLayoutSelector, + ToolbarModeSelector, Tools, Onboarding, Colormaps, diff --git a/platform/i18n/src/locales/fr/ToolbarModeSelector.json b/platform/i18n/src/locales/fr/ToolbarModeSelector.json new file mode 100644 index 00000000000..5f6073ee9f2 --- /dev/null +++ b/platform/i18n/src/locales/fr/ToolbarModeSelector.json @@ -0,0 +1,8 @@ +{ + "Browse modes": "Parcourir les modes", + "Modes": "Modes", + "Loading study metadata for modes…": "Chargement des métadonnées de l'étude pour les modes…", + "Current mode": "Mode actuel", + "Unable to evaluate this mode": "Impossible d'évaluer ce mode", + "No modes available": "Aucun mode disponible" +} diff --git a/platform/i18n/src/locales/fr/index.js b/platform/i18n/src/locales/fr/index.js index 7c695a58d42..03c3be9e28e 100644 --- a/platform/i18n/src/locales/fr/index.js +++ b/platform/i18n/src/locales/fr/index.js @@ -21,6 +21,7 @@ import Local from './Local.json'; import ErrorBoundary from './ErrorBoundary.json'; import Hps from './Hps.json'; import ToolbarLayoutSelector from './ToolbarLayoutSelector.json'; +import ToolbarModeSelector from './ToolbarModeSelector.json'; import WindowLevelActionMenu from './WindowLevelActionMenu.json'; import CaptureViewportModal from './CaptureViewportModal.json'; import Tools from './Tools.json'; @@ -56,6 +57,7 @@ export default { ErrorBoundary, Hps, ToolbarLayoutSelector, + ToolbarModeSelector, WindowLevelActionMenu, CaptureViewportModal, Tools, diff --git a/platform/i18n/src/locales/nl/ToolbarModeSelector.json b/platform/i18n/src/locales/nl/ToolbarModeSelector.json new file mode 100644 index 00000000000..830edc5c0c7 --- /dev/null +++ b/platform/i18n/src/locales/nl/ToolbarModeSelector.json @@ -0,0 +1,8 @@ +{ + "Browse modes": "Modi bekijken", + "Modes": "Modi", + "Loading study metadata for modes…": "Studiemetadata voor modi laden…", + "Current mode": "Huidige modus", + "Unable to evaluate this mode": "Kan deze modus niet evalueren", + "No modes available": "Geen modi beschikbaar" +} diff --git a/platform/i18n/src/locales/nl/index.js b/platform/i18n/src/locales/nl/index.js index d1703aca0af..2e2fc70984c 100644 --- a/platform/i18n/src/locales/nl/index.js +++ b/platform/i18n/src/locales/nl/index.js @@ -25,6 +25,7 @@ import WindowLevelActionMenu from './WindowLevelActionMenu.json'; import CaptureViewportModal from './CaptureViewportModal.json'; import Hps from './Hps.json'; import ToolbarLayoutSelector from './ToolbarLayoutSelector.json'; +import ToolbarModeSelector from './ToolbarModeSelector.json'; import Tools from './Tools.json'; import Onboarding from './Onboarding.json'; import Colormaps from './Colormaps.json'; @@ -60,6 +61,7 @@ export default { CaptureViewportModal, Hps, ToolbarLayoutSelector, + ToolbarModeSelector, Tools, Onboarding, Colormaps, diff --git a/platform/i18n/src/locales/test-LNG/ToolbarModeSelector.json b/platform/i18n/src/locales/test-LNG/ToolbarModeSelector.json new file mode 100644 index 00000000000..ea140fda7da --- /dev/null +++ b/platform/i18n/src/locales/test-LNG/ToolbarModeSelector.json @@ -0,0 +1,8 @@ +{ + "Browse modes": "Test Browse modes", + "Modes": "Test Modes", + "Loading study metadata for modes…": "Test Loading study metadata for modes…", + "Current mode": "Test Current mode", + "Unable to evaluate this mode": "Test Unable to evaluate this mode", + "No modes available": "Test No modes available" +} diff --git a/platform/i18n/src/locales/test-LNG/index.js b/platform/i18n/src/locales/test-LNG/index.js index 64059c1bf11..7decbba28d8 100644 --- a/platform/i18n/src/locales/test-LNG/index.js +++ b/platform/i18n/src/locales/test-LNG/index.js @@ -33,6 +33,7 @@ import CaptureViewportModal from './CaptureViewportModal.json'; import Tools from './Tools.json'; import Hps from './Hps.json'; import ToolbarLayoutSelector from './ToolbarLayoutSelector.json'; +import ToolbarModeSelector from './ToolbarModeSelector.json'; import USAnnotationPanel from './USAnnotationPanel.json'; export default { @@ -72,6 +73,7 @@ export default { Tools, Hps, ToolbarLayoutSelector, + ToolbarModeSelector, USAnnotationPanel, }, }; diff --git a/platform/i18n/src/locales/zh/ToolbarModeSelector.json b/platform/i18n/src/locales/zh/ToolbarModeSelector.json new file mode 100644 index 00000000000..3d145203f14 --- /dev/null +++ b/platform/i18n/src/locales/zh/ToolbarModeSelector.json @@ -0,0 +1,8 @@ +{ + "Browse modes": "浏览模式", + "Modes": "模式", + "Loading study metadata for modes…": "正在为模式加载检查元数据…", + "Current mode": "当前模式", + "Unable to evaluate this mode": "无法评估此模式", + "No modes available": "没有可用模式" +} diff --git a/platform/i18n/src/locales/zh/index.js b/platform/i18n/src/locales/zh/index.js index 43a1f821784..cef800419a5 100644 --- a/platform/i18n/src/locales/zh/index.js +++ b/platform/i18n/src/locales/zh/index.js @@ -23,6 +23,7 @@ import WindowLevelActionMenu from './WindowLevelActionMenu.json'; import CaptureViewportModal from './CaptureViewportModal.json'; import Hps from './Hps.json'; import ToolbarLayoutSelector from './ToolbarLayoutSelector.json'; +import ToolbarModeSelector from './ToolbarModeSelector.json'; import Tools from './Tools.json'; export default { @@ -52,6 +53,7 @@ export default { CaptureViewportModal, Hps, ToolbarLayoutSelector, + ToolbarModeSelector, Tools, }, };