interop.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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} message the query to use 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} payload optional data to send
  38. * @return {Promise}
  39. */
  40. function atomicQueryPromise(message: any): Promise<{}> {
  41. return new Promise(function(resolve, reject) {
  42. let queryMessage = message;
  43. // if message is coming in as an object then let's stringify it
  44. if (typeof (message) != "string") {
  45. queryMessage = JSON.stringify(message);
  46. }
  47. window.atomicQuery({
  48. request: queryMessage,
  49. persistent: false,
  50. onSuccess: resolve,
  51. onFailure: (error_code, error_message) => reject({ error_code: error_code, error_message: error_message })
  52. });
  53. });
  54. }
  55. export default class HostInteropType {
  56. private static _inst: HostInteropType = null;
  57. private fileName: string = null;
  58. private fileExt: string = null;
  59. static getInstance(): HostInteropType {
  60. if (HostInteropType._inst == null) {
  61. HostInteropType._inst = new HostInteropType();
  62. }
  63. return HostInteropType._inst;
  64. }
  65. static EDITOR_SAVE_CODE = "editorSaveCode";
  66. static EDITOR_SAVE_FILE = "editorSaveFile";
  67. static EDITOR_LOAD_COMPLETE = "editorLoadComplete";
  68. static EDITOR_CHANGE = "editorChange";
  69. static EDITOR_GET_USER_PREFS = "editorGetUserPrefs";
  70. constructor() {
  71. // Set up the window object so the host can call into it
  72. window.HOST_loadCode = this.loadCode.bind(this);
  73. window.HOST_saveCode = this.saveCode.bind(this);
  74. window.HOST_projectUnloaded = this.projectUnloaded.bind(this);
  75. window.HOST_resourceRenamed = this.resourceRenamed.bind(this);
  76. window.HOST_resourceDeleted = this.resourceDeleted.bind(this);
  77. window.HOST_loadPreferences = this.loadPreferences.bind(this);
  78. }
  79. /**
  80. * Called from the host to notify the client what file to load
  81. * @param {string} codeUrl
  82. */
  83. loadCode(codeUrl: string) {
  84. console.log("Load Code called for :" + codeUrl);
  85. const fileExt = codeUrl.indexOf(".") != -1 ? codeUrl.split(".").pop() : "";
  86. const filename = codeUrl.replace("atomic://", "");
  87. // Keep track of our filename
  88. this.fileName = filename;
  89. this.fileExt = fileExt;
  90. // go ahead and set the theme prior to pulling the file across
  91. editorCommands.configure(fileExt, filename);
  92. // get the code
  93. this.getResource(codeUrl).then((src: string) => {
  94. editorCommands.loadCodeIntoEditor(src, filename, fileExt);
  95. atomicQueryPromise({
  96. message: HostInteropType.EDITOR_GET_USER_PREFS
  97. });
  98. }).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
  99. console.log("Error loading code: " + e.error_message);
  100. });
  101. }
  102. /**
  103. * Save the contents of the editor
  104. * @return {Promise}
  105. */
  106. saveCode(): Promise<any> {
  107. let source = editorCommands.getSourceText();
  108. return atomicQueryPromise({
  109. message: 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. const fileExt = filename.indexOf(".") != -1 ? filename.split(".").pop() : "";
  123. return atomicQueryPromise({
  124. message: HostInteropType.EDITOR_SAVE_FILE,
  125. filename: filename,
  126. payload: fileContents
  127. }).then(() => {
  128. editorCommands.codeSaved(filename, fileExt, fileContents);
  129. });
  130. }
  131. /**
  132. * Call this function when the client is fully loaded up. This will notify the host that
  133. * it can start loading code
  134. */
  135. editorLoaded() {
  136. if (DEBUG_ALERT) {
  137. alert(`Attach chrome dev tools to this instance by navigating to http://localhost:${DEBUG_PORT}`);
  138. }
  139. atomicQueryPromise(HostInteropType.EDITOR_LOAD_COMPLETE);
  140. }
  141. /**
  142. * Queries the host for a particular resource and returns it in a promise
  143. * @param {string} codeUrl
  144. * @return {Promise}
  145. */
  146. getResource(codeUrl: string): Promise<{}> {
  147. return new Promise(function(resolve, reject) {
  148. const xmlHttp = new XMLHttpRequest();
  149. xmlHttp.onreadystatechange = () => {
  150. if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
  151. resolve(xmlHttp.responseText);
  152. }
  153. };
  154. xmlHttp.open("GET", codeUrl, true); // true for asynchronous
  155. xmlHttp.send(null);
  156. });
  157. }
  158. /**
  159. * Returns a file resource from the resources directory
  160. * @param {string} filename name and path of file under the project directory or a fully qualified file name
  161. * @return {Promise}
  162. */
  163. getFileResource(filename: string): Promise<{}> {
  164. return this.getResource(`atomic://${filename}`);
  165. }
  166. /**
  167. * Notify the host that the contents of the editor has changed
  168. */
  169. notifyEditorChange() {
  170. atomicQueryPromise(HostInteropType.EDITOR_CHANGE).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
  171. console.log("Error on change: " + e.error_message);
  172. });
  173. }
  174. /**
  175. * Notify that the project has been unloaded
  176. */
  177. projectUnloaded() {
  178. editorCommands.projectUnloaded();
  179. }
  180. /**
  181. * Notify that a resource has been renamed
  182. * @param {string} path
  183. * @param {string} newPath
  184. */
  185. resourceRenamed(path: string, newPath: string) {
  186. this.fileName = newPath;
  187. editorCommands.resourceRenamed(path, newPath);
  188. }
  189. /**
  190. * Notify that a resource has been deleted
  191. * @param {string} path
  192. */
  193. resourceDeleted(path: string) {
  194. editorCommands.resourceDeleted(path);
  195. }
  196. /**
  197. * Host is notifying client that there are preferences to load and passing us the path
  198. * of the prefs.
  199. * @param {string} prefUrl
  200. */
  201. loadPreferences(prefUrl: string) {
  202. console.log("Load preferences called for :" + prefUrl);
  203. // load prefs
  204. this.getResource(prefUrl).then((prefsJson: string) => {
  205. let prefs = JSON.parse(prefsJson);
  206. editorCommands.loadPreferences(prefs);
  207. }).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
  208. console.log("Error loading preferences: " + e.error_message);
  209. });
  210. }
  211. }
  212. HostInteropType.getInstance().editorLoaded();