interop.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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. // This is the interop file, exposing functions that can be called by the host game engine
  23. import * as editorCommands from "./editor/editorCommands";
  24. /**
  25. * Port to attach Chrome Dev Tools to
  26. * @type {Number}
  27. */
  28. const DEBUG_PORT = 3335;
  29. /**
  30. * Display "Attach dev tools now" alert on startup if this is set to true
  31. * @type {Boolean}
  32. */
  33. const DEBUG_ALERT = false;
  34. /**
  35. * Promise version of atomic query
  36. * @param {string} messageType the message type to pass to atomicQuery. If there is no payload, this will be passed directly, otherwise it will be passed in a data object
  37. * @param {any} data optional data to send
  38. * @return {Promise}
  39. */
  40. window.atomicQueryPromise = function(messageType: string, data?: {}): Promise<{}> {
  41. return new Promise(function(resolve, reject) {
  42. let queryMessage;
  43. // if we have a data element, then we need to structure the message so that the host understands it
  44. // by adding the message to the object and then stringify-ing the whole thing
  45. if (data) {
  46. // stringify and reparse since we need to modify the data, but don't want to modify the passed in object
  47. queryMessage = JSON.parse(JSON.stringify(data));
  48. queryMessage.message = messageType;
  49. } else {
  50. queryMessage = {
  51. message: messageType
  52. };
  53. }
  54. window.atomicQuery({
  55. request: JSON.stringify(queryMessage),
  56. persistent: false,
  57. onSuccess: resolve,
  58. onFailure: (error_code, error_message) => reject({ error_code: error_code, error_message: error_message })
  59. });
  60. });
  61. };
  62. export default class HostInteropType {
  63. private static _inst: HostInteropType = null;
  64. private fileName: string = null;
  65. private fileExt: string = null;
  66. static getInstance(): HostInteropType {
  67. if (HostInteropType._inst == null) {
  68. HostInteropType._inst = new HostInteropType();
  69. }
  70. return HostInteropType._inst;
  71. }
  72. static EDITOR_SAVE_CODE = "editorSaveCode";
  73. static EDITOR_SAVE_FILE = "editorSaveFile";
  74. static EDITOR_LOAD_COMPLETE = "editorLoadComplete";
  75. static EDITOR_CHANGE = "editorChange";
  76. static EDITOR_GET_USER_PREFS = "editorGetUserPrefs";
  77. private setCodeLoaded;
  78. private editorReady = new Promise((resolve, reject) => {
  79. this.setCodeLoaded = resolve;
  80. });
  81. /**
  82. * Called from the host to notify the client what file to load
  83. * @param {string} codeUrl
  84. */
  85. loadCode(codeUrl: string) {
  86. const fileExt = codeUrl.indexOf(".") != -1 ? codeUrl.split(".").pop() : "";
  87. const filename = codeUrl.replace("atomic://", "");
  88. // Keep track of our filename
  89. this.fileName = filename;
  90. this.fileExt = fileExt;
  91. // go ahead and set the theme prior to pulling the file across
  92. editorCommands.configure(fileExt, filename);
  93. // get the code
  94. this.getResource(codeUrl).then((src: string) => {
  95. editorCommands.loadCodeIntoEditor(src, filename, fileExt);
  96. return window.atomicQueryPromise(HostInteropType.EDITOR_GET_USER_PREFS);
  97. }).then(() => {
  98. this.setCodeLoaded();
  99. }).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
  100. console.log("Error loading code: " + e.error_message);
  101. });
  102. }
  103. /**
  104. * Save the contents of the editor
  105. * @return {Promise}
  106. */
  107. saveCode(): Promise<any> {
  108. let source = editorCommands.getSourceText();
  109. return window.atomicQueryPromise(HostInteropType.EDITOR_SAVE_CODE, {
  110. payload: source
  111. }).then(() => {
  112. editorCommands.codeSaved(this.fileName, this.fileExt, source);
  113. });
  114. }
  115. /**
  116. * Save the contents of a file as filename
  117. * @param {string} filename
  118. * @param {string} fileContents
  119. * @return {Promise}
  120. */
  121. saveFile(filename: string, fileContents: string): Promise<any> {
  122. return window.atomicQueryPromise(HostInteropType.EDITOR_SAVE_FILE, {
  123. filename: filename,
  124. payload: fileContents
  125. });
  126. }
  127. /**
  128. * Call this function when the client is fully loaded up. This will notify the host that
  129. * it can start loading code
  130. */
  131. editorLoaded() {
  132. if (DEBUG_ALERT) {
  133. alert(`Attach chrome dev tools to this instance by navigating to http://localhost:${DEBUG_PORT}`);
  134. }
  135. editorCommands.editorLoaded();
  136. window.atomicQueryPromise(HostInteropType.EDITOR_LOAD_COMPLETE);
  137. }
  138. /**
  139. * Queries the host for a particular resource and returns it in a promise
  140. * @param {string} codeUrl
  141. * @return {Promise}
  142. */
  143. getResource(codeUrl: string): Promise<{}> {
  144. return new Promise(function(resolve, reject) {
  145. const xmlHttp = new XMLHttpRequest();
  146. xmlHttp.onreadystatechange = () => {
  147. if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
  148. resolve(xmlHttp.responseText);
  149. }
  150. };
  151. xmlHttp.open("GET", codeUrl, true); // true for asynchronous
  152. xmlHttp.send(null);
  153. });
  154. }
  155. /**
  156. * Returns a file resource from the resources directory
  157. * @param {string} filename name and path of file under the project directory or a fully qualified file name
  158. * @return {Promise}
  159. */
  160. getFileResource(filename: string): Promise<{}> {
  161. return this.getResource(`atomic://${filename}`);
  162. }
  163. /**
  164. * Notify the host that the contents of the editor has changed
  165. */
  166. notifyEditorChange() {
  167. window.atomicQueryPromise(HostInteropType.EDITOR_CHANGE).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
  168. console.log("Error on change: " + e.error_message);
  169. });
  170. }
  171. /**
  172. * Notify that a resource has been renamed
  173. * @param {string} path
  174. * @param {string} newPath
  175. */
  176. resourceRenamed(path: string, newPath: string) {
  177. this.fileName = newPath;
  178. editorCommands.resourceRenamed(path, newPath);
  179. }
  180. /**
  181. * Notify that a resource has been deleted
  182. * @param {string} path
  183. */
  184. resourceDeleted(path: string) {
  185. editorCommands.resourceDeleted(path);
  186. }
  187. /**
  188. * Host is notifying client that there are preferences to load and passing us JSON objects containing the prefs
  189. * of the prefs.
  190. */
  191. preferencesChanged(prefs: Editor.ClientExtensions.PreferencesChangedEventData) {
  192. editorCommands.preferencesChanged(prefs);
  193. }
  194. /**
  195. * This adds a global routine to the window object so that it can be called from the host
  196. * @param {string} routineName
  197. * @param {(} callback
  198. */
  199. addCustomHostRoutine(routineName: string, callback: () => void) {
  200. window[routineName] = callback;
  201. }
  202. /**
  203. * Sets the editor instance
  204. * @param {any} editor
  205. */
  206. setEditor(editor: any) {
  207. editorCommands.setEditor(editor);
  208. }
  209. /**
  210. * Called when a shortcut should be invoked, coming from the host editor
  211. * @param {Editor.EditorShortcutType} shortcut shortcut to be executed
  212. */
  213. invokeShortcut(shortcut: Editor.EditorShortcutType) {
  214. editorCommands.invokeShortcut(shortcut);
  215. }
  216. /**
  217. * Format the code inside the editor
  218. */
  219. formatCode() {
  220. editorCommands.formatCode();
  221. }
  222. /**
  223. * Jump to the provided line number
  224. */
  225. gotoLineNumber(lineNumber:number) {
  226. this.editorReady.then(() => {
  227. editorCommands.gotoLineNumber(lineNumber);
  228. });
  229. }
  230. /**
  231. * Jump to the provided position
  232. */
  233. gotoTokenPos(tokenPos:number) {
  234. editorCommands.gotoTokenPos(tokenPos);
  235. }
  236. }