HostExtensionServices.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. //
  2. // Copyright (c) 2014-2016 THUNDERBEAST GAMES LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. import * as EditorEvents from "../editor/EditorEvents";
  23. import * as EditorUI from "../ui/EditorUI";
  24. import MainFrame = require("../ui/frames/MainFrame");
  25. import InspectorFrame = require("../ui/frames/inspector/InspectorFrame");
  26. import ModalOps = require("../ui/modal/ModalOps");
  27. import ResourceOps = require("../resources/ResourceOps");
  28. /**
  29. * Generic registry for storing Editor Extension Services
  30. */
  31. export class ServicesProvider<T extends Editor.Extensions.ServiceEventListener> implements Editor.Extensions.ServicesProvider<T> {
  32. registeredServices: T[] = [];
  33. /**
  34. * Adds a service to the registered services list for this type of service
  35. * @param {T} service the service to register
  36. */
  37. register(service: T) {
  38. this.registeredServices.push(service);
  39. }
  40. unregister(service: T) {
  41. var index = this.registeredServices.indexOf(service, 0);
  42. if (index > -1) {
  43. this.registeredServices.splice(index, 1);
  44. }
  45. }
  46. }
  47. export interface ServiceEventSubscriber {
  48. /**
  49. * Allow this service registry to subscribe to events that it is interested in
  50. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  51. */
  52. subscribeToEvents(topLevelWindow: Atomic.UIWidget);
  53. }
  54. /**
  55. * Registry for service extensions that are concerned about project events
  56. */
  57. export class ProjectServicesProvider extends ServicesProvider<Editor.HostExtensions.ProjectServicesEventListener> implements Editor.HostExtensions.ProjectServicesProvider {
  58. constructor() {
  59. super();
  60. }
  61. /**
  62. * Allow this service registry to subscribe to events that it is interested in
  63. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  64. */
  65. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  66. eventDispatcher.subscribeToEvent(EditorEvents.LoadProjectNotificationEvent((ev) => this.projectLoaded(ev)));
  67. eventDispatcher.subscribeToEvent(Editor.EditorCloseProjectEvent((ev) => this.projectUnloaded(ev)));
  68. eventDispatcher.subscribeToEvent(Editor.EditorPlayRequestEvent(() => this.playerStarted()));
  69. }
  70. /**
  71. * Called when the project is unloaded
  72. * @param {[type]} data Event info from the project unloaded event
  73. */
  74. projectUnloaded(data) {
  75. // Need to use a for loop for length down to 0 because extensions *could* delete themselves from the list on projectUnloaded
  76. for (let i = this.registeredServices.length - 1; i >= 0; i--) {
  77. let service = this.registeredServices[i];
  78. // Notify services that the project has been unloaded
  79. try {
  80. if (service.projectUnloaded) {
  81. service.projectUnloaded();
  82. }
  83. } catch (e) {
  84. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e} \n\n ${e.stack}`);
  85. }
  86. };
  87. }
  88. /**
  89. * Called when the project is loaded
  90. * @param {[type]} data Event info from the project unloaded event
  91. */
  92. projectLoaded(ev: Editor.EditorLoadProjectEvent) {
  93. // Need to use a for loop and don't cache the length because the list of services *may* change while processing. Extensions could be appended to the end
  94. for (let i = 0; i < this.registeredServices.length; i++) {
  95. let service = this.registeredServices[i];
  96. try {
  97. // Notify services that the project has just been loaded
  98. if (service.projectLoaded) {
  99. service.projectLoaded(ev);
  100. }
  101. } catch (e) {
  102. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  103. }
  104. };
  105. }
  106. playerStarted() {
  107. this.registeredServices.forEach((service) => {
  108. try {
  109. // Notify services that the project has just been loaded
  110. if (service.playerStarted) {
  111. service.playerStarted();
  112. }
  113. } catch (e) {
  114. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}\n \n ${e.stack}`);
  115. }
  116. });
  117. }
  118. /**
  119. * Return a preference value or the provided default from the user settings file
  120. * @param {string} extensionName name of the extension the preference lives under
  121. * @param {string} preferenceName name of the preference to retrieve
  122. * @param {number | boolean | string} defaultValue value to return if pref doesn't exist
  123. * @return {number|boolean|string}
  124. */
  125. getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: number): number;
  126. getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: string): string;
  127. getUserPreference(settingsGroup: string, preferenceName: string, defaultValue?: boolean): boolean;
  128. getUserPreference(extensionName: string, preferenceName: string, defaultValue?: any): any {
  129. return EditorUI.getEditor().getUserPreference(extensionName, preferenceName, defaultValue);
  130. }
  131. /**
  132. * Sets a user preference value in the user settings file
  133. * @param {string} extensionName name of the extension the preference lives under
  134. * @param {string} preferenceName name of the preference to set
  135. * @param {number | boolean | string} value value to set
  136. */
  137. setUserPreference(extensionName: string, preferenceName: string, value: number | boolean | string) {
  138. EditorUI.getEditor().setUserPreference(extensionName, preferenceName, value);
  139. }
  140. /**
  141. * Sets a group of user preference values in the user settings file located in the project. Elements in the
  142. * group will merge in with existing group preferences. Use this method if setting a bunch of settings
  143. * at once.
  144. * @param {string} extensionName name of the group the preference lives under
  145. * @param {string} groupPreferenceValues an object literal containing all of the preferences for the group.
  146. */
  147. setUserPreferenceGroup(extensionName: string, groupPreferenceValues: Object) {
  148. EditorUI.getEditor().setUserPreferenceGroup(extensionName, groupPreferenceValues);
  149. }
  150. }
  151. /**
  152. * Registry for service extensions that are concerned about Resources
  153. */
  154. export class ResourceServicesProvider extends ServicesProvider<Editor.HostExtensions.ResourceServicesEventListener> implements Editor.HostExtensions.ResourceServicesProvider {
  155. constructor() {
  156. super();
  157. }
  158. /**
  159. * Allow this service registry to subscribe to events that it is interested in
  160. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  161. */
  162. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  163. eventDispatcher.subscribeToEvent(Editor.EditorSaveResourceNotificationEvent((ev) => this.saveResource(ev)));
  164. eventDispatcher.subscribeToEvent(Editor.EditorDeleteResourceNotificationEvent((ev) => this.deleteResource(ev)));
  165. eventDispatcher.subscribeToEvent(Editor.EditorRenameResourceNotificationEvent((ev) => this.renameResource(ev)));
  166. eventDispatcher.subscribeToEvent(Editor.EditorEditResourceEvent((ev) => this.editResource(ev)));
  167. }
  168. /**
  169. * Called after a resource has been saved
  170. * @param {Editor.EditorEvents.SaveResourceEvent} ev
  171. */
  172. saveResource(ev: Editor.EditorSaveResourceEvent) {
  173. // run through and find any services that can handle this.
  174. this.registeredServices.forEach((service) => {
  175. try {
  176. // Verify that the service contains the appropriate methods and that it can save
  177. if (service.save) {
  178. service.save(ev);
  179. }
  180. } catch (e) {
  181. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  182. }
  183. });
  184. }
  185. /**
  186. * Called when a resource has been deleted
  187. */
  188. deleteResource(ev: Editor.EditorDeleteResourceEvent) {
  189. this.registeredServices.forEach((service) => {
  190. try {
  191. // Verify that the service contains the appropriate methods and that it can delete
  192. if (service.delete) {
  193. service.delete(ev);
  194. }
  195. } catch (e) {
  196. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  197. }
  198. });
  199. }
  200. /**
  201. * Called when a resource has been renamed
  202. * @param ev
  203. */
  204. renameResource(ev: Editor.EditorRenameResourceNotificationEvent) {
  205. this.registeredServices.forEach((service) => {
  206. try {
  207. // Verify that the service contains the appropriate methods and that it can handle the rename
  208. if (service.rename) {
  209. service.rename(ev);
  210. }
  211. } catch (e) {
  212. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  213. }
  214. });
  215. }
  216. /**
  217. * Called when a resource is about to be edited
  218. * @param {Editor.EditorEvents.EditResourceEvent} ev
  219. */
  220. editResource(ev: Editor.EditorEditResourceEvent) {
  221. this.registeredServices.forEach((service) => {
  222. try {
  223. // Verify that the service contains the appropriate methods and that it can handle the edit
  224. if (service.edit) {
  225. service.edit(ev);
  226. }
  227. } catch (e) {
  228. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  229. }
  230. });
  231. }
  232. /**
  233. * Create New Material
  234. * @param {string} resourcePath
  235. * @param {string} materialName
  236. * @param {boolean} reportError
  237. */
  238. createMaterial(resourcePath: string, materialName: string, reportError: boolean): boolean {
  239. return ResourceOps.CreateNewMaterial(resourcePath, materialName, reportError);
  240. }
  241. }
  242. /**
  243. * Registry for service extensions that are concerned about Scenes
  244. */
  245. export class SceneServicesProvider extends ServicesProvider<Editor.HostExtensions.SceneServicesEventListener> implements Editor.HostExtensions.SceneServicesProvider {
  246. constructor() {
  247. super();
  248. }
  249. /**
  250. * Allow this service registry to subscribe to events that it is interested in
  251. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  252. */
  253. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  254. eventDispatcher.subscribeToEvent(Editor.EditorActiveSceneEditorChangeEvent((ev) => this.activeSceneEditorChange(ev)));
  255. eventDispatcher.subscribeToEvent(Editor.EditorSceneClosedEvent((ev) => this.sceneClosed(ev)));
  256. }
  257. /**
  258. * Called after an active scene editor change
  259. * @param {Editor.EditorEvents.ActiveSceneEditorChangeEvent} ev
  260. */
  261. activeSceneEditorChange(ev: Editor.EditorActiveSceneEditorChangeEvent) {
  262. // run through and find any services that can handle this.
  263. this.registeredServices.forEach((service) => {
  264. try {
  265. // Verify that the service contains the appropriate methods and that it can save
  266. if (service.activeSceneEditorChanged) {
  267. service.activeSceneEditorChanged(ev);
  268. }
  269. } catch (e) {
  270. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  271. }
  272. });
  273. }
  274. /**
  275. * Called after a scene is closed
  276. * @param {Editor.EditorEvents.SceneClosedEvent} ev
  277. */
  278. sceneClosed(ev: Editor.EditorSceneClosedEvent) {
  279. // run through and find any services that can handle this.
  280. this.registeredServices.forEach((service) => {
  281. try {
  282. // Verify that the service contains the appropriate methods and that it can save
  283. if (service.editorSceneClosed) {
  284. service.editorSceneClosed(ev);
  285. }
  286. } catch (e) {
  287. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  288. }
  289. });
  290. }
  291. }
  292. /**
  293. * Registry for service extensions that are concerned about and need access to parts of the editor user interface
  294. * Note: we may want to move this out into it's own file since it has a bunch of editor dependencies
  295. */
  296. export class UIServicesProvider extends ServicesProvider<Editor.HostExtensions.UIServicesEventListener> implements Editor.HostExtensions.UIServicesProvider {
  297. constructor() {
  298. super();
  299. }
  300. private mainFrame: MainFrame = null;
  301. private inspectorFrame: InspectorFrame = null;
  302. private modalOps: ModalOps;
  303. init(mainFrame: MainFrame, modalOps: ModalOps) {
  304. // Only set these once
  305. if (this.mainFrame == null) {
  306. this.mainFrame = mainFrame;
  307. this.inspectorFrame = this.mainFrame.inspectorframe;
  308. }
  309. if (this.modalOps == null) {
  310. this.modalOps = modalOps;
  311. }
  312. }
  313. /**
  314. * Adds a new menu to the plugin menu
  315. * @param {string} id
  316. * @param {any} items
  317. * @return {Atomic.UIMenuItemSource}
  318. */
  319. createPluginMenuItemSource(id: string, items: any): Atomic.UIMenuItemSource {
  320. return this.mainFrame.menu.createPluginMenuItemSource(id, items);
  321. }
  322. /**
  323. * Removes a previously added menu from the plugin menu
  324. * @param {string} id
  325. */
  326. removePluginMenuItemSource(id: string) {
  327. this.mainFrame.menu.removePluginMenuItemSource(id);
  328. }
  329. /**
  330. * Returns the currently active resource editor or null
  331. * @return {Editor.ResourceEditor}
  332. */
  333. getCurrentResourceEditor(): Editor.ResourceEditor {
  334. return this.mainFrame.resourceframe.currentResourceEditor;
  335. }
  336. /**
  337. * Adds a new menu to the hierarchy context menu
  338. * @param {string} id
  339. * @param {any} items
  340. * @return {Atomic.UIMenuItemSource}
  341. */
  342. createHierarchyContextMenuItemSource(id: string, items: any): Atomic.UIMenuItemSource {
  343. return this.mainFrame.hierarchyFrame.menu.createPluginItemSource(id, items);
  344. }
  345. /**
  346. * Removes a previously added menu from the hierarchy context menu
  347. * @param {string} id
  348. */
  349. removeHierarchyContextMenuItemSource(id: string) {
  350. this.mainFrame.hierarchyFrame.menu.removePluginItemSource(id);
  351. }
  352. /**
  353. * Adds a new menu to the project context menu
  354. * @param {string} id
  355. * @param {any} items
  356. * @return {Atomic.UIMenuItemSource}
  357. */
  358. createProjectContextMenuItemSource(id: string, items: any): Atomic.UIMenuItemSource {
  359. return this.mainFrame.projectframe.menu.createPluginItemSource(id, items);
  360. }
  361. /**
  362. * Removes a previously added menu from the project context menu
  363. * @param {string} id
  364. */
  365. removeProjectContextMenuItemSource(id: string) {
  366. this.mainFrame.projectframe.menu.removePluginItemSource(id);
  367. }
  368. /**
  369. * Refreshes the hierarchy frame
  370. */
  371. refreshHierarchyFrame() {
  372. this.mainFrame.hierarchyFrame.populate();
  373. }
  374. /**
  375. * Loads Custom Inspector Widget
  376. * @param {Atomic.UIWidget} customInspector
  377. */
  378. loadCustomInspector(customInspector: Atomic.UIWidget) {
  379. if (this.inspectorFrame) {
  380. this.inspectorFrame.loadCustomInspectorWidget(customInspector);
  381. }
  382. }
  383. /**
  384. * Disaplays a modal window
  385. * @param {Editor.Modal.ModalWindow} window
  386. */
  387. showModalWindow(windowText: string, uifilename: string, handleWidgetEventCB: (ev: Atomic.UIWidgetEvent) => void): Editor.Modal.ExtensionWindow {
  388. return this.modalOps.showExtensionWindow(windowText, uifilename, handleWidgetEventCB);
  389. }
  390. /**
  391. * Disaplays a modal error window
  392. * @param {string} windowText
  393. * @param {string} message
  394. */
  395. showModalError(windowText: string, message: string) {
  396. return this.modalOps.showError(windowText, message);
  397. }
  398. /**
  399. * Displays a resource slection window
  400. * @param {string} windowText
  401. * @param {string} importerType
  402. * @param {string} resourceType
  403. * @param {function} callback
  404. * @param {any} retObject
  405. * @param {any} args
  406. */
  407. showResourceSelection(windowText: string, importerType: string, resourceType: string, callback: (retObject: any, args: any) => void, args: any = undefined) {
  408. this.modalOps.showResourceSelection(windowText, importerType, resourceType, callback);
  409. }
  410. /**
  411. * Will register a custom editor for a particular file type.
  412. * @param {Editor.Extensions.ResourceEditorBuilder} editorBuilder
  413. */
  414. registerCustomEditor(editorBuilder: Editor.Extensions.ResourceEditorBuilder) {
  415. this.mainFrame.resourceframe.resourceEditorProvider.registerCustomEditor(editorBuilder);
  416. }
  417. /**
  418. * Will unregister a previously registered editor builder
  419. * @param {Editor.Extensions.ResourceEditorBuilder} editorBuilder
  420. */
  421. unregisterCustomEditor(editorBuilder: Editor.Extensions.ResourceEditorBuilder) {
  422. this.mainFrame.resourceframe.resourceEditorProvider.unregisterCustomEditor(editorBuilder);
  423. }
  424. /**
  425. * Called when a menu item has been clicked
  426. * @param {string} refId
  427. * @type {boolean} return true if handled
  428. */
  429. menuItemClicked(refid: string): boolean {
  430. // run through and find any services that can handle this.
  431. return this.registeredServices.some((service) => {
  432. try {
  433. // Verify that the service contains the appropriate methods and that it can handle it
  434. if (service.menuItemClicked) {
  435. if (service.menuItemClicked(refid)) {
  436. return true;
  437. }
  438. }
  439. } catch (e) {
  440. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  441. }
  442. });
  443. }
  444. /**
  445. * Called when a context menu item in the hierarchy pane has been clicked
  446. * @param {Atomic.Node} node
  447. * @param {string} refId
  448. * @type {boolean} return true if handled
  449. */
  450. hierarchyContextItemClicked(node: Atomic.Node, refid: string): boolean {
  451. if (!node)
  452. return false;
  453. // run through and find any services that can handle this.
  454. return this.registeredServices.some((service) => {
  455. try {
  456. // Verify that the service contains the appropriate methods and that it can handle it
  457. if (service.hierarchyContextItemClicked) {
  458. if (service.hierarchyContextItemClicked(node, refid)) {
  459. return true;
  460. }
  461. }
  462. } catch (e) {
  463. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  464. }
  465. });
  466. }
  467. /**
  468. * Called when a context menu item in the hierarchy pane has been clicked
  469. * @param {ToolCore.Asset} asset
  470. * @param {string} refId
  471. * @type {boolean} return true if handled
  472. */
  473. projectContextItemClicked(asset: ToolCore.Asset, refid: string): boolean {
  474. if (!asset)
  475. return false;
  476. // run through and find any services that can handle this.
  477. return this.registeredServices.some((service) => {
  478. try {
  479. // Verify that the service contains the appropriate methods and that it can handle it
  480. if (service.projectContextItemClicked) {
  481. if (service.projectContextItemClicked(asset, refid)) {
  482. return true;
  483. }
  484. }
  485. } catch (e) {
  486. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  487. }
  488. });
  489. }
  490. /**
  491. * Called when a project asset in the hierarchy pane has been clicked
  492. * @param {ToolCore.Asset} asset
  493. * @type {boolean} return true if handled
  494. */
  495. projectAssetClicked(asset: ToolCore.Asset):boolean {
  496. if (!asset) {
  497. return false;
  498. }
  499. // run through and find any services that can handle this.
  500. return this.registeredServices.some((service) => {
  501. try {
  502. // Verify that the service contains the appropriate methods and that it can handle it
  503. if (service.projectAssetClicked) {
  504. if (service.projectAssetClicked(asset)) {
  505. return true;
  506. }
  507. }
  508. } catch (e) {
  509. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  510. }
  511. });
  512. }
  513. /**
  514. * Hooks into web messages coming in from web views
  515. * @param {[String|Object]} data
  516. */
  517. handleWebMessage(data: WebView.WebMessageEvent) {
  518. let messageType;
  519. let messageObject;
  520. try {
  521. messageObject = JSON.parse(data.request);
  522. messageType = messageObject.message;
  523. } catch (e) {
  524. // not JSON, we are just getting a notification message of some sort
  525. messageType = data.request;
  526. }
  527. // run through and find any services that can handle this.
  528. this.registeredServices.forEach((service) => {
  529. try {
  530. // Verify that the service contains the appropriate methods and that it can save
  531. if (service.handleWebMessage) {
  532. service.handleWebMessage(messageType, messageObject);
  533. }
  534. } catch (e) {
  535. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  536. }
  537. });
  538. }
  539. /**
  540. * Allow this service registry to subscribe to events that it is interested in
  541. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  542. */
  543. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  544. // Placeholder for when UI events published by the editor need to be listened for
  545. eventDispatcher.subscribeToEvent(WebView.WebMessageEvent((ev) => this.handleWebMessage(ev)));
  546. }
  547. }