HostExtensionServices.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  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 ModalOps = require("../ui/modal/ModalOps");
  26. /**
  27. * Generic registry for storing Editor Extension Services
  28. */
  29. export class ServicesProvider<T extends Editor.Extensions.ServiceEventListener> implements Editor.Extensions.ServicesProvider<T> {
  30. registeredServices: T[] = [];
  31. /**
  32. * Adds a service to the registered services list for this type of service
  33. * @param {T} service the service to register
  34. */
  35. register(service: T) {
  36. this.registeredServices.push(service);
  37. }
  38. unregister(service: T) {
  39. var index = this.registeredServices.indexOf(service, 0);
  40. if (index > -1) {
  41. this.registeredServices.splice(index, 1);
  42. }
  43. }
  44. }
  45. export interface ServiceEventSubscriber {
  46. /**
  47. * Allow this service registry to subscribe to events that it is interested in
  48. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  49. */
  50. subscribeToEvents(topLevelWindow: Atomic.UIWidget);
  51. }
  52. /**
  53. * Registry for service extensions that are concerned about project events
  54. */
  55. export class ProjectServicesProvider extends ServicesProvider<Editor.HostExtensions.ProjectServicesEventListener> implements Editor.HostExtensions.ProjectServicesProvider {
  56. constructor() {
  57. super();
  58. }
  59. /**
  60. * Allow this service registry to subscribe to events that it is interested in
  61. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  62. */
  63. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  64. eventDispatcher.subscribeToEvent(EditorEvents.LoadProjectNotification, (ev) => this.projectLoaded(ev));
  65. eventDispatcher.subscribeToEvent(EditorEvents.CloseProject, (ev) => this.projectUnloaded(ev));
  66. eventDispatcher.subscribeToEvent(EditorEvents.PlayerStartRequest, () => this.playerStarted());
  67. }
  68. /**
  69. * Called when the project is unloaded
  70. * @param {[type]} data Event info from the project unloaded event
  71. */
  72. projectUnloaded(data) {
  73. // Need to use a for loop for length down to 0 because extensions *could* delete themselves from the list on projectUnloaded
  74. for (let i = this.registeredServices.length - 1; i >= 0; i--) {
  75. let service = this.registeredServices[i];
  76. // Notify services that the project has been unloaded
  77. try {
  78. if (service.projectUnloaded) {
  79. service.projectUnloaded();
  80. }
  81. } catch (e) {
  82. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e} \n\n ${e.stack}`);
  83. }
  84. };
  85. }
  86. /**
  87. * Called when the project is loaded
  88. * @param {[type]} data Event info from the project unloaded event
  89. */
  90. projectLoaded(ev: Editor.EditorEvents.LoadProjectEvent) {
  91. // 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
  92. for (let i = 0; i < this.registeredServices.length; i++) {
  93. let service = this.registeredServices[i];
  94. try {
  95. // Notify services that the project has just been loaded
  96. if (service.projectLoaded) {
  97. service.projectLoaded(ev);
  98. }
  99. } catch (e) {
  100. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  101. }
  102. };
  103. }
  104. playerStarted() {
  105. this.registeredServices.forEach((service) => {
  106. try {
  107. // Notify services that the project has just been loaded
  108. if (service.playerStarted) {
  109. service.playerStarted();
  110. }
  111. } catch (e) {
  112. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}\n \n ${e.stack}`);
  113. }
  114. });
  115. }
  116. }
  117. /**
  118. * Registry for service extensions that are concerned about Resources
  119. */
  120. export class ResourceServicesProvider extends ServicesProvider<Editor.HostExtensions.ResourceServicesEventListener> implements Editor.HostExtensions.ResourceServicesProvider {
  121. constructor() {
  122. super();
  123. }
  124. /**
  125. * Allow this service registry to subscribe to events that it is interested in
  126. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  127. */
  128. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  129. eventDispatcher.subscribeToEvent(EditorEvents.SaveResourceNotification, (ev) => this.saveResource(ev));
  130. eventDispatcher.subscribeToEvent(EditorEvents.DeleteResourceNotification, (ev) => this.deleteResource(ev));
  131. eventDispatcher.subscribeToEvent(EditorEvents.RenameResourceNotification, (ev) => this.renameResource(ev));
  132. }
  133. /**
  134. * Called after a resource has been saved
  135. * @param {Editor.EditorEvents.SaveResourceEvent} ev
  136. */
  137. saveResource(ev: Editor.EditorEvents.SaveResourceEvent) {
  138. // run through and find any services that can handle this.
  139. this.registeredServices.forEach((service) => {
  140. try {
  141. // Verify that the service contains the appropriate methods and that it can save
  142. if (service.save) {
  143. service.save(ev);
  144. }
  145. } catch (e) {
  146. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  147. }
  148. });
  149. }
  150. /**
  151. * Called when a resource has been deleted
  152. */
  153. deleteResource(ev: Editor.EditorEvents.DeleteResourceEvent) {
  154. this.registeredServices.forEach((service) => {
  155. try {
  156. // Verify that the service contains the appropriate methods and that it can delete
  157. if (service.delete) {
  158. service.delete(ev);
  159. }
  160. } catch (e) {
  161. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  162. }
  163. });
  164. }
  165. /**
  166. * Called when a resource has been renamed
  167. * @param {Editor.EditorEvents.RenameResourceEvent} ev
  168. */
  169. renameResource(ev: Editor.EditorEvents.RenameResourceEvent) {
  170. this.registeredServices.forEach((service) => {
  171. try {
  172. // Verify that the service contains the appropriate methods and that it can handle the rename
  173. if (service.rename) {
  174. service.rename(ev);
  175. }
  176. } catch (e) {
  177. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  178. }
  179. });
  180. }
  181. }
  182. /**
  183. * Registry for service extensions that are concerned about and need access to parts of the editor user interface
  184. * Note: we may want to move this out into it's own file since it has a bunch of editor dependencies
  185. */
  186. export class UIServicesProvider extends ServicesProvider<Editor.HostExtensions.UIServicesEventListener> implements Editor.HostExtensions.UIServicesProvider {
  187. constructor() {
  188. super();
  189. }
  190. private mainFrameMenu: MainFramMenu = null;
  191. private modalOps: ModalOps;
  192. init(menu: MainFramMenu, modalOps: ModalOps) {
  193. // Only set these once
  194. if (this.mainFrameMenu == null) {
  195. this.mainFrameMenu = menu;
  196. }
  197. if (this.modalOps == null) {
  198. this.modalOps = modalOps;
  199. }
  200. }
  201. /**
  202. * Adds a new menu to the plugin menu
  203. * @param {string} id
  204. * @param {any} items
  205. * @return {Atomic.UIMenuItemSource}
  206. */
  207. createPluginMenuItemSource(id: string, items: any): Atomic.UIMenuItemSource {
  208. return this.mainFrameMenu.createPluginMenuItemSource(id, items);
  209. }
  210. /**
  211. * Removes a previously added menu from the plugin menu
  212. * @param {string} id
  213. */
  214. removePluginMenuItemSource(id: string) {
  215. this.mainFrameMenu.removePluginMenuItemSource(id);
  216. }
  217. /**
  218. * Disaplays a modal window
  219. * @param {Editor.Modal.ModalWindow} window
  220. */
  221. showModalWindow(windowText: string, uifilename: string, handleWidgetEventCB: (ev: Atomic.UIWidgetEvent) => void): Editor.Modal.ExtensionWindow {
  222. return this.modalOps.showExtensionWindow(windowText, uifilename, handleWidgetEventCB);
  223. }
  224. /**
  225. * Called when a menu item has been clicked
  226. * @param {string} refId
  227. * @type {boolean} return true if handled
  228. */
  229. menuItemClicked(refId: string): boolean {
  230. // run through and find any services that can handle this.
  231. let holdResult = false;
  232. this.registeredServices.forEach((service) => {
  233. try {
  234. // Verify that the service contains the appropriate methods and that it can handle it
  235. if (service.menuItemClicked) {
  236. if (service.menuItemClicked(refId)) {
  237. holdResult = true;
  238. }
  239. }
  240. } catch (e) {
  241. EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}:\n${e}\n\n ${e.stack}`);
  242. }
  243. });
  244. return holdResult;
  245. }
  246. /**
  247. * Allow this service registry to subscribe to events that it is interested in
  248. * @param {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
  249. */
  250. subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
  251. // Placeholder for when UI events published by the editor need to be listened for
  252. //eventDispatcher.subscribeToEvent(EditorEvents.SaveResourceNotification, (ev) => this.doSomeUiMessage(ev));
  253. }
  254. }