Home Reference Source

cables_dev/cables_electron/src_client/electron_editor.js

  1. import { TalkerAPI } from "cables-shared-client";
  2. import cablesElectron from "./renderer.js";
  3. /**
  4. * @name EditorParams
  5. * @type {object}
  6. * @property {{}} config config options for the ui
  7. * @property {boolean} config.isTrustedPatch does the user have write permission in the patch, always true in cablesElectron
  8. * @property {string} config.platformClass the platform class to use in the ui, allows for hooks and overrides in community vs cablesElectron
  9. * @property {string} config.urlCables url used for links to outside the sandbox on community platform
  10. * @property {string} config.urlSandbox url used for links to inside the sandbox on community platform
  11. * @property {{}} config.user current user object
  12. * @property {{}} config.usersettings current user settings
  13. * @property {{}} config.usersettings.settings current user editor preferences
  14. * @property {boolean} config.isDevEnv handle current environment as development environment?
  15. * @property {string} config.env string identifying the current environment
  16. * @property {string} config.patchId current patchid
  17. * @property {string} config.patchVersion current patchid if working on a backup version of a patch
  18. * @property {{}} config.socketcluster config for websocket connection in community platform
  19. * @property {boolean} config.remoteClient are we a remote client?
  20. * @property {{}} config.buildInfo buildinfo for the currently running version
  21. * @property {{}} config.patchConfig configuration handed over to the loaded patch
  22. * @property {boolean} config.patchConfig.allowEdit is the user allowed to edit the pacht, always true in cablesElectron
  23. * @property {string} config.patchConfig.prefixAssetPath where to look for assets that are set to relative paths in the project
  24. */
  25. /**
  26. * cables editor instance for electron cablesElectron version
  27. * handles ipc messages from and to the ui
  28. *
  29. * @param {EditorParams} params
  30. */
  31. export default class ElectronEditor
  32. {
  33. constructor(params)
  34. {
  35. this.config = params.config;
  36. const frame = document.getElementById("editorIframe");
  37. this._talker = new TalkerAPI(frame.contentWindow);
  38. this._patchId = this.config.patchId;
  39. window.addEventListener("unhandledrejection", (e) =>
  40. {
  41. this._talker.send(TalkerAPI.CMD_UI_LOG_ERROR, { "level": "error", "message": e.reason });
  42. });
  43. window.addEventListener("error", (e) =>
  44. {
  45. this._talker.send(TalkerAPI.CMD_UI_LOG_ERROR, { "level": "error", "message": e.error });
  46. });
  47. window.ipcRenderer.on("talkerMessage", (_event, data) =>
  48. {
  49. this._talker.send(data.cmd, data.data);
  50. });
  51. /**
  52. * send patch config to ui
  53. *
  54. * @name ElectronEditor#requestPatchData
  55. * @param {*} data unused
  56. * @param {function} next callback
  57. * @listens TalkerAPI#requestPatchData
  58. */
  59. this._talker.on(TalkerAPI.CMD_REQUEST_PATCH_DATA, (data, next) =>
  60. {
  61. if (next) next(this.config);
  62. });
  63. /**
  64. * notififed by ui of patch name change
  65. *
  66. * @name ElectronEditor#updatePatchName
  67. * @param {{}} data
  68. * @param {string} data.name the new patch name
  69. * @param {function} next callback
  70. * @listens TalkerAPI#updatePatchName
  71. */
  72. this._talker.on(TalkerAPI.CMD_UPDATE_PATCH_NAME, (data, next) =>
  73. {
  74. if (next) next(null, data);
  75. });
  76. /**
  77. * reload the page
  78. *
  79. * @name ElectronEditor#reload
  80. * @param {*} data unused
  81. * @param {function} next unused
  82. * @listens TalkerAPI#reload
  83. */
  84. this._talker.on(TalkerAPI.CMD_RELOAD_PATCH, (data, next) =>
  85. {
  86. document.location.reload();
  87. });
  88. /**
  89. * upload a file via the ui
  90. *
  91. * @name ElectronEditor#fileUploadStr
  92. * @param {*} data
  93. * @param {string} data.fileStr the file content as data-url
  94. * @param {string} data.filename the name of the file
  95. * @param {function} next callback
  96. * @listens TalkerAPI#fileUploadStr
  97. * @fires TalkerAPI#refreshFileManager
  98. * @fires TalkerAPI#fileUpdated
  99. */
  100. this._talker.on(TalkerAPI.CMD_UPLOAD_FILE, (data, next) =>
  101. {
  102. this.api("fileUpload", data, (err, r) =>
  103. {
  104. const error = r && r.hasOwnProperty("error") ? r.error : null;
  105. this._talker.send(TalkerAPI.CMD_UI_REFRESH_FILEMANAGER, {});
  106. this._talker.send(TalkerAPI.CMD_UI_FILE_UPDATED, { "filename": r.filename });
  107. if (error) this._talker.send(TalkerAPI.CMD_UI_LOG_ERROR, { "level": error.level, "message": error.msg || error });
  108. next(error, r);
  109. });
  110. });
  111. /**
  112. * update a file from the ui (e.g. edit a textfile)
  113. *
  114. * @name ElectronEditor#updateFile
  115. * @param {*} data
  116. * @param {string} data.content raw content of the file written to disk (e.g. ASCII)
  117. * @param {string} data.filename the name of the file
  118. * @param {function} next callback
  119. * @listens TalkerAPI#updateFile
  120. * @fires TalkerAPI#fileUpdated
  121. */
  122. this._talker.on(TalkerAPI.CMD_UPDATE_FILE, (data, next) =>
  123. {
  124. this.api("updateFile", data, (err, r) =>
  125. {
  126. const error = r && r.hasOwnProperty("error") ? r.error : null;
  127. if (error) this._talker.send(TalkerAPI.CMD_UI_LOG_ERROR, { "level": error.level, "message": error.msg || error });
  128. next(error, r);
  129. this._talker.send(TalkerAPI.CMD_UI_FILE_UPDATED, { "filename": data.fileName });
  130. });
  131. });
  132. this._talker.on(TalkerAPI.CMD_CREATE_NEW_FILE, (data, next) =>
  133. {
  134. this.api("createFile", data, (error, r) =>
  135. {
  136. if (error)
  137. {
  138. this._talker.send(TalkerAPI.CMD_UI_LOG_ERROR, { "level": error.level, "message": error.msg || error });
  139. }
  140. else
  141. {
  142. if (window.cablesElectron && window.cablesElectron.gui && r)
  143. {
  144. window.cablesElectron.gui.patchView.addAssetOpAuto(r);
  145. window.cablesElectron.gui.fileManagerEditor.editAssetTextFile("file:" + r, "text");
  146. }
  147. }
  148. next(error, r);
  149. });
  150. });
  151. this._talker.on(TalkerAPI.CMD_ADD_OP_PACKAGE, (data, next) =>
  152. {
  153. let opTargetDir = null;
  154. this.api("getProjectOpDirs", {}, (err, res) =>
  155. {
  156. let html = "";
  157. let opDirSelect = "Choose target directory:<br/><br/>";
  158. opDirSelect += "<select id=\"opTargetDir\" name=\"opTargetDir\">";
  159. for (let i = 0; i < res.data.length; i++)
  160. {
  161. const dirInfo = res.data[i];
  162. if (i === 0) opTargetDir = dirInfo.dir;
  163. opDirSelect += "<option value=\"" + dirInfo.dir + "\">" + dirInfo.dir + "</option>";
  164. }
  165. opDirSelect += "</select>";
  166. opDirSelect += "<hr/>";
  167. html += opDirSelect;
  168. html += "Enter <a href=\"https://docs.npmjs.com/cli/v10/commands/npm-install\">package.json</a> location (git, npm, thz, url, ...):";
  169. new cablesElectron.CABLES.UI.ModalDialog({
  170. "prompt": true,
  171. "title": "Install ops from package",
  172. "html": html,
  173. "promptOk": (packageLocation) =>
  174. {
  175. const loadingModal = cablesElectron.gui.startModalLoading("Installing ops...");
  176. const packageOptions = { "targetDir": opTargetDir, "package": packageLocation };
  177. this.api("addOpPackage", packageOptions, (_err, result) =>
  178. {
  179. const r = result.data;
  180. if (r)
  181. {
  182. if (r.targetDir)
  183. {
  184. loadingModal.setTask("installing to " + r.targetDir);
  185. }
  186. if (r.packages && r.packages.length > 0)
  187. {
  188. loadingModal.setTask("found ops");
  189. r.packages.forEach((p) =>
  190. {
  191. loadingModal.setTask(p);
  192. });
  193. }
  194. if (r.stdout)
  195. {
  196. loadingModal.setTask(r.stdout);
  197. }
  198. if (r.stderr)
  199. {
  200. loadingModal.setTask(r.stderr);
  201. }
  202. loadingModal.setTask("done");
  203. if (next) next(_err, r);
  204. setTimeout(() => { cablesElectron.gui.endModalLoading(); }, 3000);
  205. }
  206. });
  207. }
  208. });
  209. const dirSelect = cablesElectron.editorWindow.ele.byId("opTargetDir");
  210. if (dirSelect)
  211. {
  212. dirSelect.addEventListener("change", () =>
  213. {
  214. opTargetDir = dirSelect.value;
  215. });
  216. }
  217. });
  218. });
  219. this._talkerTopics = {};
  220. this._talkerTopics[TalkerAPI.CMD_GET_OP_INFO] = {};
  221. this._talkerTopics[TalkerAPI.CMD_SAVE_PATCH] = { "needsProjectFile": true };
  222. this._talkerTopics[TalkerAPI.CMD_GET_PATCH] = {};
  223. this._talkerTopics[TalkerAPI.CMD_CREATE_NEW_PATCH] = { };
  224. this._talkerTopics[TalkerAPI.CMD_GET_PROJECT_OPS] = {};
  225. this._talkerTopics[TalkerAPI.CMD_GET_ALL_OPDOCS] = {};
  226. this._talkerTopics[TalkerAPI.CMD_GET_OP_DOCS] = {};
  227. this._talkerTopics[TalkerAPI.CMD_SAVE_OP_CODE] = {};
  228. this._talkerTopics[TalkerAPI.CMD_GET_OP_CODE] = {};
  229. this._talkerTopics[TalkerAPI.CMD_GET_OP_ATTACHMENT] = {};
  230. this._talkerTopics[TalkerAPI.CMD_FORMAT_OP_CODE] = {};
  231. this._talkerTopics[TalkerAPI.CMD_SAVE_USER_SETTINGS] = {};
  232. this._talkerTopics[TalkerAPI.CMD_CHECK_PATCH_UPDATED] = {};
  233. this._talkerTopics[TalkerAPI.CMD_ADD_OP_LIBRARY] = {};
  234. this._talkerTopics[TalkerAPI.CMD_ADD_OP_CORELIB] = {};
  235. this._talkerTopics[TalkerAPI.CMD_ADD_OP_ATTACHMENT] = {};
  236. this._talkerTopics[TalkerAPI.CMD_REMOVE_OP_ATTACHMENT] = {};
  237. this._talkerTopics[TalkerAPI.CMD_REMOVE_OP_LIBRARY] = {};
  238. this._talkerTopics[TalkerAPI.CMD_REMOVE_OP_CORELIB] = {};
  239. this._talkerTopics[TalkerAPI.CMD_GET_CABLES_CHANGELOG] = {};
  240. this._talkerTopics[TalkerAPI.CMD_SAVE_OP_ATTACHMENT] = {};
  241. this._talkerTopics[TalkerAPI.CMD_SET_ICON_SAVED] = {};
  242. this._talkerTopics[TalkerAPI.CMD_SET_ICON_UNSAVED] = {};
  243. this._talkerTopics[TalkerAPI.CMD_SAVE_PATCH_SCREENSHOT] = { };
  244. this._talkerTopics[TalkerAPI.CMD_GET_FILE_LIST] = {};
  245. this._talkerTopics[TalkerAPI.CMD_GET_FILE_DETAILS] = {};
  246. this._talkerTopics[TalkerAPI.CMD_GET_LIBRARYFILE_DETAILS] = {};
  247. this._talkerTopics[TalkerAPI.CMD_CHECK_OP_NAME] = {};
  248. this._talkerTopics[TalkerAPI.CMD_GET_RECENT_PATCHES] = {};
  249. this._talkerTopics[TalkerAPI.CMD_CREATE_OP] = { "needsProjectFile": true };
  250. this._talkerTopics[TalkerAPI.CMD_UPDATE_OP] = {};
  251. this._talkerTopics[TalkerAPI.CMD_CLONE_OP] = { };
  252. this._talkerTopics[TalkerAPI.CMD_SAVE_OP_LAYOUT] = { };
  253. this._talkerTopics[TalkerAPI.CMD_GET_ASSET_USAGE_COUNT] = {};
  254. this._talkerTopics[TalkerAPI.CMD_SAVE_PATCH_AS] = { };
  255. this._talkerTopics[TalkerAPI.CMD_GOTO_PATCH] = {};
  256. this._talkerTopics[TalkerAPI.CMD_SET_PATCH_NAME] = { "needsProjectFile": true };
  257. this._talkerTopics[TalkerAPI.CMD_GET_COLLECTION_OPDOCS] = {};
  258. this._talkerTopics[TalkerAPI.CMD_CREATE_PATCH_BACKUP] = { "needsProjectFile": true };
  259. this._talkerTopics[TalkerAPI.CMD_ADD_OP_DEPENDENCY] = {};
  260. this._talkerTopics[TalkerAPI.CMD_REMOVE_OP_DEPENDENCY] = {};
  261. this._talkerTopics[TalkerAPI.CMD_UPLOAD_OP_DEPENDENCY] = {};
  262. this._talkerTopics[TalkerAPI.CMD_GET_PATCH_SUMMARY] = {};
  263. this._talkerTopics[TalkerAPI.CMD_SEND_ERROR_REPORT] = {};
  264. this._talkerTopics[TalkerAPI.CMD_ELECTRON_RENAME_OP] = { };
  265. this._talkerTopics[TalkerAPI.CMD_ELECTRON_DELETE_OP] = {};
  266. this._talkerTopics[TalkerAPI.CMD_ELECTRON_SET_OP_SUMMARY] = { };
  267. this._talkerTopics[TalkerAPI.CMD_ELECTRON_GET_PROJECT_OPDIRS] = {};
  268. this._talkerTopics[TalkerAPI.CMD_ELECTRON_OPEN_DIR] = {};
  269. this._talkerTopics[TalkerAPI.CMD_ELECTRON_SELECT_FILE] = {};
  270. this._talkerTopics[TalkerAPI.CMD_ELECTRON_SELECT_DIR] = {};
  271. this._talkerTopics[TalkerAPI.CMD_ELECTRON_COLLECT_ASSETS] = { "needsProjectFile": true };
  272. this._talkerTopics[TalkerAPI.CMD_ELECTRON_COLLECT_OPS] = { "needsProjectFile": true };
  273. this._talkerTopics[TalkerAPI.CMD_ELECTRON_SAVE_PROJECT_OPDIRS_ORDER] = { "needsProjectFile": true };
  274. this._talkerTopics[TalkerAPI.CMD_ELECTRON_REMOVE_PROJECT_OPDIR] = { "needsProjectFile": true };
  275. this._talkerTopics[TalkerAPI.CMD_ELECTRON_EXPORT_PATCH] = { "needsProjectFile": true };
  276. this._talkerTopics[TalkerAPI.CMD_ELECTRON_EXPORT_PATCH_BUNDLE] = { "needsProjectFile": true };
  277. this._talkerTopics[TalkerAPI.CMD_ELECTRON_ADD_PROJECT_OPDIR] = { "needsProjectFile": true };
  278. Object.keys(this._talkerTopics).forEach((talkerTopic) =>
  279. {
  280. this._talker.addEventListener(talkerTopic, (data, next) =>
  281. {
  282. const topicConfig = this._talkerTopics[talkerTopic];
  283. window.ipcRenderer.invoke("talkerMessage", talkerTopic, data, topicConfig).then((r) =>
  284. {
  285. const error = r && r.hasOwnProperty("error") ? r : null;
  286. if (error) this._talker.send(TalkerAPI.CMD_UI_LOG_ERROR, { "level": error.level, "message": error.msg || error });
  287. next(error, r);
  288. });
  289. });
  290. });
  291. }
  292. /**
  293. * make a call to a method in electron_api
  294. *
  295. * @param cmd
  296. * @param data
  297. * @param next
  298. */
  299. api(cmd, data, next)
  300. {
  301. const topicConfig = this._talkerTopics[cmd];
  302. window.ipcRenderer.invoke("talkerMessage", cmd, data, topicConfig).then((r) =>
  303. {
  304. const error = r && r.hasOwnProperty("error") ? r : null;
  305. if (error) this._talker.send(TalkerAPI.CMD_UI_LOG_ERROR, { "level": error.level, "message": error.msg || error });
  306. next(error, r);
  307. });
  308. }
  309. editor(cmd, data, next)
  310. {
  311. this._talker.send(cmd, data, next);
  312. }
  313. notify(msg)
  314. {
  315. if (!msg) return;
  316. this._talker.send(TalkerAPI.CMD_UI_NOTIFY, { "msg": msg });
  317. }
  318. }