fix(ToolGroupService): Centralize tool binding persistence and provide API to add and remove persisted bindings. (#5989)
* Reset crosshair modifier to default when resetting user preferences. * Apply bindings when loading persisted bindings.
This commit is contained in:
parent
5bccf0a753
commit
979d619f7c
@ -23,6 +23,12 @@ type Tools = {
|
|||||||
disabled?: Tool[];
|
disabled?: Tool[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ToolBindings = Array<Record<string, unknown>>;
|
||||||
|
type PersistedToolBindings = Record<string, Record<string, ToolBindings>>;
|
||||||
|
type ApplyToolBindingsOptions = {
|
||||||
|
replaceExisting?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export default class ToolGroupService {
|
export default class ToolGroupService {
|
||||||
public static REGISTRATION = {
|
public static REGISTRATION = {
|
||||||
name: 'toolGroupService',
|
name: 'toolGroupService',
|
||||||
@ -39,6 +45,7 @@ export default class ToolGroupService {
|
|||||||
customizationService: any;
|
customizationService: any;
|
||||||
private toolGroupIds: Set<string> = new Set();
|
private toolGroupIds: Set<string> = new Set();
|
||||||
private toolBindingsMap: Map<string, Map<string, Array<Record<string, unknown>>>> = new Map();
|
private toolBindingsMap: Map<string, Map<string, Array<Record<string, unknown>>>> = new Map();
|
||||||
|
private defaultToolBindingsMap: Map<string, Map<string, ToolBindings>> = new Map();
|
||||||
/**
|
/**
|
||||||
* Service-specific
|
* Service-specific
|
||||||
*/
|
*/
|
||||||
@ -131,6 +138,7 @@ export default class ToolGroupService {
|
|||||||
ToolGroupManager.destroy();
|
ToolGroupManager.destroy();
|
||||||
this.toolGroupIds = new Set();
|
this.toolGroupIds = new Set();
|
||||||
this.toolBindingsMap.clear();
|
this.toolBindingsMap.clear();
|
||||||
|
this.defaultToolBindingsMap.clear();
|
||||||
|
|
||||||
eventTarget.removeEventListener(Enums.Events.TOOL_ACTIVATED, this._onToolActivated);
|
eventTarget.removeEventListener(Enums.Events.TOOL_ACTIVATED, this._onToolActivated);
|
||||||
}
|
}
|
||||||
@ -255,22 +263,61 @@ export default class ToolGroupService {
|
|||||||
public getToolBindings(
|
public getToolBindings(
|
||||||
toolGroupId: string,
|
toolGroupId: string,
|
||||||
toolName: string
|
toolName: string
|
||||||
): Array<Record<string, unknown>> | undefined {
|
): ToolBindings | undefined {
|
||||||
return this.toolBindingsMap.get(toolGroupId)?.get(toolName);
|
return this.toolBindingsMap.get(toolGroupId)?.get(toolName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public setToolBindings(
|
public setToolBindings(toolGroupId: string, toolName: string, bindings: ToolBindings): void {
|
||||||
toolGroupId: string,
|
|
||||||
toolName: string,
|
|
||||||
bindings: Array<Record<string, unknown>>
|
|
||||||
): void {
|
|
||||||
if (!this.toolBindingsMap.has(toolGroupId)) {
|
if (!this.toolBindingsMap.has(toolGroupId)) {
|
||||||
this.toolBindingsMap.set(toolGroupId, new Map());
|
this.toolBindingsMap.set(toolGroupId, new Map());
|
||||||
}
|
}
|
||||||
this.toolBindingsMap.get(toolGroupId).set(toolName, bindings);
|
this.toolBindingsMap.get(toolGroupId).set(toolName, this._cloneToolBindings(bindings));
|
||||||
}
|
}
|
||||||
|
|
||||||
public applyToolBindings(toolGroupId: string, toolName: string): void {
|
public getDefaultToolBindings(toolGroupId: string, toolName: string): ToolBindings | undefined {
|
||||||
|
const defaultBindings = this.defaultToolBindingsMap.get(toolGroupId)?.get(toolName);
|
||||||
|
return defaultBindings ? this._cloneToolBindings(defaultBindings) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public persistToolBindings(toolGroupId: string, toolName: string, bindings: ToolBindings): void {
|
||||||
|
const persistedBindings = this._readPersistedToolBindings();
|
||||||
|
if (!persistedBindings[toolGroupId]) {
|
||||||
|
persistedBindings[toolGroupId] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
persistedBindings[toolGroupId][toolName] = bindings;
|
||||||
|
this._writePersistedToolBindings(persistedBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removePersistedToolBindings(toolGroupId: string, toolName?: string): void {
|
||||||
|
const persistedBindings = this._readPersistedToolBindings();
|
||||||
|
if (!persistedBindings[toolGroupId]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toolName) {
|
||||||
|
delete persistedBindings[toolGroupId][toolName];
|
||||||
|
if (!Object.keys(persistedBindings[toolGroupId]).length) {
|
||||||
|
delete persistedBindings[toolGroupId];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete persistedBindings[toolGroupId];
|
||||||
|
}
|
||||||
|
|
||||||
|
this._writePersistedToolBindings(persistedBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the currently tracked bindings to the runtime tool instance.
|
||||||
|
*
|
||||||
|
* Note: this method may activate tools that are currently Passive or Enabled.
|
||||||
|
* Assigning bindings is treated as making the tool interactable.
|
||||||
|
*/
|
||||||
|
public applyToolBindings(
|
||||||
|
toolGroupId: string,
|
||||||
|
toolName: string,
|
||||||
|
options: ApplyToolBindingsOptions = {}
|
||||||
|
): void {
|
||||||
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
|
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
|
||||||
if (!toolGroup || !toolGroup.hasTool(toolName)) {
|
if (!toolGroup || !toolGroup.hasTool(toolName)) {
|
||||||
return;
|
return;
|
||||||
@ -285,6 +332,10 @@ export default class ToolGroupService {
|
|||||||
mode === Enums.ToolModes.Passive ||
|
mode === Enums.ToolModes.Passive ||
|
||||||
mode === Enums.ToolModes.Enabled
|
mode === Enums.ToolModes.Enabled
|
||||||
) {
|
) {
|
||||||
|
if (options.replaceExisting) {
|
||||||
|
// Opt-in behavior for callers that need replacement semantics.
|
||||||
|
toolGroup.setToolDisabled(toolName);
|
||||||
|
}
|
||||||
toolGroup.setToolActive(toolName, { bindings });
|
toolGroup.setToolActive(toolName, { bindings });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,6 +361,7 @@ export default class ToolGroupService {
|
|||||||
active.forEach(({ toolName, bindings }) => {
|
active.forEach(({ toolName, bindings }) => {
|
||||||
if (bindings) {
|
if (bindings) {
|
||||||
this.setToolBindings(toolGroup.id, toolName, bindings);
|
this.setToolBindings(toolGroup.id, toolName, bindings);
|
||||||
|
this._setDefaultToolBindingsIfMissing(toolGroup.id, toolName, bindings);
|
||||||
}
|
}
|
||||||
toolGroup.setToolActive(toolName, { bindings });
|
toolGroup.setToolActive(toolName, { bindings });
|
||||||
});
|
});
|
||||||
@ -319,6 +371,7 @@ export default class ToolGroupService {
|
|||||||
passive.forEach(({ toolName, bindings }) => {
|
passive.forEach(({ toolName, bindings }) => {
|
||||||
if (bindings) {
|
if (bindings) {
|
||||||
this.setToolBindings(toolGroup.id, toolName, bindings);
|
this.setToolBindings(toolGroup.id, toolName, bindings);
|
||||||
|
this._setDefaultToolBindingsIfMissing(toolGroup.id, toolName, bindings);
|
||||||
}
|
}
|
||||||
toolGroup.setToolPassive(toolName);
|
toolGroup.setToolPassive(toolName);
|
||||||
});
|
});
|
||||||
@ -328,6 +381,7 @@ export default class ToolGroupService {
|
|||||||
enabled.forEach(({ toolName, bindings }) => {
|
enabled.forEach(({ toolName, bindings }) => {
|
||||||
if (bindings) {
|
if (bindings) {
|
||||||
this.setToolBindings(toolGroup.id, toolName, bindings);
|
this.setToolBindings(toolGroup.id, toolName, bindings);
|
||||||
|
this._setDefaultToolBindingsIfMissing(toolGroup.id, toolName, bindings);
|
||||||
}
|
}
|
||||||
toolGroup.setToolEnabled(toolName);
|
toolGroup.setToolEnabled(toolName);
|
||||||
});
|
});
|
||||||
@ -337,12 +391,28 @@ export default class ToolGroupService {
|
|||||||
disabled.forEach(({ toolName, bindings }) => {
|
disabled.forEach(({ toolName, bindings }) => {
|
||||||
if (bindings) {
|
if (bindings) {
|
||||||
this.setToolBindings(toolGroup.id, toolName, bindings);
|
this.setToolBindings(toolGroup.id, toolName, bindings);
|
||||||
|
this._setDefaultToolBindingsIfMissing(toolGroup.id, toolName, bindings);
|
||||||
}
|
}
|
||||||
toolGroup.setToolDisabled(toolName);
|
toolGroup.setToolDisabled(toolName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _setDefaultToolBindingsIfMissing(
|
||||||
|
toolGroupId: string,
|
||||||
|
toolName: string,
|
||||||
|
bindings: ToolBindings
|
||||||
|
): void {
|
||||||
|
if (!this.defaultToolBindingsMap.has(toolGroupId)) {
|
||||||
|
this.defaultToolBindingsMap.set(toolGroupId, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolMap = this.defaultToolBindingsMap.get(toolGroupId);
|
||||||
|
if (!toolMap.has(toolName)) {
|
||||||
|
toolMap.set(toolName, this._cloneToolBindings(bindings));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _addTools(toolGroup, tools) {
|
private _addTools(toolGroup, tools) {
|
||||||
const addTools = tools => {
|
const addTools = tools => {
|
||||||
tools.forEach(({ toolName, parentTool, configuration }) => {
|
tools.forEach(({ toolName, parentTool, configuration }) => {
|
||||||
@ -374,24 +444,60 @@ export default class ToolGroupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _loadPersistedBindings(toolGroupId: string): void {
|
private _loadPersistedBindings(toolGroupId: string): void {
|
||||||
|
const toolGroupBindings = this._readPersistedToolBindings()[toolGroupId];
|
||||||
|
if (!toolGroupBindings) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolGroup = ToolGroupManager.getToolGroup(toolGroupId);
|
||||||
|
|
||||||
|
for (const [toolName, bindings] of Object.entries(toolGroupBindings)) {
|
||||||
|
this.setToolBindings(toolGroupId, toolName, bindings as ToolBindings);
|
||||||
|
|
||||||
|
if (!toolGroup || !toolGroup.hasTool(toolName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mode } = toolGroup.getToolOptions(toolName);
|
||||||
|
if (mode === Enums.ToolModes.Active) {
|
||||||
|
this.applyToolBindings(toolGroupId, toolName, { replaceExisting: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _readPersistedToolBindings(): PersistedToolBindings {
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(this._getToolBindingsStorageKey());
|
const stored = localStorage.getItem(this._getToolBindingsStorageKey());
|
||||||
if (!stored) {
|
if (!stored) {
|
||||||
return;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed = JSON.parse(stored);
|
const parsed = JSON.parse(stored);
|
||||||
const toolGroupBindings = parsed[toolGroupId];
|
if (!parsed || typeof parsed !== 'object') {
|
||||||
if (!toolGroupBindings) {
|
return {};
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const [toolName, bindings] of Object.entries(toolGroupBindings)) {
|
|
||||||
this.setToolBindings(toolGroupId, toolName, bindings as Array<Record<string, unknown>>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return parsed as PersistedToolBindings;
|
||||||
} catch {
|
} catch {
|
||||||
// ignore corrupt localStorage
|
// ignore corrupt localStorage
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _writePersistedToolBindings(bindings: PersistedToolBindings): void {
|
||||||
|
const storageKey = this._getToolBindingsStorageKey();
|
||||||
|
if (!Object.keys(bindings).length) {
|
||||||
|
localStorage.removeItem(storageKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem(storageKey, JSON.stringify(bindings));
|
||||||
|
}
|
||||||
|
|
||||||
|
private _cloneToolBindings(bindings: ToolBindings): ToolBindings {
|
||||||
|
return bindings.map(binding => ({ ...binding }));
|
||||||
|
}
|
||||||
|
|
||||||
private _getToolBindingsStorageKey(): string {
|
private _getToolBindingsStorageKey(): string {
|
||||||
const customizationValue = this.customizationService?.getCustomization(
|
const customizationValue = this.customizationService?.getCustomization(
|
||||||
'ohif.userPreferences.toolBindingsStorageKey'
|
'ohif.userPreferences.toolBindingsStorageKey'
|
||||||
|
|||||||
@ -26,17 +26,12 @@ const MODIFIER_OPTIONS = [
|
|||||||
|
|
||||||
const DEFAULT_TOOL_BINDINGS_STORAGE_KEY = 'user-preferred-tool-bindings';
|
const DEFAULT_TOOL_BINDINGS_STORAGE_KEY = 'user-preferred-tool-bindings';
|
||||||
|
|
||||||
function getToolBindingsStorageKey(customizationService: any): string {
|
function getToolModifier(
|
||||||
const customizationValue = customizationService?.getCustomization(
|
toolGroupService: any,
|
||||||
'ohif.userPreferences.toolBindingsStorageKey'
|
toolGroupId: string,
|
||||||
);
|
toolName: string,
|
||||||
|
mouseButton: number
|
||||||
return typeof customizationValue === 'string' && customizationValue.length > 0
|
): string | null {
|
||||||
? customizationValue
|
|
||||||
: DEFAULT_TOOL_BINDINGS_STORAGE_KEY;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getToolModifier(toolGroupService: any, toolGroupId: string, toolName: string): string | null {
|
|
||||||
if (!toolGroupService) {
|
if (!toolGroupService) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -45,7 +40,28 @@ function getToolModifier(toolGroupService: any, toolGroupId: string, toolName: s
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const modifierBinding = bindings.find(
|
const modifierBinding = bindings.find(
|
||||||
binding => binding.modifierKey != null && binding.numTouchPoints == null
|
binding =>
|
||||||
|
binding.mouseButton === mouseButton &&
|
||||||
|
binding.modifierKey != null &&
|
||||||
|
binding.numTouchPoints == null
|
||||||
|
);
|
||||||
|
|
||||||
|
return modifierBinding?.modifierKey != null ? String(modifierBinding.modifierKey) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getModifierFromBindings(
|
||||||
|
bindings: Array<Record<string, unknown>> | undefined,
|
||||||
|
mouseButton: number
|
||||||
|
): string | null {
|
||||||
|
if (!bindings?.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifierBinding = bindings.find(
|
||||||
|
binding =>
|
||||||
|
binding.mouseButton === mouseButton &&
|
||||||
|
binding.modifierKey != null &&
|
||||||
|
binding.numTouchPoints == null
|
||||||
);
|
);
|
||||||
|
|
||||||
return modifierBinding?.modifierKey != null ? String(modifierBinding.modifierKey) : null;
|
return modifierBinding?.modifierKey != null ? String(modifierBinding.modifierKey) : null;
|
||||||
@ -55,8 +71,6 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
|||||||
const { hotkeysManager, servicesManager } = useSystem();
|
const { hotkeysManager, servicesManager } = useSystem();
|
||||||
const { t, i18n: i18nextInstance } = useTranslation('UserPreferencesModal');
|
const { t, i18n: i18nextInstance } = useTranslation('UserPreferencesModal');
|
||||||
const toolGroupService = (servicesManager as any)?.services?.toolGroupService;
|
const toolGroupService = (servicesManager as any)?.services?.toolGroupService;
|
||||||
const customizationService = (servicesManager as any)?.services?.customizationService;
|
|
||||||
const toolBindingsStorageKey = getToolBindingsStorageKey(customizationService);
|
|
||||||
|
|
||||||
const { hotkeyDefinitions = {}, hotkeyDefaults = {} } = hotkeysManager;
|
const { hotkeyDefinitions = {}, hotkeyDefaults = {} } = hotkeysManager;
|
||||||
|
|
||||||
@ -89,7 +103,11 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
|||||||
const currentLanguage = currentLanguageFn();
|
const currentLanguage = currentLanguageFn();
|
||||||
|
|
||||||
const initialCrosshairModifier = useMemo(
|
const initialCrosshairModifier = useMemo(
|
||||||
() => getToolModifier(toolGroupService, 'mpr', 'Crosshairs'),
|
() => getToolModifier(toolGroupService, 'mpr', 'Crosshairs', 1),
|
||||||
|
[toolGroupService]
|
||||||
|
);
|
||||||
|
const defaultCrosshairBindings = useMemo(
|
||||||
|
() => toolGroupService?.getDefaultToolBindings?.('mpr', 'Crosshairs'),
|
||||||
[toolGroupService]
|
[toolGroupService]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -121,11 +139,17 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
|||||||
...state,
|
...state,
|
||||||
languageValue: defaultLanguage.value,
|
languageValue: defaultLanguage.value,
|
||||||
hotkeyDefinitions: resolvedHotkeyDefaults,
|
hotkeyDefinitions: resolvedHotkeyDefaults,
|
||||||
crosshairModifier: initialCrosshairModifier,
|
crosshairModifier: getModifierFromBindings(defaultCrosshairBindings, 1),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
hotkeysManager.restoreDefaultBindings();
|
hotkeysManager.restoreDefaultBindings();
|
||||||
localStorage.removeItem(toolBindingsStorageKey);
|
if (toolGroupService && defaultCrosshairBindings?.length) {
|
||||||
|
toolGroupService.setToolBindings('mpr', 'Crosshairs', defaultCrosshairBindings);
|
||||||
|
toolGroupService.applyToolBindings('mpr', 'Crosshairs', {
|
||||||
|
replaceExisting: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
toolGroupService?.removePersistedToolBindings('mpr', 'Crosshairs');
|
||||||
};
|
};
|
||||||
|
|
||||||
const displayNames = React.useMemo(() => {
|
const displayNames = React.useMemo(() => {
|
||||||
@ -280,11 +304,10 @@ function UserPreferencesModalDefault({ hide }: { hide: () => void }) {
|
|||||||
{ mouseButton: 1, modifierKey: Number(state.crosshairModifier) },
|
{ mouseButton: 1, modifierKey: Number(state.crosshairModifier) },
|
||||||
];
|
];
|
||||||
toolGroupService.setToolBindings('mpr', 'Crosshairs', bindings);
|
toolGroupService.setToolBindings('mpr', 'Crosshairs', bindings);
|
||||||
toolGroupService.applyToolBindings('mpr', 'Crosshairs');
|
toolGroupService.applyToolBindings('mpr', 'Crosshairs', {
|
||||||
localStorage.setItem(
|
replaceExisting: true,
|
||||||
toolBindingsStorageKey,
|
});
|
||||||
JSON.stringify({ mpr: { Crosshairs: bindings } })
|
toolGroupService.persistToolBindings('mpr', 'Crosshairs', bindings);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hotkeysModule.stopRecord();
|
hotkeysModule.stopRecord();
|
||||||
|
|||||||
@ -41,6 +41,14 @@ The `ToolGroupService` emits the following events:
|
|||||||
- `createToolGroupAndAddTools(toolGroupId, tools)`: Creates a new tool group and adds the specified tools to it.
|
- `createToolGroupAndAddTools(toolGroupId, tools)`: Creates a new tool group and adds the specified tools to it.
|
||||||
- `getToolConfiguration(toolGroupId, toolName)`: Retrieves the configuration for the specified tool in the given tool group.
|
- `getToolConfiguration(toolGroupId, toolName)`: Retrieves the configuration for the specified tool in the given tool group.
|
||||||
- `setToolConfiguration(toolGroupId, toolName, config)`: Sets the configuration for the specified tool in the given tool group.
|
- `setToolConfiguration(toolGroupId, toolName, config)`: Sets the configuration for the specified tool in the given tool group.
|
||||||
|
- `getActivePrimaryMouseButtonTool(toolGroupId?)`: Returns the active primary mouse button tool for the specified tool group, or for the active viewport's tool group when omitted.
|
||||||
|
- `getToolBindings(toolGroupId, toolName)`: Returns the bindings from the service's internal binding map for the specified tool in the given tool group.
|
||||||
|
- `setToolBindings(toolGroupId, toolName, bindings)`: Updates the service's internal binding map for the specified tool in the given tool group. This does not change the tool's active runtime mode by itself.
|
||||||
|
- `getAllToolBindings()`: Returns all bindings currently stored in the service's internal binding map across tool groups.
|
||||||
|
- `getDefaultToolBindings(toolGroupId, toolName)`: Returns the default bindings captured when the tool group was initialized.
|
||||||
|
- `applyToolBindings(toolGroupId, toolName, options?)`: Reads the currently tracked bindings for that tool and applies them to the underlying runtime tool instance (via tool activation). This can activate tools that are currently `Passive` or `Enabled`, since assigning bindings is treated as making the tool interactable. Use this after `setToolBindings` when you want runtime behavior to update. Set `options.replaceExisting` to `true` to clear previous active bindings before applying.
|
||||||
|
- `persistToolBindings(toolGroupId, toolName, bindings)`: Persists tool bindings for a specific tool in a tool group to user preferences storage.
|
||||||
|
- `removePersistedToolBindings(toolGroupId, toolName?)`: Removes persisted tool bindings for a specific tool, or for the whole tool group when `toolName` is omitted.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user