fix(StackSync): Miscellaneous fixes for stack image sync (#3663)
This commit is contained in:
parent
b3429729f1
commit
8a335bd03d
@ -32,6 +32,26 @@ const COMMIT_HASH = fs.readFileSync(path.join(__dirname, '../commit.txt'), 'utf8
|
|||||||
//
|
//
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
const defineValues = {
|
||||||
|
/* Application */
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
||||||
|
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
|
||||||
|
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
|
||||||
|
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
|
||||||
|
'process.env.VERSION_NUMBER': JSON.stringify(VERSION_NUMBER),
|
||||||
|
'process.env.COMMIT_HASH': JSON.stringify(COMMIT_HASH),
|
||||||
|
/* i18n */
|
||||||
|
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
|
||||||
|
'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''),
|
||||||
|
'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''),
|
||||||
|
'process.env.REACT_APP_I18N_DEBUG': JSON.stringify(process.env.REACT_APP_I18N_DEBUG || ''),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only redefine updated values. This avoids warning messages in the logs
|
||||||
|
if (!process.env.APP_CONFIG) {
|
||||||
|
defineValues['process.env.APP_CONFIG'] = '';
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
||||||
if (!process.env.NODE_ENV) {
|
if (!process.env.NODE_ENV) {
|
||||||
throw new Error('process.env.NODE_ENV not set');
|
throw new Error('process.env.NODE_ENV not set');
|
||||||
@ -133,21 +153,7 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin(defineValues),
|
||||||
/* Application */
|
|
||||||
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
|
|
||||||
'process.env.DEBUG': JSON.stringify(process.env.DEBUG),
|
|
||||||
'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''),
|
|
||||||
'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'),
|
|
||||||
'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM),
|
|
||||||
'process.env.VERSION_NUMBER': JSON.stringify(VERSION_NUMBER),
|
|
||||||
'process.env.COMMIT_HASH': JSON.stringify(COMMIT_HASH),
|
|
||||||
/* i18n */
|
|
||||||
'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''),
|
|
||||||
'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''),
|
|
||||||
'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''),
|
|
||||||
'process.env.REACT_APP_I18N_DEBUG': JSON.stringify(process.env.REACT_APP_I18N_DEBUG || ''),
|
|
||||||
}),
|
|
||||||
new webpack.ProvidePlugin({
|
new webpack.ProvidePlugin({
|
||||||
Buffer: ['buffer', 'Buffer'],
|
Buffer: ['buffer', 'Buffer'],
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -265,6 +265,11 @@ function commandsModule({
|
|||||||
toolbarServiceRecordInteraction: props => {
|
toolbarServiceRecordInteraction: props => {
|
||||||
toolbarService.recordInteraction(props);
|
toolbarService.recordInteraction(props);
|
||||||
},
|
},
|
||||||
|
// Enable or disable a toggleable command, without calling the activation
|
||||||
|
// Used to setup already active tools from hanging protocols
|
||||||
|
setToolbarToggled: props => {
|
||||||
|
toolbarService.setToggled(props.toolId, props.isActive ?? true);
|
||||||
|
},
|
||||||
setToolActive: ({ toolName, toolGroupId = null, toggledState }) => {
|
setToolActive: ({ toolName, toolGroupId = null, toggledState }) => {
|
||||||
if (toolName === 'Crosshairs') {
|
if (toolName === 'Crosshairs') {
|
||||||
const activeViewportToolGroup = toolGroupService.getToolGroup(null);
|
const activeViewportToolGroup = toolGroupService.getToolGroup(null);
|
||||||
@ -549,16 +554,16 @@ function commandsModule({
|
|||||||
|
|
||||||
toggleStackImageSync: ({ toggledState }) => {
|
toggleStackImageSync: ({ toggledState }) => {
|
||||||
toggleStackImageSync({
|
toggleStackImageSync({
|
||||||
getEnabledElement,
|
|
||||||
servicesManager,
|
servicesManager,
|
||||||
toggledState,
|
toggledState,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setSourceViewportForReferenceLinesTool: ({ toggledState }) => {
|
setSourceViewportForReferenceLinesTool: ({ toggledState, viewportId }) => {
|
||||||
|
if (!viewportId) {
|
||||||
const { activeViewportId } = viewportGridService.getState();
|
const { activeViewportId } = viewportGridService.getState();
|
||||||
const viewportInfo = cornerstoneViewportService.getViewportInfo(activeViewportId);
|
viewportId = activeViewportId;
|
||||||
|
}
|
||||||
|
|
||||||
const viewportId = viewportInfo.getViewportId();
|
|
||||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||||
|
|
||||||
toolGroup.setToolConfiguration(
|
toolGroup.setToolConfiguration(
|
||||||
@ -699,8 +704,9 @@ function commandsModule({
|
|||||||
},
|
},
|
||||||
storePresentation: {
|
storePresentation: {
|
||||||
commandFn: actions.storePresentation,
|
commandFn: actions.storePresentation,
|
||||||
storeContexts: [],
|
},
|
||||||
options: {},
|
setToolbarToggled: {
|
||||||
|
commandFn: actions.setToolbarToggled,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
utilities as csUtilities,
|
utilities as csUtilities,
|
||||||
Enums as csEnums,
|
Enums as csEnums,
|
||||||
} from '@cornerstonejs/core';
|
} from '@cornerstonejs/core';
|
||||||
import { Enums, utilities, ReferenceLinesTool } from '@cornerstonejs/tools';
|
import { Enums } from '@cornerstonejs/tools';
|
||||||
import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader';
|
import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader';
|
||||||
|
|
||||||
import initWADOImageLoader from './initWADOImageLoader';
|
import initWADOImageLoader from './initWADOImageLoader';
|
||||||
@ -93,6 +93,7 @@ export default async function init({
|
|||||||
cornerstoneViewportService,
|
cornerstoneViewportService,
|
||||||
hangingProtocolService,
|
hangingProtocolService,
|
||||||
toolGroupService,
|
toolGroupService,
|
||||||
|
toolbarService,
|
||||||
viewportGridService,
|
viewportGridService,
|
||||||
stateSyncService,
|
stateSyncService,
|
||||||
} = servicesManager.services as CornerstoneServices;
|
} = servicesManager.services as CornerstoneServices;
|
||||||
@ -208,9 +209,45 @@ export default async function init({
|
|||||||
commandsManager,
|
commandsManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
const newStackCallback = evt => {
|
/**
|
||||||
|
* When a viewport gets a new display set, this call will go through all the
|
||||||
|
* active tools in the toolbar, and call any commands registered in the
|
||||||
|
* toolbar service with a callback to re-enable on displaying the viewport.
|
||||||
|
*/
|
||||||
|
const toolbarEventListener = evt => {
|
||||||
const { element } = evt.detail;
|
const { element } = evt.detail;
|
||||||
utilities.stackPrefetch.enable(element);
|
const activeTools = toolbarService.getActiveTools();
|
||||||
|
|
||||||
|
activeTools.forEach(tool => {
|
||||||
|
const toolData = toolbarService.getNestedButton(tool);
|
||||||
|
const commands = toolData?.listeners?.[evt.type];
|
||||||
|
commandsManager.run(commands, { element, evt });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Listens for active viewport events and fires the toolbar listeners */
|
||||||
|
const activeViewportEventListener = evt => {
|
||||||
|
const { viewportId } = evt;
|
||||||
|
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
||||||
|
|
||||||
|
const activeTools = toolbarService.getActiveTools();
|
||||||
|
|
||||||
|
activeTools.forEach(tool => {
|
||||||
|
if (!toolGroup?._toolInstances?.[tool]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if tool is active on the new viewport
|
||||||
|
const toolEnabled = toolGroup._toolInstances[tool].mode === Enums.ToolModes.Enabled;
|
||||||
|
|
||||||
|
if (!toolEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = toolbarService.getNestedButton(tool);
|
||||||
|
const commands = button?.listeners?.[evt.type];
|
||||||
|
commandsManager.run(commands, { viewportId, evt });
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetCrosshairs = evt => {
|
const resetCrosshairs = evt => {
|
||||||
@ -237,11 +274,16 @@ export default async function init({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, evt => {
|
||||||
|
const { element } = evt.detail;
|
||||||
|
cornerstoneTools.utilities.stackContextPrefetch.enable(element);
|
||||||
|
});
|
||||||
|
|
||||||
function elementEnabledHandler(evt) {
|
function elementEnabledHandler(evt) {
|
||||||
const { element } = evt.detail;
|
const { element } = evt.detail;
|
||||||
element.addEventListener(EVENTS.CAMERA_RESET, resetCrosshairs);
|
element.addEventListener(EVENTS.CAMERA_RESET, resetCrosshairs);
|
||||||
|
|
||||||
eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, newStackCallback);
|
eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, toolbarEventListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
function elementDisabledHandler(evt) {
|
function elementDisabledHandler(evt) {
|
||||||
@ -262,33 +304,7 @@ export default async function init({
|
|||||||
|
|
||||||
viewportGridService.subscribe(
|
viewportGridService.subscribe(
|
||||||
viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED,
|
viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED,
|
||||||
({ viewportId }) => {
|
activeViewportEventListener
|
||||||
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
|
|
||||||
|
|
||||||
if (!toolGroup || !toolGroup._toolInstances?.['ReferenceLines']) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if reference lines are active
|
|
||||||
const referenceLinesEnabled =
|
|
||||||
toolGroup._toolInstances['ReferenceLines'].mode === Enums.ToolModes.Enabled;
|
|
||||||
|
|
||||||
if (!referenceLinesEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
toolGroup.setToolConfiguration(
|
|
||||||
ReferenceLinesTool.toolName,
|
|
||||||
{
|
|
||||||
sourceViewportId: viewportId,
|
|
||||||
},
|
|
||||||
true // overwrite
|
|
||||||
);
|
|
||||||
|
|
||||||
// make sure to set it to enabled again since we want to recalculate
|
|
||||||
// the source-target lines
|
|
||||||
toolGroup.setToolEnabled(ReferenceLinesTool.toolName);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -142,12 +142,16 @@ const CornerstoneViewportDownloadForm = ({
|
|||||||
const properties = viewport.getProperties();
|
const properties = viewport.getProperties();
|
||||||
|
|
||||||
downloadViewport.setStack([imageId]).then(() => {
|
downloadViewport.setStack([imageId]).then(() => {
|
||||||
|
try {
|
||||||
downloadViewport.setProperties(properties);
|
downloadViewport.setProperties(properties);
|
||||||
|
|
||||||
const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE);
|
const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE);
|
||||||
const newHeight = Math.min(height || image.height, MAX_TEXTURE_SIZE);
|
const newHeight = Math.min(height || image.height, MAX_TEXTURE_SIZE);
|
||||||
|
|
||||||
resolve({ width: newWidth, height: newHeight });
|
resolve({ width: newWidth, height: newHeight });
|
||||||
|
} catch (e) {
|
||||||
|
// Happens on clicking the cancel button
|
||||||
|
console.warn('Unable to set properties', e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else if (downloadViewport instanceof VolumeViewport) {
|
} else if (downloadViewport instanceof VolumeViewport) {
|
||||||
const actors = viewport.getActors();
|
const actors = viewport.getActors();
|
||||||
|
|||||||
@ -1,90 +1,80 @@
|
|||||||
import calculateViewportRegistrations from './calculateViewportRegistrations';
|
const STACK_SYNC_NAME = 'stackImageSync';
|
||||||
|
|
||||||
// [ {
|
export default function toggleStackImageSync({
|
||||||
// synchronizerId: string,
|
toggledState,
|
||||||
// viewports: [ { viewportId: string, renderingEngineId: string, index: number } , ...]
|
servicesManager,
|
||||||
// ]}
|
viewports: providedViewports,
|
||||||
let STACK_IMAGE_SYNC_GROUPS_INFO = [];
|
}) {
|
||||||
|
if (!toggledState) {
|
||||||
|
return disableSync(STACK_SYNC_NAME, servicesManager);
|
||||||
|
}
|
||||||
|
|
||||||
export default function toggleStackImageSync({ toggledState, servicesManager, getEnabledElement }) {
|
|
||||||
const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } =
|
const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } =
|
||||||
servicesManager.services;
|
servicesManager.services;
|
||||||
|
|
||||||
if (!toggledState) {
|
const viewports = providedViewports || getReconstructableStackViewports(viewportGridService, displaySetService);
|
||||||
STACK_IMAGE_SYNC_GROUPS_INFO.forEach(syncGroupInfo => {
|
|
||||||
const { viewports, synchronizerId } = syncGroupInfo;
|
|
||||||
|
|
||||||
viewports.forEach(({ viewportId, renderingEngineId }) => {
|
|
||||||
syncGroupService.removeViewportFromSyncGroup(viewportId, renderingEngineId, synchronizerId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// create synchronization group and add the viewports to it.
|
||||||
|
viewports.forEach(gridViewport => {
|
||||||
|
const { viewportId } = gridViewport.viewportOptions;
|
||||||
|
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
|
if (!viewport) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
syncGroupService.addViewportToSyncGroup(viewportId, viewport.getRenderingEngine().id, {
|
||||||
|
type: 'stackimage',
|
||||||
|
id: STACK_SYNC_NAME,
|
||||||
|
source: true,
|
||||||
|
target: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
STACK_IMAGE_SYNC_GROUPS_INFO = [];
|
function disableSync(syncName, servicesManager) {
|
||||||
|
const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } =
|
||||||
|
servicesManager.services;
|
||||||
|
const viewports = getReconstructableStackViewports(viewportGridService, displaySetService);
|
||||||
|
viewports.forEach(gridViewport => {
|
||||||
|
const { viewportId } = gridViewport.viewportOptions;
|
||||||
|
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
|
||||||
|
if (!viewport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
syncGroupService.removeViewportFromSyncGroup(
|
||||||
|
viewport.id,
|
||||||
|
viewport.getRenderingEngine().id,
|
||||||
|
syncName
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// create synchronization groups and add viewports
|
/**
|
||||||
const { viewports } = viewportGridService.getState();
|
* Gets the consistent spacing stack viewport types, which are the ones which
|
||||||
|
* can be navigated using the stack image sync right now.
|
||||||
|
*/
|
||||||
|
function getReconstructableStackViewports(viewportGridService, displaySetService) {
|
||||||
|
let { viewports } = viewportGridService.getState();
|
||||||
|
|
||||||
|
viewports = [...viewports.values()];
|
||||||
// filter empty viewports
|
// filter empty viewports
|
||||||
const viewportsArray = Array.from(viewports.values())
|
viewports = viewports.filter(
|
||||||
.filter(viewport => viewport.displaySetInstanceUIDs?.length)
|
viewport => viewport.displaySetInstanceUIDs && viewport.displaySetInstanceUIDs.length
|
||||||
|
);
|
||||||
|
|
||||||
// filter reconstructable viewports
|
// filter reconstructable viewports
|
||||||
.filter(viewport => {
|
viewports = viewports.filter(viewport => {
|
||||||
const { displaySetInstanceUIDs } = viewport;
|
const { displaySetInstanceUIDs } = viewport;
|
||||||
|
|
||||||
for (const displaySetInstanceUID of displaySetInstanceUIDs) {
|
for (const displaySetInstanceUID of displaySetInstanceUIDs) {
|
||||||
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
|
||||||
|
|
||||||
return !!displaySet?.isReconstructable;
|
// TODO - add a better test than isReconstructable
|
||||||
}
|
if (displaySet && displaySet.isReconstructable) {
|
||||||
});
|
return true;
|
||||||
|
|
||||||
const viewportsByOrientation = viewportsArray.reduce((acc, viewport) => {
|
|
||||||
const { viewportId, viewportType } = viewport.viewportOptions;
|
|
||||||
|
|
||||||
if (viewportType !== 'stack') {
|
|
||||||
console.warn('Viewport is not a stack, cannot sync images yet');
|
|
||||||
return acc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { element } = cornerstoneViewportService.getViewportInfo(viewportId);
|
return false;
|
||||||
const { viewport: csViewport, renderingEngineId } = getEnabledElement(element);
|
|
||||||
const { viewPlaneNormal } = csViewport.getCamera();
|
|
||||||
|
|
||||||
// Should we round here? I guess so, but not sure how much precision we need
|
|
||||||
const orientation = viewPlaneNormal.map(v => Math.round(v)).join(',');
|
|
||||||
|
|
||||||
if (!acc[orientation]) {
|
|
||||||
acc[orientation] = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
acc[orientation].push({ viewportId, renderingEngineId });
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
// create synchronizer for each group
|
|
||||||
Object.values(viewportsByOrientation).map(viewports => {
|
|
||||||
let synchronizerId = viewports.map(({ viewportId }) => viewportId).join(',');
|
|
||||||
|
|
||||||
synchronizerId = `imageSync_${synchronizerId}`;
|
|
||||||
|
|
||||||
calculateViewportRegistrations(viewports);
|
|
||||||
|
|
||||||
viewports.forEach(({ viewportId, renderingEngineId }) => {
|
|
||||||
syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, {
|
|
||||||
type: 'stackimage',
|
|
||||||
id: synchronizerId,
|
|
||||||
source: true,
|
|
||||||
target: true,
|
|
||||||
});
|
});
|
||||||
});
|
return viewports;
|
||||||
|
};
|
||||||
STACK_IMAGE_SYNC_GROUPS_INFO.push({
|
|
||||||
synchronizerId,
|
|
||||||
viewports,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -134,7 +134,7 @@ const commandsModule = ({
|
|||||||
(!protocolId || protocolId === protocol.id) &&
|
(!protocolId || protocolId === protocol.id) &&
|
||||||
(stageIndex === undefined || stageIndex === toggleStageIndex) &&
|
(stageIndex === undefined || stageIndex === toggleStageIndex) &&
|
||||||
(!stageId || stageId === stage.id);
|
(!stageId || stageId === stage.id);
|
||||||
toolbarService.setActive(button.id, isActive);
|
toolbarService.setToggled(button.id, isActive);
|
||||||
};
|
};
|
||||||
Object.values(toolbarService.getButtons()).forEach(enableListener);
|
Object.values(toolbarService.getButtons()).forEach(enableListener);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export default function getDisplaySetMessages(
|
|||||||
|
|
||||||
const firstInstance = instances[0];
|
const firstInstance = instances[0];
|
||||||
// Due to current requirements, LOCALIZER series doesn't have any messages
|
// Due to current requirements, LOCALIZER series doesn't have any messages
|
||||||
if (firstInstance.ImageType.includes('LOCALIZER')) {
|
if (firstInstance?.ImageType?.includes('LOCALIZER')) {
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "OHIF/Viewers",
|
"repository": "OHIF/Viewers",
|
||||||
"main": "dist/ohif-mode-test.umd.js",
|
"main": "dist/ohif-mode-test.umd.js",
|
||||||
"module": "src/index.js",
|
"module": "src/index.ts",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14",
|
"node": ">=14",
|
||||||
"npm": ">=6",
|
"npm": ">=6",
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { hotkeys } from '@ohif/core';
|
import { hotkeys } from '@ohif/core';
|
||||||
import toolbarButtons from './toolbarButtons.js';
|
import toolbarButtons from './toolbarButtons';
|
||||||
import { id } from './id.js';
|
import { id } from './id';
|
||||||
import initToolGroups from './initToolGroups.js';
|
import initToolGroups from './initToolGroups';
|
||||||
|
|
||||||
// Allow this mode by excluding non-imaging modalities such as SR, SEG
|
// Allow this mode by excluding non-imaging modalities such as SR, SEG
|
||||||
// Also, SM is not a simple imaging modalities, so exclude it.
|
// Also, SM is not a simple imaging modalities, so exclude it.
|
||||||
@ -5,43 +5,15 @@ import {
|
|||||||
// ListMenu,
|
// ListMenu,
|
||||||
WindowLevelMenuItem,
|
WindowLevelMenuItem,
|
||||||
} from '@ohif/ui';
|
} from '@ohif/ui';
|
||||||
import { defaults } from '@ohif/core';
|
import { defaults, ToolbarService } from '@ohif/core';
|
||||||
|
import type { Button, RunCommand } from '@ohif/core/types';
|
||||||
|
import { EVENTS } from '@cornerstonejs/core';
|
||||||
|
|
||||||
const { windowLevelPresets } = defaults;
|
const { windowLevelPresets } = defaults;
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {*} type - 'tool' | 'action' | 'toggle'
|
|
||||||
* @param {*} id
|
|
||||||
* @param {*} icon
|
|
||||||
* @param {*} label
|
|
||||||
*/
|
|
||||||
function _createButton(type, id, icon, label, commands, tooltip, uiType) {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
icon,
|
|
||||||
label,
|
|
||||||
type,
|
|
||||||
commands,
|
|
||||||
tooltip,
|
|
||||||
uiType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createCommands(commandName, toolName, toolGroupIds) {
|
const _createActionButton = ToolbarService._createButton.bind(null, 'action');
|
||||||
return toolGroupIds.map(toolGroupId => ({
|
const _createToggleButton = ToolbarService._createButton.bind(null, 'toggle');
|
||||||
/* It's a command that is being run when the button is clicked. */
|
const _createToolButton = ToolbarService._createButton.bind(null, 'tool');
|
||||||
commandName,
|
|
||||||
commandOptions: {
|
|
||||||
toolName,
|
|
||||||
toolGroupId,
|
|
||||||
},
|
|
||||||
context: 'CORNERSTONE',
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const _createActionButton = _createButton.bind(null, 'action');
|
|
||||||
const _createToggleButton = _createButton.bind(null, 'toggle');
|
|
||||||
const _createToolButton = _createButton.bind(null, 'tool');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -67,7 +39,21 @@ function _createWwwcPreset(preset, title, subtitle) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolbarButtons = [
|
const ReferenceLinesCommands: RunCommand = [
|
||||||
|
{
|
||||||
|
commandName: 'setSourceViewportForReferenceLinesTool',
|
||||||
|
context: 'CORNERSTONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
commandName: 'setToolActive',
|
||||||
|
commandOptions: {
|
||||||
|
toolName: 'ReferenceLines',
|
||||||
|
},
|
||||||
|
context: 'CORNERSTONE',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const toolbarButtons: Button[] = [
|
||||||
// Measurement
|
// Measurement
|
||||||
{
|
{
|
||||||
id: 'MeasurementTools',
|
id: 'MeasurementTools',
|
||||||
@ -515,24 +501,37 @@ const toolbarButtons = [
|
|||||||
],
|
],
|
||||||
'Flip Horizontal'
|
'Flip Horizontal'
|
||||||
),
|
),
|
||||||
_createToggleButton('StackImageSync', 'link', 'Stack Image Sync', [
|
_createToggleButton(
|
||||||
|
'StackImageSync',
|
||||||
|
'link',
|
||||||
|
'Stack Image Sync',
|
||||||
|
[
|
||||||
{
|
{
|
||||||
commandName: 'toggleStackImageSync',
|
commandName: 'toggleStackImageSync',
|
||||||
commandOptions: {},
|
|
||||||
context: 'CORNERSTONE',
|
|
||||||
},
|
},
|
||||||
]),
|
],
|
||||||
|
'Enable position synchronization on stack viewports',
|
||||||
|
{
|
||||||
|
listeners: {
|
||||||
|
[EVENTS.STACK_VIEWPORT_NEW_STACK]: {
|
||||||
|
commandName: 'toggleStackImageSync',
|
||||||
|
commandOptions: { toggledState: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
_createToggleButton(
|
_createToggleButton(
|
||||||
'ReferenceLines',
|
'ReferenceLines',
|
||||||
'tool-referenceLines', // change this with the new icon
|
'tool-referenceLines', // change this with the new icon
|
||||||
'Reference Lines',
|
'Reference Lines',
|
||||||
[
|
ReferenceLinesCommands,
|
||||||
|
'Show Reference Lines',
|
||||||
{
|
{
|
||||||
commandName: 'toggleReferenceLines',
|
listeners: {
|
||||||
commandOptions: {},
|
[EVENTS.STACK_VIEWPORT_NEW_STACK]: ReferenceLinesCommands,
|
||||||
context: 'CORNERSTONE',
|
[EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesCommands,
|
||||||
},
|
},
|
||||||
]
|
}
|
||||||
),
|
),
|
||||||
_createToolButton(
|
_createToolButton(
|
||||||
'StackScroll',
|
'StackScroll',
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { hotkeys } from '@ohif/core';
|
import { hotkeys } from '@ohif/core';
|
||||||
import toolbarButtons from './toolbarButtons.js';
|
import toolbarButtons from './toolbarButtons';
|
||||||
import { id } from './id.js';
|
import { id } from './id';
|
||||||
import initToolGroups from './initToolGroups.js';
|
import initToolGroups from './initToolGroups';
|
||||||
|
|
||||||
// Allow this mode by excluding non-imaging modalities such as SR, SEG
|
// Allow this mode by excluding non-imaging modalities such as SR, SEG
|
||||||
// Also, SM is not a simple imaging modalities, so exclude it.
|
// Also, SM is not a simple imaging modalities, so exclude it.
|
||||||
|
|||||||
@ -5,32 +5,15 @@ import {
|
|||||||
// ListMenu,
|
// ListMenu,
|
||||||
WindowLevelMenuItem,
|
WindowLevelMenuItem,
|
||||||
} from '@ohif/ui';
|
} from '@ohif/ui';
|
||||||
import { defaults } from '@ohif/core';
|
import { defaults, ToolbarService } from '@ohif/core';
|
||||||
|
import type { Button, RunCommand } from '@ohif/core/types';
|
||||||
|
import { EVENTS } from '@cornerstonejs/core';
|
||||||
|
|
||||||
const { windowLevelPresets } = defaults;
|
const { windowLevelPresets } = defaults;
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {*} type - 'tool' | 'action' | 'toggle'
|
|
||||||
* @param {*} id
|
|
||||||
* @param {*} icon
|
|
||||||
* @param {*} label
|
|
||||||
*/
|
|
||||||
function _createButton(type, id, icon, label, commands, tooltip, uiType, isActive) {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
icon,
|
|
||||||
label,
|
|
||||||
type,
|
|
||||||
commands,
|
|
||||||
tooltip,
|
|
||||||
uiType,
|
|
||||||
isActive,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const _createActionButton = _createButton.bind(null, 'action');
|
const _createActionButton = ToolbarService._createButton.bind(null, 'action');
|
||||||
const _createToggleButton = _createButton.bind(null, 'toggle');
|
const _createToggleButton = ToolbarService._createButton.bind(null, 'toggle');
|
||||||
const _createToolButton = _createButton.bind(null, 'tool');
|
const _createToolButton = ToolbarService._createButton.bind(null, 'tool');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -76,7 +59,21 @@ function _createSetToolActiveCommands(toolName) {
|
|||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolbarButtons = [
|
const ReferenceLinesCommands: RunCommand = [
|
||||||
|
{
|
||||||
|
commandName: 'setSourceViewportForReferenceLinesTool',
|
||||||
|
context: 'CORNERSTONE',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
commandName: 'setToolActive',
|
||||||
|
commandOptions: {
|
||||||
|
toolName: 'ReferenceLines',
|
||||||
|
},
|
||||||
|
context: 'CORNERSTONE',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const toolbarButtons: Button[] = [
|
||||||
// Measurement
|
// Measurement
|
||||||
{
|
{
|
||||||
id: 'MeasurementTools',
|
id: 'MeasurementTools',
|
||||||
@ -422,34 +419,37 @@ const toolbarButtons = [
|
|||||||
],
|
],
|
||||||
'Flip Horizontal'
|
'Flip Horizontal'
|
||||||
),
|
),
|
||||||
_createToggleButton('StackImageSync', 'link', 'Stack Image Sync', [
|
_createToggleButton(
|
||||||
|
'StackImageSync',
|
||||||
|
'link',
|
||||||
|
'Stack Image Sync',
|
||||||
|
[
|
||||||
{
|
{
|
||||||
commandName: 'toggleStackImageSync',
|
commandName: 'toggleStackImageSync',
|
||||||
commandOptions: {},
|
|
||||||
context: 'CORNERSTONE',
|
|
||||||
},
|
},
|
||||||
]),
|
],
|
||||||
|
'Enable position synchronization on stack viewports',
|
||||||
|
{
|
||||||
|
listeners: {
|
||||||
|
[EVENTS.STACK_VIEWPORT_NEW_STACK]: {
|
||||||
|
commandName: 'toggleStackImageSync',
|
||||||
|
commandOptions: { toggledState: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
_createToggleButton(
|
_createToggleButton(
|
||||||
'ReferenceLines',
|
'ReferenceLines',
|
||||||
'tool-referenceLines', // change this with the new icon
|
'tool-referenceLines', // change this with the new icon
|
||||||
'Reference Lines',
|
'Reference Lines',
|
||||||
// two commands for the reference lines tool:
|
ReferenceLinesCommands,
|
||||||
// - the first to set the source viewport for the tool when it is enabled
|
'Show Reference Lines',
|
||||||
// - the second to toggle the tool
|
|
||||||
[
|
|
||||||
{
|
{
|
||||||
commandName: 'setSourceViewportForReferenceLinesTool',
|
listeners: {
|
||||||
commandOptions: {},
|
[EVENTS.STACK_VIEWPORT_NEW_STACK]: ReferenceLinesCommands,
|
||||||
context: 'CORNERSTONE',
|
[EVENTS.ACTIVE_VIEWPORT_ID_CHANGED]: ReferenceLinesCommands,
|
||||||
},
|
},
|
||||||
{
|
}
|
||||||
commandName: 'setToolActive',
|
|
||||||
commandOptions: {
|
|
||||||
toolName: 'ReferenceLines',
|
|
||||||
},
|
|
||||||
context: 'CORNERSTONE',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
),
|
),
|
||||||
_createToggleButton(
|
_createToggleButton(
|
||||||
'ImageOverlayViewer',
|
'ImageOverlayViewer',
|
||||||
@ -465,8 +465,7 @@ const toolbarButtons = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
'Image Overlay',
|
'Image Overlay',
|
||||||
null,
|
{ isActive: true }
|
||||||
true
|
|
||||||
),
|
),
|
||||||
_createToolButton(
|
_createToolButton(
|
||||||
'StackScroll',
|
'StackScroll',
|
||||||
@ -131,6 +131,8 @@ module.exports = (env, argv) => {
|
|||||||
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
||||||
// Need to exclude the theme as it is updated independently
|
// Need to exclude the theme as it is updated independently
|
||||||
exclude: [/theme/],
|
exclude: [/theme/],
|
||||||
|
// Cache large files for the manifests to avoid warning messages
|
||||||
|
maximumFileSizeToCacheInBytes: 1024 * 1024 * 50,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
// https://webpack.js.org/configuration/dev-server/
|
// https://webpack.js.org/configuration/dev-server/
|
||||||
|
|||||||
@ -74,7 +74,7 @@ function getRuntimeLoadModesExtensions(modules) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
dynamicLoad.push(
|
dynamicLoad.push(
|
||||||
' return (await import(module)).default;',
|
' return (await import(/* webpackIgnore: true */ module)).default;',
|
||||||
'}\n',
|
'}\n',
|
||||||
'// Import a list of items (modules or string names)',
|
'// Import a list of items (modules or string names)',
|
||||||
'// @return a Promise evaluating to a list of modules',
|
'// @return a Promise evaluating to a list of modules',
|
||||||
|
|||||||
@ -409,20 +409,42 @@ describe('OHIF Cornerstone Toolbar', () => {
|
|||||||
cy.get('@moreBtn').click();
|
cy.get('@moreBtn').click();
|
||||||
cy.get('.tooltip-toolbar-overlay').should('not.exist');
|
cy.get('.tooltip-toolbar-overlay').should('not.exist');
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
it('check if Flip V tool will flip the image vertically in the viewport', () => {
|
it('check if Flip tool will flip the image in the viewport', () => {
|
||||||
//Click on More button
|
|
||||||
cy.get('@moreBtn').click();
|
|
||||||
//Verify if overlay is displayed
|
|
||||||
cy.get('.tooltip-toolbar-overlay').should('be.visible');
|
|
||||||
|
|
||||||
//Click on Flip V button
|
|
||||||
cy.get('[data-cy="flip v"]').click();
|
|
||||||
cy.get('@viewportInfoMidLeft').should('contains.text', 'R');
|
cy.get('@viewportInfoMidLeft').should('contains.text', 'R');
|
||||||
cy.get('@viewportInfoMidTop').should('contains.text', 'F');
|
cy.get('@viewportInfoMidTop').should('contains.text', 'A');
|
||||||
|
|
||||||
//Click on More button to close it
|
//Click on More button
|
||||||
cy.get('@moreBtn').click();
|
cy.get('@moreBtnSecondary').click();
|
||||||
cy.get('.tooltip-toolbar-overlay').should('not.exist');
|
|
||||||
});*/
|
//Click on Flip button
|
||||||
|
cy.get('[data-cy="flip-horizontal"]').click();
|
||||||
|
cy.waitDicomImage();
|
||||||
|
cy.get('@viewportInfoMidLeft').should('contains.text', 'L');
|
||||||
|
cy.get('@viewportInfoMidTop').should('contains.text', 'A');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if stack sync is preserved on new display set and uses FOR', () => {
|
||||||
|
// Active stack image sync and reference lines
|
||||||
|
cy.get('[data-cy="MoreTools-split-button-secondary"]').click();
|
||||||
|
cy.get('[data-cy="StackImageSync"]').click();
|
||||||
|
// Add reference lines as that sometimes throws an exception
|
||||||
|
cy.get('[data-cy="MoreTools-split-button-secondary"]').click();
|
||||||
|
cy.get('[data-cy="ReferenceLines"]').click();
|
||||||
|
|
||||||
|
cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)').dblclick();
|
||||||
|
cy.get('body').type('{downarrow}{downarrow}');
|
||||||
|
|
||||||
|
// Change the layout and double load the first
|
||||||
|
cy.setLayout(2, 1);
|
||||||
|
cy.get('body').type('{rightarrow}');
|
||||||
|
cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)').dblclick();
|
||||||
|
cy.waitDicomImage();
|
||||||
|
|
||||||
|
// Now navigate down once and check that the left hand pane navigated
|
||||||
|
cy.get('body').type('{downarrow}');
|
||||||
|
cy.get('body').type('{leftarrow}');
|
||||||
|
cy.setLayout(1, 1);
|
||||||
|
cy.get('@viewportInfoTopRight').should('contains.text', 'I:2 (2/20)');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -36,6 +36,8 @@ describe('OHIF Download Snapshot File', () => {
|
|||||||
// Check buttons
|
// Check buttons
|
||||||
cy.get('[data-cy="cancel-btn"]').scrollIntoView().should('be.visible');
|
cy.get('[data-cy="cancel-btn"]').scrollIntoView().should('be.visible');
|
||||||
cy.get('[data-cy="download-btn"]').scrollIntoView().should('be.visible');
|
cy.get('[data-cy="download-btn"]').scrollIntoView().should('be.visible');
|
||||||
|
|
||||||
|
cy.get('[data-cy="cancel-btn"]').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
/*it('cancel changes on download modal', function() {
|
/*it('cancel changes on download modal', function() {
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
describe('OHIF Study Viewer Page', function () {
|
describe('OHIF General Viewer', function () {
|
||||||
beforeEach(function () {
|
beforeEach(() =>
|
||||||
cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
|
cy.initViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78', {
|
||||||
|
minimumThumbnails: 3,
|
||||||
cy.expectMinimumThumbnails(3);
|
})
|
||||||
cy.initCommonElementsAliases();
|
);
|
||||||
cy.initCornerstoneToolsAliases();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('scrolls series stack using scrollbar', function () {
|
it('scrolls series stack using scrollbar', function () {
|
||||||
cy.scrollToIndex(13);
|
cy.scrollToIndex(13);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
describe('OHIF Study Viewer Page', function () {
|
describe('OHIF Study Browser', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
|
cy.checkStudyRouteInViewer('1.2.840.113619.2.5.1762583153.215519.978957063.78');
|
||||||
|
|
||||||
|
|||||||
@ -69,6 +69,18 @@ Cypress.Commands.add(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Cypress.Commands.add('initViewer', (StudyInstanceUID, other = {}) => {
|
||||||
|
const { mode = '/basic-test', minimumThumbnails = 1, params = '' } = other;
|
||||||
|
cy.openStudyInViewer(StudyInstanceUID, params, mode);
|
||||||
|
cy.waitDicomImage();
|
||||||
|
// Very short wait to ensure pending updates are handled
|
||||||
|
cy.wait(25);
|
||||||
|
|
||||||
|
cy.expectMinimumThumbnails(minimumThumbnails);
|
||||||
|
cy.initCommonElementsAliases();
|
||||||
|
cy.initCornerstoneToolsAliases();
|
||||||
|
});
|
||||||
|
|
||||||
Cypress.Commands.add(
|
Cypress.Commands.add(
|
||||||
'openStudyInViewer',
|
'openStudyInViewer',
|
||||||
(StudyInstanceUID, otherParams = '', mode = '/basic-test') => {
|
(StudyInstanceUID, otherParams = '', mode = '/basic-test') => {
|
||||||
@ -347,14 +359,9 @@ Cypress.Commands.add('percyCanvasSnapshot', (name, options = {}) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('setLayout', (columns = 1, rows = 1) => {
|
Cypress.Commands.add('setLayout', (columns = 1, rows = 1) => {
|
||||||
cy.get('[data-cy="layout"]').click();
|
cy.get('[data-cy="Layout"]').click();
|
||||||
|
|
||||||
cy.get('.layoutChooser')
|
cy.get(`[data-cy="Layout-${columns - 1}-${rows - 1}"]`).click();
|
||||||
.find('tr')
|
|
||||||
.eq(rows - 1)
|
|
||||||
.find('td')
|
|
||||||
.eq(columns - 1)
|
|
||||||
.click();
|
|
||||||
|
|
||||||
cy.wait(10);
|
cy.wait(10);
|
||||||
cy.waitDicomImage();
|
cy.waitDicomImage();
|
||||||
|
|||||||
@ -38,8 +38,28 @@ window.config = {
|
|||||||
configuration: {
|
configuration: {
|
||||||
friendlyName: 'Static WADO Local Data',
|
friendlyName: 'Static WADO Local Data',
|
||||||
name: 'DCM4CHEE',
|
name: 'DCM4CHEE',
|
||||||
qidoRoot: '/dicomweb',
|
qidoRoot: 'http://localhost:5000/dicomweb',
|
||||||
wadoRoot: '/dicomweb',
|
wadoRoot: 'http://localhost:5000/dicomweb',
|
||||||
|
qidoSupportsIncludeField: false,
|
||||||
|
supportsReject: true,
|
||||||
|
supportsStow: true,
|
||||||
|
imageRendering: 'wadors',
|
||||||
|
thumbnailRendering: 'wadors',
|
||||||
|
enableStudyLazyLoad: true,
|
||||||
|
supportsFuzzyMatching: false,
|
||||||
|
supportsWildcard: true,
|
||||||
|
staticWado: true,
|
||||||
|
singlepart: 'bulkdata,video,pdf',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
|
||||||
|
sourceName: 'docker',
|
||||||
|
configuration: {
|
||||||
|
friendlyName: 'Static WADO Docker Data',
|
||||||
|
name: 'DCM4CHEE',
|
||||||
|
qidoRoot: 'http://localhost:25080/dicomweb',
|
||||||
|
wadoRoot: 'http://localhost:25080/dicomweb',
|
||||||
qidoSupportsIncludeField: false,
|
qidoSupportsIncludeField: false,
|
||||||
supportsReject: true,
|
supportsReject: true,
|
||||||
supportsStow: true,
|
supportsStow: true,
|
||||||
|
|||||||
@ -2,12 +2,37 @@ import merge from 'lodash.merge';
|
|||||||
import { CommandsManager } from '../../classes';
|
import { CommandsManager } from '../../classes';
|
||||||
import { ExtensionManager } from '../../extensions';
|
import { ExtensionManager } from '../../extensions';
|
||||||
import { PubSubService } from '../_shared/pubSubServiceInterface';
|
import { PubSubService } from '../_shared/pubSubServiceInterface';
|
||||||
|
import type { RunCommand, Commands } from '../../types/Command';
|
||||||
|
|
||||||
const EVENTS = {
|
const EVENTS = {
|
||||||
TOOL_BAR_MODIFIED: 'event::toolBarService:toolBarModified',
|
TOOL_BAR_MODIFIED: 'event::toolBarService:toolBarModified',
|
||||||
TOOL_BAR_STATE_MODIFIED: 'event::toolBarService:toolBarStateModified',
|
TOOL_BAR_STATE_MODIFIED: 'event::toolBarService:toolBarStateModified',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ButtonListeners = Record<string, RunCommand>;
|
||||||
|
|
||||||
|
export interface ButtonProps {
|
||||||
|
primary?: Button;
|
||||||
|
secondary?: Button;
|
||||||
|
items?: Button[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Button extends Commands {
|
||||||
|
id: string;
|
||||||
|
icon?: string;
|
||||||
|
label?: string;
|
||||||
|
type?: string;
|
||||||
|
tooltip?: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
listeners?: ButtonListeners;
|
||||||
|
props?: ButtonProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtraButtonOptions {
|
||||||
|
listeners?: ButtonListeners;
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default class ToolbarService extends PubSubService {
|
export default class ToolbarService extends PubSubService {
|
||||||
public static REGISTRATION = {
|
public static REGISTRATION = {
|
||||||
name: 'toolbarService',
|
name: 'toolbarService',
|
||||||
@ -18,7 +43,27 @@ export default class ToolbarService extends PubSubService {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
buttons: Record<string, unknown> = {};
|
public static _createButton(
|
||||||
|
type: string,
|
||||||
|
id: string,
|
||||||
|
icon: string,
|
||||||
|
label: string,
|
||||||
|
commands: Command | Commands,
|
||||||
|
tooltip?: string,
|
||||||
|
extraOptions?: ExtraButtonOptions
|
||||||
|
): Button {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
type,
|
||||||
|
commands,
|
||||||
|
tooltip,
|
||||||
|
...extraOptions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
buttons: Record<string, Button> = {};
|
||||||
state: {
|
state: {
|
||||||
primaryToolId: string;
|
primaryToolId: string;
|
||||||
toggles: Record<string, boolean>;
|
toggles: Record<string, boolean>;
|
||||||
@ -54,7 +99,7 @@ export default class ToolbarService extends PubSubService {
|
|||||||
this.buttons = {};
|
this.buttons = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
onModeEnter() {
|
public onModeEnter(): void {
|
||||||
this.reset();
|
this.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +110,7 @@ export default class ToolbarService extends PubSubService {
|
|||||||
* used for calling the specified interaction. That is, the command is
|
* used for calling the specified interaction. That is, the command is
|
||||||
* called with {...commandOptions,...options}
|
* called with {...commandOptions,...options}
|
||||||
*/
|
*/
|
||||||
recordInteraction(interaction, options?: Record<string, unknown>) {
|
public recordInteraction(interaction, options?: Record<string, unknown>) {
|
||||||
if (!interaction) {
|
if (!interaction) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -174,12 +219,18 @@ export default class ToolbarService extends PubSubService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getActiveTools() {
|
getActiveTools() {
|
||||||
return [this.state.primaryToolId, ...Object.keys(this.state.toggles)];
|
const activeTools = [this.state.primaryToolId];
|
||||||
|
Object.keys(this.state.toggles).forEach(key => {
|
||||||
|
if (this.state.toggles[key]) {
|
||||||
|
activeTools.push(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return activeTools;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the toggle state of a button to the isActive state */
|
/** Sets the toggle state of a button to the isToggled state */
|
||||||
public setActive(id: string, isActive: boolean): void {
|
public setToggled(id: string, isToggled: boolean): void {
|
||||||
if (isActive) {
|
if (isToggled) {
|
||||||
this.state.toggles[id] = true;
|
this.state.toggles[id] = true;
|
||||||
} else {
|
} else {
|
||||||
delete this.state.toggles[id];
|
delete this.state.toggles[id];
|
||||||
@ -197,10 +248,25 @@ export default class ToolbarService extends PubSubService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getButton(id) {
|
public getButton(id: string): Button {
|
||||||
return this.buttons[id];
|
return this.buttons[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Gets a nested button, found in the items/props for the children */
|
||||||
|
public getNestedButton(id: string): Button {
|
||||||
|
if (this.buttons[id]) {
|
||||||
|
return this.buttons[id];
|
||||||
|
}
|
||||||
|
for (const buttonId of Object.keys(this.buttons)) {
|
||||||
|
const { primary, items } = this.buttons[buttonId].props || {};
|
||||||
|
if (primary?.id === id) { return primary; }
|
||||||
|
const found = items?.find(childButton => childButton.id === id);
|
||||||
|
if (found) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setButtons(buttons) {
|
setButtons(buttons) {
|
||||||
this.buttons = buttons;
|
this.buttons = buttons;
|
||||||
this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {
|
this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {
|
||||||
@ -267,23 +333,22 @@ export default class ToolbarService extends PubSubService {
|
|||||||
if (!this.buttons[button.id]) {
|
if (!this.buttons[button.id]) {
|
||||||
this.buttons[button.id] = button;
|
this.buttons[button.id] = button;
|
||||||
}
|
}
|
||||||
this._setTogglesForButtonItems(button.props?.items);
|
|
||||||
});
|
});
|
||||||
|
this._setTogglesForButtonItems(buttons);
|
||||||
|
|
||||||
this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {});
|
this._broadcastEvent(this.EVENTS.TOOL_BAR_MODIFIED, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
_setTogglesForButtonItems(buttonItems) {
|
_setTogglesForButtonItems(buttons) {
|
||||||
if (!buttonItems) {
|
if (!buttons) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
buttonItems.forEach(buttonItem => {
|
buttons.forEach(buttonItem => {
|
||||||
if (buttonItem.type === 'toggle') {
|
if (buttonItem.type === 'toggle') {
|
||||||
this.state.toggles[buttonItem.id] = buttonItem.isActive;
|
this.setToggled(buttonItem.id, buttonItem.isActive);
|
||||||
} else {
|
|
||||||
this._setTogglesForButtonItems(buttonItem.props?.items);
|
|
||||||
}
|
}
|
||||||
|
this._setTogglesForButtonItems(buttonItem.props?.items);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,9 @@ export interface Command {
|
|||||||
context?: string;
|
context?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RunCommand = Command | Command[];
|
||||||
|
|
||||||
/** A set of commands, typically contained in a tool item or other configuration */
|
/** A set of commands, typically contained in a tool item or other configuration */
|
||||||
export interface Commands {
|
export interface Commands {
|
||||||
commands: Command[];
|
commands: RunCommand;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@ function LayoutSelector({ onSelection, rows, columns }) {
|
|||||||
border: '1px solid white',
|
border: '1px solid white',
|
||||||
backgroundColor: isHovered(index) ? '#5acce6' : '#0b1a42',
|
backgroundColor: isHovered(index) ? '#5acce6' : '#0b1a42',
|
||||||
}}
|
}}
|
||||||
|
data-cy={`Layout-${index % columns}-${Math.floor(index / columns)}`}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const x = index % columns;
|
const x = index % columns;
|
||||||
|
|||||||
2
testdata
2
testdata
@ -1 +1 @@
|
|||||||
Subproject commit da63adc206455ce860b87c6fe723d71fb415a891
|
Subproject commit 4d59660c2883ed749a680e5fb6d4624ab54c9422
|
||||||
Loading…
Reference in New Issue
Block a user