HostExtensionServices.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  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 ModalOps = require("../ui/modal/ModalOps");
  26. import ResourceOps = require("../resources/ResourceOps");
  27. /**
  28. * Generic registry for storing Editor Extension Services
  29. */
  30. export class ServicesProvider<T extends Editor.Extensions.ServiceEventListener> implements Editor.Extensions.ServicesProvider<T> {
  31. registeredServices: T[] = [];
  32. /**
  33. * Adds a service to the registered services list for this type of service
  34. * @param {T} service the service to register
  35. */
  36. register(service: T) {
  37. this.registeredServices.push(service);
  38. }
  39. unregister(service: T) {
  40. var index = this.registeredServices.indexOf(service, 0);
  41. if (index > -1) {
  42. this.registeredServices.splice(index, 1);
  43. }
  44. }
  45. }
  46. export interface ServiceEventSubscriber {
  47. /**
  48. * Allow this service registry to subscribe to events that it is interested in
  49. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  50. */
  51. subscribeToEvents(topLevelWindow: Atomic.UIWidget);
  52. }
  53. /**
  54. * Registry for service extensions that are concerned about project events
  55. */
  56. export class ProjectServicesProvider extends ServicesProvider<Editor.HostExtensions.ProjectServicesEventListener> implements Editor.HostExtensions.ProjectServicesProvider {
  57. constructor() {
  58. super();
  59. }
  60. /**
  61. * Allow this service registry to subscribe to events that it is interested in
  62. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  63. */
  64. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  65. eventDispatcher.subscribeToEvent(EditorEvents.LoadProjectNotification, (ev) => this.projectLoaded(ev));
  66. eventDispatcher.subscribeToEvent(EditorEvents.CloseProject, (ev) => this.projectUnloaded(ev));
  67. eventDispatcher.subscribeToEvent(EditorEvents.PlayerStartRequest, () => this.playerStarted());
  68. }
  69. /**
  70. * Called when the project is unloaded
  71. * @param {[type]} data Event info from the project unloaded event
  72. */
  73. projectUnloaded(data) {
  74. // Need to use a for loop for length down to 0 because extensions *could* delete themselves from the list on projectUnloaded
  75. for (let i = this.registeredServices.length - 1; i >= 0; i--) {
  76. let service = this.registeredServices[i];
  77. // Notify services that the project has been unloaded
  78. try {
  79. if (service.projectUnloaded) {
  80. service.projectUnloaded();
  81. }
  82. } catch (e) {
  83. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e} \n\n ${e.stack}`);
  84. }
  85. };
  86. }
  87. /**
  88. * Called when the project is loaded
  89. * @param {[type]} data Event info from the project unloaded event
  90. */
  91. projectLoaded(ev: Editor.EditorEvents.LoadProjectEvent) {
  92. // 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
  93. for (let i = 0; i < this.registeredServices.length; i++) {
  94. let service = this.registeredServices[i];
  95. try {
  96. // Notify services that the project has just been loaded
  97. if (service.projectLoaded) {
  98. service.projectLoaded(ev);
  99. }
  100. } catch (e) {
  101. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  102. }
  103. };
  104. }
  105. playerStarted() {
  106. this.registeredServices.forEach((service) => {
  107. try {
  108. // Notify services that the project has just been loaded
  109. if (service.playerStarted) {
  110. service.playerStarted();
  111. }
  112. } catch (e) {
  113. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}\n \n ${e.stack}`);
  114. }
  115. });
  116. }
  117. }
  118. /**
  119. * Registry for service extensions that are concerned about Resources
  120. */
  121. export class ResourceServicesProvider extends ServicesProvider<Editor.HostExtensions.ResourceServicesEventListener> implements Editor.HostExtensions.ResourceServicesProvider {
  122. constructor() {
  123. super();
  124. }
  125. /**
  126. * Allow this service registry to subscribe to events that it is interested in
  127. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  128. */
  129. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  130. eventDispatcher.subscribeToEvent(EditorEvents.SaveResourceNotification, (ev) => this.saveResource(ev));
  131. eventDispatcher.subscribeToEvent(EditorEvents.DeleteResourceNotification, (ev) => this.deleteResource(ev));
  132. eventDispatcher.subscribeToEvent(EditorEvents.RenameResourceNotification, (ev) => this.renameResource(ev));
  133. }
  134. /**
  135. * Called after a resource has been saved
  136. * @param {Editor.EditorEvents.SaveResourceEvent} ev
  137. */
  138. saveResource(ev: Editor.EditorEvents.SaveResourceEvent) {
  139. // run through and find any services that can handle this.
  140. this.registeredServices.forEach((service) => {
  141. try {
  142. // Verify that the service contains the appropriate methods and that it can save
  143. if (service.save) {
  144. service.save(ev);
  145. }
  146. } catch (e) {
  147. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  148. }
  149. });
  150. }
  151. /**
  152. * Called when a resource has been deleted
  153. */
  154. deleteResource(ev: Editor.EditorEvents.DeleteResourceEvent) {
  155. this.registeredServices.forEach((service) => {
  156. try {
  157. // Verify that the service contains the appropriate methods and that it can delete
  158. if (service.delete) {
  159. service.delete(ev);
  160. }
  161. } catch (e) {
  162. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  163. }
  164. });
  165. }
  166. /**
  167. * Called when a resource has been renamed
  168. * @param {Editor.EditorEvents.RenameResourceEvent} ev
  169. */
  170. renameResource(ev: Editor.EditorEvents.RenameResourceEvent) {
  171. this.registeredServices.forEach((service) => {
  172. try {
  173. // Verify that the service contains the appropriate methods and that it can handle the rename
  174. if (service.rename) {
  175. service.rename(ev);
  176. }
  177. } catch (e) {
  178. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  179. }
  180. });
  181. }
  182. /**
  183. * Create New Material
  184. * @param {string} resourcePath
  185. * @param {string} materialName
  186. * @param {boolean} reportError
  187. */
  188. createMaterial(resourcePath: string, materialName: string, reportError: boolean): boolean {
  189. return ResourceOps.CreateNewMaterial(resourcePath, materialName, reportError);
  190. }
  191. }
  192. /**
  193. * Registry for service extensions that are concerned about Scenes
  194. */
  195. export class SceneServicesProvider extends ServicesProvider<Editor.HostExtensions.SceneServicesEventListener> implements Editor.HostExtensions.SceneServicesProvider {
  196. constructor() {
  197. super();
  198. }
  199. /**
  200. * Allow this service registry to subscribe to events that it is interested in
  201. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  202. */
  203. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  204. eventDispatcher.subscribeToEvent(EditorEvents.ActiveSceneEditorChange, (ev) => this.activeSceneEditorChange(ev));
  205. eventDispatcher.subscribeToEvent(EditorEvents.SceneClosed, (ev) => this.sceneClosed(ev));
  206. }
  207. /**
  208. * Called after an active scene editor change
  209. * @param {Editor.EditorEvents.ActiveSceneEditorChangeEvent} ev
  210. */
  211. activeSceneEditorChange(ev: Editor.EditorEvents.ActiveSceneEditorChangeEvent) {
  212. // run through and find any services that can handle this.
  213. this.registeredServices.forEach((service) => {
  214. try {
  215. // Verify that the service contains the appropriate methods and that it can save
  216. if (service.activeSceneEditorChanged) {
  217. service.activeSceneEditorChanged(ev);
  218. }
  219. } catch (e) {
  220. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  221. }
  222. });
  223. }
  224. /**
  225. * Called after a scene is closed
  226. * @param {Editor.EditorEvents.SceneClosedEvent} ev
  227. */
  228. sceneClosed(ev: Editor.EditorEvents.SceneClosedEvent) {
  229. // run through and find any services that can handle this.
  230. this.registeredServices.forEach((service) => {
  231. try {
  232. // Verify that the service contains the appropriate methods and that it can save
  233. if (service.editorSceneClosed) {
  234. service.editorSceneClosed(ev);
  235. }
  236. } catch (e) {
  237. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  238. }
  239. });
  240. }
  241. }
  242. /**
  243. * Registry for service extensions that are concerned about and need access to parts of the editor user interface
  244. * Note: we may want to move this out into it's own file since it has a bunch of editor dependencies
  245. */
  246. export class UIServicesProvider extends ServicesProvider<Editor.HostExtensions.UIServicesEventListener> implements Editor.HostExtensions.UIServicesProvider {
  247. constructor() {
  248. super();
  249. }
  250. private mainFrame: MainFrame = null;
  251. private modalOps: ModalOps;
  252. init(mainFrame: MainFrame, modalOps: ModalOps) {
  253. // Only set these once
  254. if (this.mainFrame == null) {
  255. this.mainFrame = mainFrame;
  256. }
  257. if (this.modalOps == null) {
  258. this.modalOps = modalOps;
  259. }
  260. }
  261. /**
  262. * Adds a new menu to the plugin menu
  263. * @param {string} id
  264. * @param {any} items
  265. * @return {Atomic.UIMenuItemSource}
  266. */
  267. createPluginMenuItemSource(id: string, items: any): Atomic.UIMenuItemSource {
  268. return this.mainFrame.menu.createPluginMenuItemSource(id, items);
  269. }
  270. /**
  271. * Removes a previously added menu from the plugin menu
  272. * @param {string} id
  273. */
  274. removePluginMenuItemSource(id: string) {
  275. this.mainFrame.menu.removePluginMenuItemSource(id);
  276. }
  277. /**
  278. * Adds a new menu to the hierarchy context menu
  279. * @param {string} id
  280. * @param {any} items
  281. * @return {Atomic.UIMenuItemSource}
  282. */
  283. createHierarchyContextMenuItemSource(id: string, items: any): Atomic.UIMenuItemSource {
  284. return this.mainFrame.hierarchyFrame.menu.createPluginItemSource(id, items);
  285. }
  286. /**
  287. * Removes a previously added menu from the hierarchy context menu
  288. * @param {string} id
  289. */
  290. removeHierarchyContextMenuItemSource(id: string) {
  291. this.mainFrame.hierarchyFrame.menu.removePluginItemSource(id);
  292. }
  293. /**
  294. * Adds a new menu to the project context menu
  295. * @param {string} id
  296. * @param {any} items
  297. * @return {Atomic.UIMenuItemSource}
  298. */
  299. createProjectContextMenuItemSource(id: string, items: any): Atomic.UIMenuItemSource {
  300. return this.mainFrame.projectframe.menu.createPluginItemSource(id, items);
  301. }
  302. /**
  303. * Removes a previously added menu from the project context menu
  304. * @param {string} id
  305. */
  306. removeProjectContextMenuItemSource(id: string) {
  307. this.mainFrame.projectframe.menu.removePluginItemSource(id);
  308. }
  309. /**
  310. * Refreshes the hierarchy frame
  311. */
  312. refreshHierarchyFrame() {
  313. this.mainFrame.hierarchyFrame.populate();
  314. }
  315. /**
  316. * Disaplays a modal window
  317. * @param {Editor.Modal.ModalWindow} window
  318. */
  319. showModalWindow(windowText: string, uifilename: string, handleWidgetEventCB: (ev: Atomic.UIWidgetEvent) => void): Editor.Modal.ExtensionWindow {
  320. return this.modalOps.showExtensionWindow(windowText, uifilename, handleWidgetEventCB);
  321. }
  322. /**
  323. * Disaplays a modal error window
  324. * @param {string} windowText
  325. * @param {string} message
  326. */
  327. showModalError(windowText: string, message: string) {
  328. return this.modalOps.showError(windowText, message);
  329. }
  330. /**
  331. * Disaplays a resource slection window
  332. * @param {string} windowText
  333. * @param {string} importerType
  334. * @param {string} resourceType
  335. * @param {function} callback
  336. * @param {any} retObject
  337. * @param {any} args
  338. */
  339. showResourceSelection(windowText: string, importerType: string, resourceType: string, callback: (retObject: any, args: any) => void, args: any = undefined) {
  340. this.modalOps.showResourceSelection(windowText, importerType, resourceType, callback);
  341. }
  342. /**
  343. * Called when a menu item has been clicked
  344. * @param {string} refId
  345. * @type {boolean} return true if handled
  346. */
  347. menuItemClicked(refid: string): boolean {
  348. // run through and find any services that can handle this.
  349. return this.registeredServices.some((service) => {
  350. try {
  351. // Verify that the service contains the appropriate methods and that it can handle it
  352. if (service.menuItemClicked) {
  353. if (service.menuItemClicked(refid)) {
  354. return true;
  355. }
  356. }
  357. } catch (e) {
  358. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  359. }
  360. });
  361. }
  362. /**
  363. * Called when a context menu item in the hierarchy pane has been clicked
  364. * @param {Atomic.Node} node
  365. * @param {string} refId
  366. * @type {boolean} return true if handled
  367. */
  368. hierarchyContextItemClicked(node: Atomic.Node, refid: string): boolean {
  369. if (!node)
  370. return false;
  371. // run through and find any services that can handle this.
  372. return this.registeredServices.some((service) => {
  373. try {
  374. // Verify that the service contains the appropriate methods and that it can handle it
  375. if (service.hierarchyContextItemClicked) {
  376. if (service.hierarchyContextItemClicked(node, refid)) {
  377. return true;
  378. }
  379. }
  380. } catch (e) {
  381. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  382. }
  383. });
  384. }
  385. /**
  386. * Called when a context menu item in the hierarchy pane has been clicked
  387. * @param {ToolCore.Asset} asset
  388. * @param {string} refId
  389. * @type {boolean} return true if handled
  390. */
  391. projectContextItemClicked(asset: ToolCore.Asset, refid: string): boolean {
  392. if (!asset)
  393. return false;
  394. // run through and find any services that can handle this.
  395. return this.registeredServices.some((service) => {
  396. try {
  397. // Verify that the service contains the appropriate methods and that it can handle it
  398. if (service.projectContextItemClicked) {
  399. if (service.projectContextItemClicked(asset, refid)) {
  400. return true;
  401. }
  402. }
  403. } catch (e) {
  404. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  405. }
  406. });
  407. }
  408. /**
  409. * Allow this service registry to subscribe to events that it is interested in
  410. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  411. */
  412. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  413. // Placeholder for when UI events published by the editor need to be listened for
  414. //eventDispatcher.subscribeToEvent(EditorEvents.SaveResourceNotification, (ev) => this.doSomeUiMessage(ev));
  415. }
  416. }