Skip to content

Commit 7c55342

Browse files
committed
custom keybinds now supports modifying the other actionMap contexts (perspectiveView, sliceView)
cleaned up code, addressed PR comments
1 parent fc515d7 commit 7c55342

File tree

2 files changed

+84
-58
lines changed

2 files changed

+84
-58
lines changed

src/ui/default_viewer_setup.ts

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17-
import type { UserLayer, UserLayerConstructor } from "#src/layer/index.js";
17+
import { schemePattern } from "#src/kvstore/url.js";
18+
import type { UserLayer } from "#src/layer/index.js";
1819
import { layerTypes } from "#src/layer/index.js";
1920
import { StatusMessage } from "#src/status.js";
2021
import {
@@ -27,60 +28,72 @@ import { bindTitle } from "#src/ui/title.js";
2728
import type { Tool } from "#src/ui/tool.js";
2829
import { restoreTool } from "#src/ui/tool.js";
2930
import { UrlHashBinding } from "#src/ui/url_hash_binding.js";
31+
import type {
32+
ActionIdentifier,
33+
EventAction,
34+
EventActionMap,
35+
EventIdentifier,
36+
} from "#src/util/event_action_map.js";
3037
import {
3138
verifyObject,
3239
verifyObjectProperty,
3340
verifyString,
3441
} from "#src/util/json.js";
42+
import type { Viewer } from "#src/viewer.js";
3543

3644
declare let NEUROGLANCER_DEFAULT_STATE_FRAGMENT: string | undefined;
3745

3846
type CustomToolBinding = {
39-
layer: string;
47+
layerType: string;
4048
tool: unknown;
4149
provider?: string;
4250
};
4351

52+
type CustomBinding = {
53+
action: EventAction | ActionIdentifier | CustomToolBinding | boolean;
54+
context?: "global" | "perspectiveView" | "sliceView";
55+
};
56+
4457
type CustomBindings = {
45-
[key: string]: CustomToolBinding | string | boolean;
58+
[identifier: EventIdentifier]: CustomBinding;
4659
};
4760

48-
declare const CUSTOM_BINDINGS: CustomBindings | undefined;
49-
export const hasCustomBindings =
50-
typeof CUSTOM_BINDINGS !== "undefined" &&
51-
Object.keys(CUSTOM_BINDINGS).length > 0;
61+
declare const NEUROGLANCER_CUSTOM_INPUT_BINDINGS: CustomBindings | undefined;
5262

53-
/**
54-
* Sets up the default neuroglancer viewer.
55-
*/
56-
export function setupDefaultViewer() {
57-
const viewer = ((<any>window).viewer = makeDefaultViewer());
58-
setDefaultInputEventBindings(viewer.inputEventBindings);
63+
export const hasCustomBindings =
64+
typeof NEUROGLANCER_CUSTOM_INPUT_BINDINGS !== "undefined" &&
65+
Object.keys(NEUROGLANCER_CUSTOM_INPUT_BINDINGS).length > 0;
5966

67+
function setCustomInputEventBindings(viewer: Viewer, bindings: CustomBindings) {
6068
const bindNonLayerSpecificTool = (
61-
obj: unknown,
62-
toolKey: string,
63-
desiredLayerType: UserLayerConstructor,
64-
desiredProvider?: string,
69+
key: string,
70+
customBinding: CustomToolBinding,
6571
) => {
72+
const { layerType, provider: desiredProvider } = customBinding;
73+
let toolJson = customBinding.tool;
74+
const desiredLayerConstructor = layerTypes.get(layerType);
75+
if (desiredLayerConstructor === undefined) {
76+
throw new Error(`Invalid layer type: ${layerType}`);
77+
}
78+
const toolKey = key.charAt(key.length - 1).toUpperCase();
6679
let previousTool: Tool<object> | undefined;
6780
let previousLayer: UserLayer | undefined;
68-
if (typeof obj === "string") {
69-
obj = { type: obj };
81+
if (typeof toolJson === "string") {
82+
toolJson = { type: toolJson };
7083
}
71-
verifyObject(obj);
72-
const type = verifyObjectProperty(obj, "type", verifyString);
73-
viewer.bindAction(`tool-${type}`, () => {
84+
verifyObject(toolJson);
85+
const type = verifyObjectProperty(toolJson, "type", verifyString);
86+
const action = `tool-${type}`;
87+
viewer.bindAction(action, () => {
7488
const acceptableLayers = viewer.layerManager.managedLayers.filter(
7589
(managedLayer) => {
7690
const correctLayerType =
77-
managedLayer.layer instanceof desiredLayerType;
91+
managedLayer.layer instanceof desiredLayerConstructor;
7892
if (desiredProvider && correctLayerType) {
7993
for (const dataSource of managedLayer.layer?.dataSources || []) {
80-
const protocol = viewer.dataSourceProvider.getProvider(
81-
dataSource.spec.url,
82-
)[2];
83-
if (protocol === desiredProvider) {
94+
const m = dataSource.spec.url.match(schemePattern)!;
95+
const scheme = m[1];
96+
if (scheme === desiredProvider) {
8497
return true;
8598
}
8699
}
@@ -94,7 +107,7 @@ export function setupDefaultViewer() {
94107
const firstLayer = acceptableLayers[0].layer;
95108
if (firstLayer) {
96109
if (firstLayer !== previousLayer) {
97-
previousTool = restoreTool(firstLayer, obj);
110+
previousTool = restoreTool(firstLayer, toolJson);
98111
previousLayer = firstLayer;
99112
}
100113
if (previousTool) {
@@ -103,34 +116,44 @@ export function setupDefaultViewer() {
103116
}
104117
}
105118
});
119+
return action;
106120
};
107121

108-
if (hasCustomBindings) {
109-
for (const [key, val] of Object.entries(CUSTOM_BINDINGS!)) {
110-
if (typeof val === "string") {
111-
viewer.inputEventBindings.global.set(key, val);
112-
} else if (typeof val === "boolean") {
113-
if (!val) {
114-
viewer.inputEventBindings.global.delete(key);
115-
viewer.inputEventBindings.global.parents.map((parent) =>
116-
parent.delete(key),
117-
);
118-
}
119-
} else {
120-
viewer.inputEventBindings.global.set(key, `tool-${val.tool}`);
121-
const layerConstructor = layerTypes.get(val.layer);
122-
if (layerConstructor) {
123-
const toolKey = key.charAt(key.length - 1).toUpperCase();
124-
bindNonLayerSpecificTool(
125-
val.tool,
126-
toolKey,
127-
layerConstructor,
128-
val.provider,
129-
);
130-
}
122+
const deleteKey = (map: EventActionMap, key: string) => {
123+
map.delete(key);
124+
for (const pMap of map.parents) {
125+
deleteKey(pMap, key);
126+
}
127+
};
128+
129+
for (const [key, val] of Object.entries(bindings)) {
130+
const { action, context = "global" } = val;
131+
const actionMap = viewer.inputEventBindings[context];
132+
if (actionMap === undefined) {
133+
throw new Error(`invalid action map context: ${context}`);
134+
}
135+
if (typeof action === "boolean") {
136+
if (action === false) {
137+
deleteKey(actionMap, key);
131138
}
139+
} else if (typeof action === "string" || "action" in action) {
140+
actionMap.set(key, action);
141+
} else {
142+
const toolAction = bindNonLayerSpecificTool(key, action);
143+
actionMap.set(key, toolAction);
132144
}
133145
}
146+
}
147+
148+
/**
149+
* Sets up the default neuroglancer viewer.
150+
*/
151+
export function setupDefaultViewer() {
152+
const viewer = ((<any>window).viewer = makeDefaultViewer());
153+
setDefaultInputEventBindings(viewer.inputEventBindings);
154+
if (hasCustomBindings) {
155+
setCustomInputEventBindings(viewer, NEUROGLANCER_CUSTOM_INPUT_BINDINGS!);
156+
}
134157

135158
const hashBinding = viewer.registerDisposer(
136159
new UrlHashBinding(

src/ui/tool.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -396,20 +396,23 @@ export class GlobalToolBinder extends RefCounted {
396396
}
397397

398398
activate(key: string, tool?: Tool<object>): Borrowed<Tool> | undefined {
399-
tool = tool || this.get(key);
399+
tool = tool ?? this.get(key);
400400
if (tool === undefined) {
401401
this.deactivate_();
402402
return;
403403
}
404404
this.debounceDeactivate.cancel();
405405
const activeTool = this.activeTool_;
406-
if (tool.toJSON() === activeTool?.tool.toJSON()) {
407-
if (tool.toggle) {
408-
this.deactivate_();
409-
}
410-
return;
411-
}
412406
if (activeTool !== undefined) {
407+
if (
408+
activeTool.tool.constructor === tool.constructor &&
409+
activeTool.tool.context === tool.context
410+
) {
411+
if (tool.toggle) {
412+
this.deactivate_();
413+
}
414+
return;
415+
}
413416
if (activeTool.tool.toggle && !tool.toggle) {
414417
this.queuedTool = activeTool.tool;
415418
}

0 commit comments

Comments
 (0)