HostExtensionServices.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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 MainFramMenu = require("../ui/frames/menus/MainFrameMenu");
  25. import HierarchyFrameMenu = require("../ui/frames/menus/HierarchyFrameMenu");
  26. import ProjectFrameMenu = require("../ui/frames/menus/ProjectFrameMenu");
  27. import ModalOps = require("../ui/modal/ModalOps");
  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.LoadProjectNotification, (ev) => this.projectLoaded(ev));
  67. eventDispatcher.subscribeToEvent(EditorEvents.CloseProject, (ev) => this.projectUnloaded(ev));
  68. eventDispatcher.subscribeToEvent(EditorEvents.PlayerStartRequest, () => 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.EditorEvents.LoadProjectEvent) {
  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. /**
  120. * Registry for service extensions that are concerned about Resources
  121. */
  122. export class ResourceServicesProvider extends ServicesProvider<Editor.HostExtensions.ResourceServicesEventListener> implements Editor.HostExtensions.ResourceServicesProvider {
  123. constructor() {
  124. super();
  125. }
  126. /**
  127. * Allow this service registry to subscribe to events that it is interested in
  128. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  129. */
  130. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  131. eventDispatcher.subscribeToEvent(EditorEvents.SaveResourceNotification, (ev) => this.saveResource(ev));
  132. eventDispatcher.subscribeToEvent(EditorEvents.DeleteResourceNotification, (ev) => this.deleteResource(ev));
  133. eventDispatcher.subscribeToEvent(EditorEvents.RenameResourceNotification, (ev) => this.renameResource(ev));
  134. }
  135. /**
  136. * Called after a resource has been saved
  137. * @param {Editor.EditorEvents.SaveResourceEvent} ev
  138. */
  139. saveResource(ev: Editor.EditorEvents.SaveResourceEvent) {
  140. // run through and find any services that can handle this.
  141. this.registeredServices.forEach((service) => {
  142. try {
  143. // Verify that the service contains the appropriate methods and that it can save
  144. if (service.save) {
  145. service.save(ev);
  146. }
  147. } catch (e) {
  148. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  149. }
  150. });
  151. }
  152. /**
  153. * Called when a resource has been deleted
  154. */
  155. deleteResource(ev: Editor.EditorEvents.DeleteResourceEvent) {
  156. this.registeredServices.forEach((service) => {
  157. try {
  158. // Verify that the service contains the appropriate methods and that it can delete
  159. if (service.delete) {
  160. service.delete(ev);
  161. }
  162. } catch (e) {
  163. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  164. }
  165. });
  166. }
  167. /**
  168. * Called when a resource has been renamed
  169. * @param {Editor.EditorEvents.RenameResourceEvent} ev
  170. */
  171. renameResource(ev: Editor.EditorEvents.RenameResourceEvent) {
  172. this.registeredServices.forEach((service) => {
  173. try {
  174. // Verify that the service contains the appropriate methods and that it can handle the rename
  175. if (service.rename) {
  176. service.rename(ev);
  177. }
  178. } catch (e) {
  179. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  180. }
  181. });
  182. }
  183. }
  184. /**
  185. * Registry for service extensions that are concerned about and need access to parts of the editor user interface
  186. * Note: we may want to move this out into it's own file since it has a bunch of editor dependencies
  187. */
  188. export class UIServicesProvider extends ServicesProvider<Editor.HostExtensions.UIServicesEventListener> implements Editor.HostExtensions.UIServicesProvider {
  189. constructor() {
  190. super();
  191. }
  192. private mainFrameMenu: MainFramMenu = null;
  193. private hierarchyFrameMenu: HierarchyFrameMenu = null;
  194. private projectFrameMenu: ProjectFrameMenu = null;
  195. private modalOps: ModalOps;
  196. init(mainFrameMenu: MainFramMenu, hierarchyFrameMenu: HierarchyFrameMenu, projectFrameMenu: ProjectFrameMenu, modalOps: ModalOps) {
  197. // Only set these once
  198. if (this.mainFrameMenu == null) {
  199. this.mainFrameMenu = mainFrameMenu;
  200. }
  201. if (this.hierarchyFrameMenu == null) {
  202. this.hierarchyFrameMenu = hierarchyFrameMenu;
  203. }
  204. if (this.projectFrameMenu == null) {
  205. this.projectFrameMenu = projectFrameMenu;
  206. }
  207. if (this.modalOps == null) {
  208. this.modalOps = modalOps;
  209. }
  210. }
  211. /**
  212. * Adds a new menu to the plugin menu
  213. * @param {string} id
  214. * @param {any} items
  215. * @return {Atomic.UIMenuItemSource}
  216. */
  217. createPluginMenuItemSource(id: string, items: any): Atomic.UIMenuItemSource {
  218. return this.mainFrameMenu.createPluginMenuItemSource(id, items);
  219. }
  220. /**
  221. * Removes a previously added menu from the plugin menu
  222. * @param {string} id
  223. */
  224. removePluginMenuItemSource(id: string) {
  225. this.mainFrameMenu.removePluginMenuItemSource(id);
  226. }
  227. /**
  228. * Adds a new menu to the hierarchy context menu
  229. * @param {string} id
  230. * @param {any} items
  231. * @return {Atomic.UIMenuItemSource}
  232. */
  233. createHierarchyContextMenuItemSource(id: string, items: any): Atomic.UIMenuItemSource {
  234. return this.hierarchyFrameMenu.createPluginItemSource(id, items);
  235. }
  236. /**
  237. * Removes a previously added menu from the hierarchy context menu
  238. * @param {string} id
  239. */
  240. removeHierarchyContextMenuItemSource(id: string) {
  241. this.hierarchyFrameMenu.removePluginItemSource(id);
  242. }
  243. /**
  244. * Adds a new menu to the project context menu
  245. * @param {string} id
  246. * @param {any} items
  247. * @return {Atomic.UIMenuItemSource}
  248. */
  249. createProjectContextMenuItemSource(id: string, items: any): Atomic.UIMenuItemSource {
  250. return this.projectFrameMenu.createPluginItemSource(id, items);
  251. }
  252. /**
  253. * Removes a previously added menu from the project context menu
  254. * @param {string} id
  255. */
  256. removeProjectContextMenuItemSource(id: string) {
  257. this.projectFrameMenu.removePluginItemSource(id);
  258. }
  259. /**
  260. * Disaplays a modal window
  261. * @param {Editor.Modal.ModalWindow} window
  262. */
  263. showModalWindow(windowText: string, uifilename: string, handleWidgetEventCB: (ev: Atomic.UIWidgetEvent) => void): Editor.Modal.ExtensionWindow {
  264. return this.modalOps.showExtensionWindow(windowText, uifilename, handleWidgetEventCB);
  265. }
  266. /**
  267. * Called when a menu item has been clicked
  268. * @param {string} refId
  269. * @type {boolean} return true if handled
  270. */
  271. menuItemClicked(refid: string): boolean {
  272. // run through and find any services that can handle this.
  273. return this.registeredServices.some((service) => {
  274. try {
  275. // Verify that the service contains the appropriate methods and that it can handle it
  276. if (service.menuItemClicked) {
  277. if (service.menuItemClicked(refid)) {
  278. return true;
  279. }
  280. }
  281. } catch (e) {
  282. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  283. }
  284. });
  285. }
  286. /**
  287. * Called when a context menu item in the hierarchy pane has been clicked
  288. * @param {Atomic.Node} node
  289. * @param {string} refId
  290. * @type {boolean} return true if handled
  291. */
  292. hierarchyContextItemClicked(node: Atomic.Node, refid: string): boolean {
  293. if (!node)
  294. return false;
  295. // run through and find any services that can handle this.
  296. return this.registeredServices.some((service) => {
  297. try {
  298. // Verify that the service contains the appropriate methods and that it can handle it
  299. if (service.hierarchyContextItemClicked) {
  300. if (service.hierarchyContextItemClicked(node, refid)) {
  301. return true;
  302. }
  303. }
  304. } catch (e) {
  305. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  306. }
  307. });
  308. }
  309. /**
  310. * Called when a context menu item in the hierarchy pane has been clicked
  311. * @param {ToolCore.Asset} asset
  312. * @param {string} refId
  313. * @type {boolean} return true if handled
  314. */
  315. projectContextItemClicked(asset: ToolCore.Asset, refid: string): boolean {
  316. if (!asset)
  317. return false;
  318. // run through and find any services that can handle this.
  319. return this.registeredServices.some((service) => {
  320. try {
  321. // Verify that the service contains the appropriate methods and that it can handle it
  322. if (service.projectContextItemClicked) {
  323. if (service.projectContextItemClicked(asset, refid)) {
  324. return true;
  325. }
  326. }
  327. } catch (e) {
  328. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  329. }
  330. });
  331. }
  332. /**
  333. * Allow this service registry to subscribe to events that it is interested in
  334. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  335. */
  336. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  337. // Placeholder for when UI events published by the editor need to be listened for
  338. //eventDispatcher.subscribeToEvent(EditorEvents.SaveResourceNotification, (ev) => this.doSomeUiMessage(ev));
  339. }
  340. }