feat(actions): simplify action corner api through the toolbarService (#5033)

This commit is contained in:
Alireza authored and GitHub committed 2025-05-15 12:08:12 -04:00
1 parent f71b88e639
commit 7c15bb8901
246 files changed
+7930 -4270

No files matched your search

+2 -2
View File
@@ -37,8 +37,8 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/core": "^3.12.2",
"@cornerstonejs/dicom-image-loader": "^3.12.2",
"@cornerstonejs/core": "^3.15.1",
"@cornerstonejs/dicom-image-loader": "^3.15.1",
"@ohif/ui": "3.11.0-beta.32",
"cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21"
@@ -388,6 +388,10 @@ export default class ExtensionManager extends PubSubService {
return this.dataSourceMap[this._activeDataSourceName];
};
getActiveDataSourceOrNull = () => {
return this.dataSourceMap[this._activeDataSourceName]?.[0] ?? null;
};
/**
* Gets the data source definition for the given data source name.
* If no data source name is provided, the active data source definition is
+5
View File
@@ -0,0 +1,5 @@
export * from './useToolbar';
export * from './types';
export * from './useViewportRef';
export * from './useViewportSize';
export * from './useViewportMousePosition';
+27
View File
@@ -0,0 +1,27 @@
export interface ToolbarButtonActions {
// Lock/Unlock actions
lockItem: (itemId: string, viewportId?: string) => void;
unlockItem: (itemId: string, viewportId?: string) => void;
toggleLock: (itemId: string, viewportId?: string) => void;
isItemLocked: (itemId: string, viewportId?: string) => boolean;
// Visibility actions
showItem: (itemId: string, viewportId?: string) => void;
hideItem: (itemId: string, viewportId?: string) => void;
toggleVisibility: (itemId: string, viewportId?: string) => void;
isItemVisible: (itemId: string, viewportId?: string) => boolean;
// Open/Close actions (for menus)
openItem: (itemId: string, viewportId?: string) => void;
closeItem: (itemId: string, viewportId?: string) => void;
closeAllItems: (viewportId?: string) => void;
isItemOpen: (itemId: string, viewportId?: string) => boolean;
// Evaluation
evaluateButtonForViewport: (itemId: string, viewportId?: string) => any;
}
export interface ToolbarHookReturn extends ToolbarButtonActions {
toolbarButtons: any[]; // The display representation of toolbar buttons
onInteraction: (args: any) => void;
}
@@ -1,6 +1,6 @@
import { useEffect, useState, useCallback } from 'react';
import { DisplaySet } from '../types';
import { useSystem } from '../';
/**
* Hook that listens for changes in the active viewport and its display sets.
* It returns the display sets associated with the active viewport.
@@ -8,7 +8,8 @@ import { DisplaySet } from '../types';
* @param servicesManager - Services manager instance
* @returns Array of display sets for the active viewport
*/
const useActiveViewportDisplaySets = ({ servicesManager }): DisplaySet[] => {
const useActiveViewportDisplaySets = (): DisplaySet[] => {
const { servicesManager } = useSystem();
const { displaySetService, viewportGridService } = servicesManager.services;
// Move this function outside useEffect and memoize it
const getDisplaySetsForViewport = useCallback(
+173 -7
View File
@@ -1,20 +1,26 @@
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useState, useMemo } from 'react';
import { useSystem } from '../contextProviders/SystemProvider';
import { ToolbarHookReturn } from './types';
export function useToolbar({ buttonSection = 'primary' }: withAppTypes) {
export function useToolbar({ buttonSection = 'primary' }: withAppTypes): ToolbarHookReturn {
const { commandsManager, servicesManager } = useSystem();
const { toolbarService, viewportGridService } = servicesManager.services;
const { EVENTS } = toolbarService;
// Store all buttons returned by the toolbar service
const [toolbarButtons, setToolbarButtons] = useState(
toolbarService.getButtonSection(buttonSection as string)
toolbarService.getButtonSection(buttonSection as string).filter(Boolean)
);
// Store state of open/closed menu items
// Note: We keep this in local state to avoid re-evaluating the toolbar on every interaction
const [openItemIds, setOpenItemIds] = useState<Record<string, boolean>>({});
// Callback function for handling toolbar interactions
const onInteraction = useCallback(
args => {
args.event?.stopPropagation?.();
const viewportId = viewportGridService.getActiveViewportId();
const viewportId = args.viewportId || viewportGridService.getActiveViewportId();
const refreshProps = { viewportId };
const buttonProps = toolbarService.getButtonProps(args.itemId);
@@ -58,15 +64,17 @@ export function useToolbar({ buttonSection = 'primary' }: withAppTypes) {
buttonProps.commands = allCommands;
}
toolbarService.recordInteraction({ ...args, ...buttonProps }, { refreshProps });
},
[toolbarService, viewportGridService, toolbarButtons]
[toolbarService, viewportGridService]
);
// Effect to handle toolbar modification events
useEffect(() => {
const handleToolbarModified = () => {
setToolbarButtons(toolbarService.getButtonSection(buttonSection as string)?.filter(Boolean));
const buttons = toolbarService.getButtonSection(buttonSection as string)?.filter(Boolean);
setToolbarButtons(buttons);
};
const subs = [EVENTS.TOOL_BAR_MODIFIED, EVENTS.TOOL_BAR_STATE_MODIFIED].map(event => {
@@ -96,5 +104,163 @@ export function useToolbar({ buttonSection = 'primary' }: withAppTypes) {
return () => subscriptions.forEach(sub => sub.unsubscribe());
}, [viewportGridService, toolbarService]);
return { toolbarButtons, onInteraction };
// Action API for toolbar buttons
const actions = useMemo(() => {
return {
// Lock/Unlock actions
lockItem: (itemId: string, viewportId?: string) => {
const targetViewportId = viewportId || viewportGridService.getActiveViewportId();
const button = toolbarService.getButton(itemId);
if (button) {
// Set isLocked flag in button metadata
button.props = {
...button.props,
isLocked: true,
};
// Re-evaluate the button
toolbarService.refreshToolbarState({ viewportId: targetViewportId, itemId });
}
},
unlockItem: (itemId: string, viewportId?: string) => {
const targetViewportId = viewportId || viewportGridService.getActiveViewportId();
const button = toolbarService.getButton(itemId);
if (button) {
// Set isLocked flag in button metadata
button.props = {
...button.props,
isLocked: false,
};
// Re-evaluate the button
toolbarService.refreshToolbarState({ viewportId: targetViewportId, itemId });
}
},
toggleLock: (itemId: string, viewportId?: string) => {
const targetViewportId = viewportId || viewportGridService.getActiveViewportId();
const button = toolbarService.getButton(itemId);
if (button) {
// Toggle isLocked flag in button metadata
button.props = {
...button.props,
isLocked: !button.props.isLocked,
};
// Re-evaluate the button
toolbarService.refreshToolbarState({ viewportId: targetViewportId, itemId });
}
},
isItemLocked: (itemId: string, viewportId?: string): boolean => {
const button = toolbarService.getButton(itemId);
return button?.props?.isLocked === true;
},
// Visibility actions - controlled by evaluator functions in toolbar items
showItem: (itemId: string, viewportId?: string) => {
const targetViewportId = viewportId || viewportGridService.getActiveViewportId();
const button = toolbarService.getButton(itemId);
if (button) {
// Set isVisible flag in button metadata
button.props = {
...button.props,
isVisible: true,
};
// Re-evaluate the button
toolbarService.refreshToolbarState({ viewportId: targetViewportId, itemId });
}
},
hideItem: (itemId: string, viewportId?: string) => {
const targetViewportId = viewportId || viewportGridService.getActiveViewportId();
const button = toolbarService.getButton(itemId);
if (button) {
// Set isVisible flag in button metadata
button.props = {
...button.props,
isVisible: false,
};
// Re-evaluate the button
toolbarService.refreshToolbarState({ viewportId: targetViewportId, itemId });
}
},
toggleVisibility: (itemId: string, viewportId?: string) => {
const targetViewportId = viewportId || viewportGridService.getActiveViewportId();
const button = toolbarService.getButton(itemId);
if (button) {
// Toggle isVisible flag in button metadata
button.props = {
...button.props,
isVisible: button.props.isVisible === false ? true : false,
};
// Re-evaluate the button
toolbarService.refreshToolbarState({ viewportId: targetViewportId, itemId });
}
},
isItemVisible: (itemId: string, viewportId?: string): boolean => {
const button = toolbarService.getButton(itemId);
// If isVisible is explicitly false, return false; otherwise return true
return button?.props?.isVisible !== false;
},
// Open/Close actions - managed in local state for performance
openItem: (itemId: string, viewportId?: string) => {
setOpenItemIds(prev => {
// Close all other items
const updated = {};
// Then set the current item to open
updated[itemId] = true;
return updated;
});
},
closeItem: (itemId: string, viewportId?: string) => {
setOpenItemIds(prev => ({
...prev,
[itemId]: false,
}));
},
closeAllItems: (viewportId?: string) => {
setOpenItemIds({});
},
isItemOpen: (itemId: string, viewportId?: string): boolean => {
return openItemIds[itemId] === true;
},
};
}, [viewportGridService, toolbarService, openItemIds]);
if (!toolbarButtons?.length) {
return {
toolbarButtons: [],
onInteraction,
...actions,
};
}
// filter out buttons that are disabled and have hideWhenDisabled set to true
const filteredToolbarButtons = toolbarButtons.filter(button => {
const props = button.componentProps;
return props.visible !== false;
});
return {
toolbarButtons: filteredToolbarButtons,
onInteraction,
...actions,
};
}
@@ -0,0 +1,67 @@
import { useState, useEffect } from 'react';
import { useViewportRef } from './useViewportRef';
import { useViewportSize } from './useViewportSize';
interface MousePosition {
x: number;
y: number;
isInViewport: boolean;
relativeY: number; // Position as percentage from top (0-1)
isInBottomPercentage: (percentage: number) => boolean;
}
function useViewportMousePosition(viewportId: string): MousePosition {
const viewportRef = useViewportRef(viewportId);
const { height, clientRect } = useViewportSize(viewportId);
const [mousePosition, setMousePosition] = useState<MousePosition>({
x: 0,
y: 0,
isInViewport: false,
relativeY: 0,
isInBottomPercentage: (percentage: number) => false,
});
useEffect(() => {
if (!viewportRef.current) {
return;
}
const handleMouseMove = (event: MouseEvent) => {
if (!clientRect) {
return;
}
// Get mouse position relative to viewport
const x = event.clientX - clientRect.left;
const y = event.clientY - clientRect.top;
const isInViewport = x >= 0 && x <= clientRect.width && y >= 0 && y <= clientRect.height;
const relativeY = Math.max(0, Math.min(1, y / height));
const isInBottomPercentage = (percentage: number) => {
return relativeY >= 1 - percentage / 100;
};
setMousePosition({
x,
y,
isInViewport,
relativeY,
isInBottomPercentage,
});
};
document.addEventListener('mousemove', handleMouseMove);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
};
}, [viewportRef, height, clientRect]);
return mousePosition;
}
export default useViewportMousePosition;
export { useViewportMousePosition };
+58
View File
@@ -0,0 +1,58 @@
import React, { createContext, useContext, useRef } from 'react';
type ViewportRefsContextType = {
registerViewport: (viewportId: string, element: HTMLElement) => void;
unregisterViewport: (viewportId: string) => void;
getViewportElement: (viewportId: string) => HTMLElement | null;
viewportRefs: Map<string, HTMLElement>;
};
// comment
const ViewportRefsContext = createContext<ViewportRefsContextType | undefined>(undefined);
export const ViewportRefsProvider = ({ children }: { children: React.ReactNode }) => {
const viewportRefsRef = useRef<Map<string, HTMLElement>>(new Map());
const registerViewport = (viewportId: string, element: HTMLElement) => {
viewportRefsRef.current.set(viewportId, element);
};
const unregisterViewport = (viewportId: string) => {
viewportRefsRef.current.delete(viewportId);
};
const getViewportElement = (viewportId: string): HTMLElement | null => {
return viewportRefsRef.current.get(viewportId) || null;
};
const contextValue: ViewportRefsContextType = {
registerViewport,
unregisterViewport,
getViewportElement,
viewportRefs: viewportRefsRef.current,
};
return React.createElement(ViewportRefsContext.Provider, { value: contextValue }, children);
};
export const useViewportRefs = () => {
const context = useContext(ViewportRefsContext);
if (context === undefined) {
throw new Error('useViewportRefs must be used within a ViewportRefsProvider');
}
return context;
};
export const useViewportRef = (viewportId: string) => {
const { registerViewport, unregisterViewport, getViewportElement } = useViewportRefs();
const ref = {
current: getViewportElement(viewportId),
register: (element: HTMLElement) => registerViewport(viewportId, element),
unregister: () => unregisterViewport(viewportId),
};
return ref;
};
@@ -0,0 +1,97 @@
import { useEffect, useState, useCallback, useMemo } from 'react';
import { useViewportRef } from './';
interface ViewportSize {
width: number;
height: number;
offsetLeft: number;
offsetTop: number;
clientRect: DOMRect | null;
isVisible: boolean;
}
/**
* Hook that provides viewport size dimensions and monitors for changes
* @param viewportId - The ID of the viewport to monitor
* @returns ViewportSize object containing width, height, and visibility info
*/
function useViewportSize(viewportId: string): ViewportSize {
const viewportElementRef = useViewportRef(viewportId);
const [size, setSize] = useState<ViewportSize>({
width: 0,
height: 0,
offsetLeft: 0,
offsetTop: 0,
clientRect: null,
isVisible: false,
});
// Update viewport dimensions
const updateViewportSize = useCallback(() => {
if (!viewportElementRef?.current) {
return;
}
const element = viewportElementRef.current;
const clientRect = element.getBoundingClientRect();
const newWidth = clientRect.width;
const newHeight = clientRect.height;
const newOffsetLeft = element.offsetLeft;
const newOffsetTop = element.offsetTop;
const newIsVisible = newWidth > 0 && newHeight > 0;
setSize(prevSize => {
if (
prevSize.width === newWidth &&
prevSize.height === newHeight &&
prevSize.offsetLeft === newOffsetLeft &&
prevSize.offsetTop === newOffsetTop &&
prevSize.isVisible === newIsVisible
) {
return prevSize;
}
return {
width: newWidth,
height: newHeight,
offsetLeft: newOffsetLeft,
offsetTop: newOffsetTop,
clientRect,
isVisible: newIsVisible,
};
});
}, [viewportElementRef]);
useEffect(() => {
if (!viewportId || !viewportElementRef?.current) {
return;
}
updateViewportSize();
const resizeObserver = new ResizeObserver(() => {
updateViewportSize();
});
resizeObserver.observe(viewportElementRef.current);
window.addEventListener('resize', updateViewportSize);
return () => {
window.removeEventListener('resize', updateViewportSize);
if (viewportElementRef.current) {
resizeObserver.unobserve(viewportElementRef.current);
}
resizeObserver.disconnect();
};
}, [viewportId, viewportElementRef, updateViewportSize]);
const memoizedSize = useMemo(() => size, [size]);
return memoizedSize;
}
export default useViewportSize;
export { useViewportSize };
+4 -3
View File
@@ -2,6 +2,7 @@ import { ExtensionManager, MODULE_TYPES } from './extensions';
import { ServiceProvidersManager, ServicesManager } from './services';
import classes, { CommandsManager, HotkeysManager } from './classes';
import { SystemContextProvider, useSystem } from './contextProviders/SystemProvider';
import { ViewportRefsProvider } from './hooks/useViewportRef';
import DICOMWeb from './DICOMWeb';
import errorHandler from './errorHandler.js';
@@ -13,7 +14,6 @@ import utils from './utils';
import defaults from './defaults';
import * as Types from './types';
import * as Enums from './enums';
import { useToolbar } from './hooks/useToolbar';
import {
CineService,
UIDialogService,
@@ -42,6 +42,8 @@ import { DisplaySetMessage, DisplaySetMessageList } from './services/DisplaySetS
import IWebApiDataSource from './DataSources/IWebApiDataSource';
import useActiveViewportDisplaySets from './hooks/useActiveViewportDisplaySets';
export * from './hooks';
const hotkeys = {
...utils.hotkeys,
defaults: { hotkeyBindings: defaults.hotkeyBindings },
@@ -86,7 +88,6 @@ const OHIF = {
pubSubServiceInterface,
PubSubService,
PanelService,
useToolbar,
useActiveViewportDisplaySets,
WorkflowStepsService,
StudyPrefetcherService,
@@ -101,6 +102,7 @@ export {
ServicesManager,
ServiceProvidersManager,
SystemContextProvider,
ViewportRefsProvider,
//
defaults,
utils,
@@ -137,7 +139,6 @@ export {
WorkflowStepsService,
StudyPrefetcherService,
useSystem,
useToolbar,
useActiveViewportDisplaySets,
};
@@ -2,13 +2,68 @@ import { CommandsManager } from '../../classes';
import { ExtensionManager } from '../../extensions';
import { PubSubService } from '../_shared/pubSubServiceInterface';
import type { RunCommand } from '../../types/Command';
import { Button, ButtonProps, EvaluateFunction, EvaluatePublic, NestedButtonProps } from './types';
import { Button, ButtonProps, EvaluateFunction, EvaluatePublic } from './types';
const EVENTS = {
TOOL_BAR_MODIFIED: 'event::toolBarService:toolBarModified',
TOOL_BAR_STATE_MODIFIED: 'event::toolBarService:toolBarStateModified',
};
/**
* Predefined toolbar sections used throughout the application
*/
export const TOOLBAR_SECTIONS = {
/**
* Main toolbar
*/
primary: 'primary',
/**
* Secondary toolbar
*/
secondary: 'secondary',
/**
* Viewport action menu sections
*/
viewportActionMenu: {
topLeft: 'viewportActionMenu.topLeft',
topRight: 'viewportActionMenu.topRight',
bottomLeft: 'viewportActionMenu.bottomLeft',
bottomRight: 'viewportActionMenu.bottomRight',
topMiddle: 'viewportActionMenu.topMiddle',
bottomMiddle: 'viewportActionMenu.bottomMiddle',
leftMiddle: 'viewportActionMenu.leftMiddle',
rightMiddle: 'viewportActionMenu.rightMiddle',
},
/**
* Measurement tools section
*/
measurementSection: 'measurementSection',
/**
* More tools section
*/
moreToolsSection: 'moreToolsSection',
/**
* Advanced rendering controls section
*/
advancedRenderingControlsSection: 'advancedRenderingControlsSection',
};
export enum ButtonLocation {
TopLeft = 0,
TopMiddle = 1,
TopRight = 2,
LeftMiddle = 3,
RightMiddle = 4,
BottomLeft = 5,
BottomMiddle = 6,
BottomRight = 7,
}
export default class ToolbarService extends PubSubService {
public static REGISTRATION = {
name: 'toolbarService',
@@ -18,6 +73,13 @@ export default class ToolbarService extends PubSubService {
},
};
/**
* Access to predefined toolbar sections for autocomplete support
*/
public get sections() {
return TOOLBAR_SECTIONS;
}
public static createButton(options: {
id: string;
label: string;
@@ -130,7 +192,7 @@ export default class ToolbarService extends PubSubService {
* @param buttons - The buttons to be added.
* @param replace - Flag indicating if any existing button with the same id as one being added should be replaced
*/
public addButtons(buttons: Button[], replace: boolean = false): void {
public register(buttons: Button[], replace: boolean = false): void {
buttons.forEach(button => {
if (replace || !this.state.buttons[button.id]) {
if (!button.props) {
@@ -231,7 +293,8 @@ export default class ToolbarService extends PubSubService {
* which buttons to evaluate based on the props
*/
public refreshToolbarState(refreshProps) {
const buttons = this.state.buttons;
const originalButtons = this.state.buttons;
const updatedButtons = { ...originalButtons };
const evaluationResults = new Map();
const evaluateButtonProps = (button, props, refreshProps) => {
@@ -239,6 +302,7 @@ export default class ToolbarService extends PubSubService {
const { disabled, disabledText, className, isActive } = evaluationResults.get(button.id);
return { ...props, disabled, disabledText, className, isActive };
} else {
const evaluateProps = props.evaluateProps;
const evaluated =
typeof props.evaluate === 'function'
? props.evaluate({ ...refreshProps, button })
@@ -247,6 +311,7 @@ export default class ToolbarService extends PubSubService {
...props,
...evaluated,
disabled: evaluated?.disabled || false,
visible: evaluateProps?.hideWhenDisabled && evaluated?.disabled ? false : true,
className: evaluated?.className || '',
isActive: evaluated?.isActive, // isActive will be undefined for buttons without this prop
};
@@ -255,7 +320,14 @@ export default class ToolbarService extends PubSubService {
}
};
Object.values(buttons).forEach(button => {
const updatedIds = new Set();
Object.values(originalButtons).forEach(button => {
// Note: do not re-evaluate buttons that have already been evaluated
// this will result in inconsistencies in the toolbar state
if (updatedIds.has(button.id)) {
return;
}
const hasSection = (button.props as NestedButtonProps)?.buttonSection;
if (!hasSection) {
@@ -263,10 +335,12 @@ export default class ToolbarService extends PubSubService {
const buttonProps = button.props as ButtonProps;
const updatedProps = evaluateButtonProps(button, buttonProps, refreshProps);
buttons[button.id] = {
updatedButtons[button.id] = {
...button,
props: updatedProps,
};
updatedIds.add(button.id);
} else {
let buttonProps = button.props as NestedButtonProps;
const { evaluate: groupEvaluate } = buttonProps;
@@ -288,17 +362,27 @@ export default class ToolbarService extends PubSubService {
}
toolButtonIds.forEach(buttonId => {
const button = buttons[buttonId];
const button = originalButtons[buttonId];
if (!button) {
return;
}
if (updatedIds.has(buttonId)) {
return;
}
const updatedProps = evaluateButtonProps(button, button.props, refreshProps);
buttons[buttonId] = {
updatedButtons[buttonId] = {
...button,
props: updatedProps,
};
updatedIds.add(buttonId);
});
}
});
this.setButtons(buttons);
this.setButtons(updatedButtons);
return this.state;
}
@@ -373,7 +457,7 @@ export default class ToolbarService extends PubSubService {
* @param {string} key - The key of the button section.
* @param {Array} buttons - The buttons to be added to the section.
*/
createButtonSection(key, buttons) {
updateSection(key, buttons) {
if (this.state.buttonSections[key]) {
this.state.buttonSections[key].push(
...buttons.filter(
@@ -443,7 +527,7 @@ export default class ToolbarService extends PubSubService {
}
const { id, uiType } = btn;
const { groupId } = btn.props as NestedButtonProps;
const { buttonSection } = btn.props;
const buttonTypes = this._getButtonUITypes();
@@ -457,7 +541,7 @@ export default class ToolbarService extends PubSubService {
return;
}
!groupId ? this.handleEvaluate(btn.props) : this.handleEvaluateNested(btn.props);
!buttonSection ? this.handleEvaluate(btn.props) : this.handleEvaluateNested(btn.props);
const { id: buttonId, props: componentProps } = btn;
@@ -587,6 +671,7 @@ export default class ToolbarService extends PubSubService {
return evaluateFunction;
});
const evaluateProps = props.evaluate;
props.evaluate = args => {
const results = evaluators.map(evaluator => evaluator(args)).filter(Boolean);
@@ -607,6 +692,8 @@ export default class ToolbarService extends PubSubService {
return mergedResult;
};
props.evaluateProps = evaluateProps;
return;
}
@@ -627,7 +714,9 @@ export default class ToolbarService extends PubSubService {
const { name, ...options } = evaluate;
const evaluateFunction = this._evaluateFunction[name];
if (evaluateFunction) {
const evaluateProps = props.evaluate;
props.evaluate = args => evaluateFunction({ ...args, ...options });
props.evaluateProps = evaluateProps;
return;
}
@@ -645,4 +734,56 @@ export default class ToolbarService extends PubSubService {
this.state.buttonSections[buttonSection] = [];
this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, { ...this.state });
}
/**
* Checks if a button exists in any toolbar section.
*
* @param buttonId - The button ID to check for
* @returns True if the button exists in any section, false otherwise
*/
isInAnySection(buttonId: string): boolean {
if (!buttonId) {
return false;
}
// Check all sections to see if the button ID exists in any of them
return Object.values(this.state.buttonSections).some(
section => Array.isArray(section) && section.includes(buttonId)
);
}
/**
* Returns the alignment and side for a specific viewport corner location.
* Used for menu positioning based on the corner location.
*
* @param location - The viewport corner location
* @returns An object with align and side properties
*/
public getAlignAndSide(location: ButtonLocation | string): {
align: 'start' | 'end' | 'center';
side: 'top' | 'bottom' | 'left' | 'right';
} {
const locationNumber = Number(location);
switch (locationNumber) {
case ButtonLocation.TopLeft: // Enum 0, Original 0 (topLeft)
return { align: 'start', side: 'bottom' };
case ButtonLocation.TopMiddle: // Enum 1, Original 4 (topMiddle)
return { align: 'center', side: 'bottom' };
case ButtonLocation.TopRight: // Enum 2, Original 1 (topRight)
return { align: 'end', side: 'bottom' };
case ButtonLocation.LeftMiddle: // Enum 3, Original 6 (leftMiddle)
return { align: 'start', side: 'right' };
case ButtonLocation.RightMiddle: // Enum 4, Original 7 (rightMiddle)
return { align: 'end', side: 'left' };
case ButtonLocation.BottomLeft: // Enum 5, Original 2 (bottomLeft)
return { align: 'start', side: 'top' };
case ButtonLocation.BottomMiddle: // Enum 6, Original 5 (bottomMiddle)
return { align: 'center', side: 'top' };
case ButtonLocation.BottomRight: // Enum 7, Original 3 (bottomRight)
return { align: 'end', side: 'top' };
default:
// Default to TopLeft behavior if an unexpected value is passed.
return { align: 'start', side: 'bottom' };
}
}
}
@@ -1,3 +1,3 @@
import ToolbarService from './ToolbarService';
import ToolbarService, { TOOLBAR_SECTIONS } from './ToolbarService';
export default ToolbarService;
export { TOOLBAR_SECTIONS };
@@ -1,6 +1,22 @@
import type { RunCommand } from '../../types/Command';
import React from 'react';
/**
* Type definitions for toolbar sections
*/
export type ToolbarSections = {
primary: string;
secondary: string;
viewportActionMenu: {
topLeft: string;
topRight: string;
bottomLeft: string;
bottomRight: string;
};
measurementSection: string;
moreToolsSection: string;
};
export type EvaluatePublic =
| string
| EvaluateFunction
@@ -137,7 +137,7 @@ class WorkflowStepsService extends PubSubService {
toUse.forEach(({ buttonSection, buttons }) => {
toolbarService.clearButtonSection(buttonSection);
toolbarService.createButtonSection(buttonSection, buttons);
toolbarService.updateSection(buttonSection, buttons);
});
}
+10
View File
@@ -35,9 +35,19 @@ export type DisplaySet = {
*/
renderedUrl?: string;
/**
* The instance UID of the display set that this display set references.
* This is used to determine if the display set is a referenced display set.
* It usually is for SEG, RTSTRUCT, etc.
*/
referencedDisplaySetInstanceUID?: string;
SeriesDate?: string;
SeriesTime?: string;
instance?: InstanceMetadata;
isHydrated?: boolean;
isRehydratable?: boolean;
};
export type DisplaySetSeriesMetadataInvalidatedEvent = {