Home Reference Source

cables_dev/cables_electron/src_client/cables_electron.js

  1. import { Logger } from "cables-shared-client";
  2. import ElectronEditor from "./electron_editor.js";
  3. import electronCommands from "./cmd_electron.js";
  4. /**
  5. * frontend class for cablesElectron
  6. * initializes the ui, starts the editor and adds functions custom to this platform
  7. */
  8. export default class CablesElectron
  9. {
  10. constructor()
  11. {
  12. this._logger = new Logger("electron");
  13. this._electron = window.nodeRequire("electron");
  14. this._importSync = window.nodeRequire("import-sync");
  15. window.ipcRenderer = this._electron.ipcRenderer; // needed to have ipcRenderer in electron_editor.js
  16. this._settings = this._electron.ipcRenderer.sendSync("platformSettings") || {};
  17. this._usersettings = this._settings.userSettings;
  18. delete this._settings.userSettings;
  19. this._config = this._electron.ipcRenderer.sendSync("cablesConfig") || {};
  20. this.editorIframe = null;
  21. this._startUpLogItems = this._electron.ipcRenderer.sendSync("getStartupLog") || [];
  22. if (!this._config.isPackaged) window.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
  23. this._loadedModules = {};
  24. }
  25. /**
  26. * the `gui` object of the current editor, if initialized
  27. *
  28. * @type {Gui|null}
  29. */
  30. get gui()
  31. {
  32. return this.editorWindow ? this.editorWindow.gui : null;
  33. }
  34. /**
  35. * the current editor window, if initialized
  36. *
  37. * @type {{}|null}
  38. */
  39. get editorWindow()
  40. {
  41. return this.editorIframe.contentWindow;
  42. }
  43. /**
  44. * the CABLES core instance of the current editor window, if initialized
  45. *
  46. * @type {{}|null}
  47. */
  48. get CABLES()
  49. {
  50. return this.editorWindow ? this.editorWindow.CABLES : null;
  51. }
  52. /**
  53. *
  54. * @type {Logger}
  55. */
  56. get _log()
  57. {
  58. CABLES.UI = this.CABLES.UI;
  59. return this._logger;
  60. }
  61. /**
  62. * initialize the editor, wait for core and ui to be ready, add
  63. * custom functionality
  64. */
  65. init()
  66. {
  67. this.editorIframe = document.getElementById("editorIframe");
  68. let src = this._config.uiIndexHtml + window.location.search;
  69. if (window.location.hash)
  70. {
  71. src += window.location.hash;
  72. }
  73. this.editorIframe.src = src;
  74. this.editorIframe.onload = () =>
  75. {
  76. if (this.editorWindow)
  77. {
  78. const waitForUi = this.editorWindow.waitForAce;
  79. this.editorWindow.waitForAce = () =>
  80. {
  81. this._logStartup("loading", this._settings.patchFile);
  82. this._incrementStartup();
  83. this._logStartup("checking/installing op dependencies...");
  84. this._electron.ipcRenderer.invoke("talkerMessage", "installProjectDependencies").then((npmResult) =>
  85. {
  86. this.editorWindow.CABLESUILOADER.cfg.patchConfig.onError = (...args) =>
  87. {
  88. // npm runtime error...
  89. if (args && args[0] === "core_patch" && args[2] && args[2].message && args[2].message.includes("was compiled against a different Node.js version"))
  90. {
  91. const dirParts = args[2].message.split("/");
  92. const opNameIndex = dirParts.findIndex((part) => { return part.startsWith("Ops."); });
  93. const opName = dirParts[opNameIndex];
  94. const packageName = dirParts[opNameIndex + 2];
  95. const onClick = "CABLES.CMD.ELECTRON.openOpDir('', '" + opName + "');";
  96. const msg = "try running this <a onclick=\"" + onClick + "\" > in the op dir</a>:";
  97. this._log.error(msg);
  98. this._log.error("`npm --prefix ./ install " + packageName + "`");
  99. this._log.error("`npx \"@electron/rebuild\" -v " + process.versions.electron);
  100. }
  101. };
  102. waitForUi();
  103. if (this.gui)
  104. {
  105. this.gui.on("uiloaded", () =>
  106. {
  107. if (this.editor && this.editor.config && !this.editor.config.patchFile) this.gui.setStateUnsaved();
  108. });
  109. const corePatch = this.gui.corePatch();
  110. corePatch.on("onOpAdd", (op) =>
  111. {
  112. this._electron.ipcRenderer.invoke("documentChanged");
  113. });
  114. }
  115. if (npmResult.error && npmResult.data && npmResult.msg !== "UNSAVED_PROJECT")
  116. {
  117. npmResult.data.forEach((msg) =>
  118. {
  119. const opName = msg.opName ? " for " + msg.opName : "";
  120. this._log.error("failed dependency" + opName + ": " + msg.stderr);
  121. });
  122. }
  123. else if (npmResult.msg !== "EMPTY" && npmResult.msg !== "UNSAVED_PROJECT")
  124. {
  125. npmResult.data.forEach((result) =>
  126. {
  127. const npmText = result.stderr || result.stdout;
  128. this._logStartup(result.opName + ": " + npmText);
  129. });
  130. }
  131. if (this.gui)
  132. {
  133. this.gui.on("uiloaded", () =>
  134. {
  135. if (this._settings.maximizeRenderer) this.gui.toggleMaximizeCanvas();
  136. });
  137. }
  138. });
  139. };
  140. if (this._settings.uiLoadStart) this.editorWindow.CABLESUILOADER.uiLoadStart -= this._settings.uiLoadStart;
  141. this._startUpLogItems.forEach((logEntry) =>
  142. {
  143. this._logStartup(logEntry.title);
  144. });
  145. if (this.editorWindow.loadjs)
  146. {
  147. this.editorWindow.loadjs.ready("cablesuinew", this._uiReady.bind(this));
  148. }
  149. }
  150. };
  151. window.addEventListener("message", (event) =>
  152. {
  153. if (event.data && event.data.type === "hashchange")
  154. {
  155. window.location.hash = event.data.data;
  156. }
  157. }, false);
  158. window.addEventListener("hashchange", () =>
  159. {
  160. if (this.editorWindow)
  161. {
  162. this.editorWindow.postMessage({ "type": "hashchange", "data": window.location.hash }, "*");
  163. }
  164. }, false);
  165. this.editor = new ElectronEditor({
  166. "config": {
  167. ...this._settings,
  168. "isTrustedPatch": true,
  169. "platformClass": "PlatformElectron",
  170. "urlCables": "cables://",
  171. "urlSandbox": "cables://",
  172. "communityUrl": this._config.communityUrl,
  173. "user": this._settings.currentUser,
  174. "usersettings": { "settings": this._usersettings },
  175. "isDevEnv": !this._config.isPackaged,
  176. "env": this._config.env,
  177. "patchId": this._settings.patchId,
  178. "patchVersion": "",
  179. "socketcluster": {},
  180. "remoteClient": false,
  181. "buildInfo": this._settings.buildInfo,
  182. "patchConfig": {
  183. "allowEdit": true,
  184. "prefixAssetPath": this._settings.currentPatchDir,
  185. "assetPath": this._settings.paths.assetPath,
  186. "paths": this._settings.paths
  187. },
  188. }
  189. });
  190. }
  191. openOpDirsTab()
  192. {
  193. if (this.CABLES) this.CABLES.platform.openOpDirsTab();
  194. }
  195. _uiReady()
  196. {
  197. if (this.CABLES)
  198. {
  199. if (this.CABLES.Op)
  200. {
  201. const cablesElectron = this;
  202. this.CABLES.Op.prototype.require = function (moduleName)
  203. {
  204. return cablesElectron._opRequire(moduleName, this, cablesElectron);
  205. };
  206. }
  207. this.CABLES.UI.DEFAULTOPNAMES.defaultOpFallback = this.CABLES.UI.DEFAULTOPNAMES.HttpRequest;
  208. this.CABLES.CMD.ELECTRON = electronCommands.functions;
  209. this.CABLES.CMD.commands = this.CABLES.CMD.commands.concat(electronCommands.commands);
  210. Object.assign(this.CABLES.CMD.UI, electronCommands.functionOverrides.UI);
  211. Object.assign(this.CABLES.CMD.PATCH, electronCommands.functionOverrides.PATCH);
  212. Object.assign(this.CABLES.CMD.RENDERER, electronCommands.functionOverrides.RENDERER);
  213. const commandOverrides = electronCommands.commandOverrides;
  214. this.CABLES.CMD.commands.forEach((command) =>
  215. {
  216. const commandOverride = commandOverrides.find((override) => { return override.cmd === command.cmd; });
  217. if (commandOverride)
  218. {
  219. Object.assign(command, commandOverride);
  220. }
  221. });
  222. }
  223. }
  224. _opRequire(moduleName, op, thisClass)
  225. {
  226. if (op) op.setUiError("oprequire", null);
  227. if (moduleName === "electron") return thisClass._electron;
  228. if (this._loadedModules[moduleName]) return this._loadedModules[moduleName];
  229. let modulePath = null;
  230. let moduleFile = null;
  231. try
  232. {
  233. // load module by directory name
  234. modulePath = window.ipcRenderer.sendSync("getOpModuleDir", { "opName": op.objName, "opId": op.opId, "moduleName": moduleName });
  235. this._loadedModules[moduleName] = window.nodeRequire(modulePath);
  236. return this._loadedModules[moduleName];
  237. }
  238. catch (ePath)
  239. {
  240. try
  241. {
  242. // load module by resolved filename from package.json
  243. moduleFile = window.ipcRenderer.sendSync("getOpModuleLocation", { "opName": op.objName, "opId": op.opId, "moduleName": moduleName });
  244. this._loadedModules[moduleName] = window.nodeRequire(moduleFile);
  245. return this._loadedModules[moduleName];
  246. }
  247. catch (eFile)
  248. {
  249. try
  250. {
  251. // load module by module name
  252. this._loadedModules[moduleName] = window.nodeRequire(moduleName);
  253. return this._loadedModules[moduleName];
  254. }
  255. catch (eName)
  256. {
  257. try
  258. {
  259. moduleFile = window.ipcRenderer.sendSync("getOpModuleLocation", { "opName": op.objName || op.name, "opId": op.opId, "moduleName": moduleName, });
  260. this._loadedModules[moduleName] = this._importSync(moduleFile);
  261. return this._loadedModules[moduleName];
  262. }
  263. catch (eImport)
  264. {
  265. let errorMessage = "failed to load node module: " + moduleName + "\n\n";
  266. if (op) op.setUiError("oprequire", errorMessage);
  267. this._log.error(errorMessage, modulePath, moduleFile);
  268. return { };
  269. }
  270. }
  271. }
  272. }
  273. }
  274. _logStartup(title)
  275. {
  276. if (this.editorWindow && this.editorWindow.logStartup) this.editorWindow.logStartup(title);
  277. }
  278. _incrementStartup()
  279. {
  280. if (this.editorWindow && this.editorWindow.logStartup) this.editorWindow.incrementStartup();
  281. }
  282. }