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" ;
1819import { layerTypes } from "#src/layer/index.js" ;
1920import { StatusMessage } from "#src/status.js" ;
2021import {
@@ -27,60 +28,72 @@ import { bindTitle } from "#src/ui/title.js";
2728import type { Tool } from "#src/ui/tool.js" ;
2829import { restoreTool } from "#src/ui/tool.js" ;
2930import { 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" ;
3037import {
3138 verifyObject ,
3239 verifyObjectProperty ,
3340 verifyString ,
3441} from "#src/util/json.js" ;
42+ import type { Viewer } from "#src/viewer.js" ;
3543
3644declare let NEUROGLANCER_DEFAULT_STATE_FRAGMENT : string | undefined ;
3745
3846type 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+
4457type 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 (
0 commit comments