Home Reference Source

cables_dev/cables_electron/src/electron/electron_api.js

  1. // eslint-disable-next-line import/no-extraneous-dependencies
  2. import { app, desktopCapturer, ipcMain, net, shell } from "electron";
  3. import fs from "fs";
  4. import path from "path";
  5. import mkdirp from "mkdirp";
  6. import { promisify } from "util";
  7. import jsonfile from "jsonfile";
  8. import sanitizeFileName from "sanitize-filename";
  9. import { utilProvider } from "cables-shared-api";
  10. import { createRequire } from "module";
  11. import cables from "../cables.js";
  12. import logger from "../utils/logger.js";
  13. import doc from "../utils/doc_util.js";
  14. import helper from "../utils/helper_util.js";
  15. import opsUtil from "../utils/ops_util.js";
  16. import subPatchOpUtil from "../utils/subpatchop_util.js";
  17. import settings from "./electron_settings.js";
  18. import projectsUtil from "../utils/projects_util.js";
  19. import electronApp from "./main.js";
  20. import filesUtil from "../utils/files_util.js";
  21. import libsUtil from "../utils/libs_util.js";
  22. import HtmlExportElectron from "../export/export_html_electron.js";
  23. import PatchExportElectron from "../export/export_patch_electron.js";
  24. class ElectronApi
  25. {
  26. constructor()
  27. {
  28. this._log = logger;
  29. }
  30. init()
  31. {
  32. ipcMain.handle("talkerMessage", async (event, cmd, data, topicConfig = {}) =>
  33. {
  34. try
  35. {
  36. return this.talkerMessage(cmd, data, topicConfig);
  37. }
  38. catch (e)
  39. {
  40. return this.error(e.message, e);
  41. }
  42. });
  43. ipcMain.handle("documentChanged", async (event, _cmd, data) =>
  44. {
  45. return electronApp.editorWindow.setDocumentEdited(true);
  46. });
  47. ipcMain.on("platformSettings", (event, _cmd, _data) =>
  48. {
  49. settings.data.buildInfo = settings.getBuildInfo();
  50. settings.data.openFullscreen = electronApp.openFullscreen();
  51. settings.data.maximizeRenderer = electronApp.maximizeRenderer();
  52. if (settings.data.openFullscreen || settings.data.maximizeRenderer)
  53. {
  54. this._log.info("skipping tour/hints in presentation mode");
  55. settings.data.userSettings = settings.data.userSettings || {};
  56. settings.data.userSettings.introCompleted = true;
  57. settings.data.userSettings.showTipps = false;
  58. settings.data.presentationMode = true;
  59. }
  60. event.returnValue = settings.data;
  61. });
  62. ipcMain.on("cablesConfig", (event, _cmd, _data) =>
  63. {
  64. event.returnValue = cables.getConfig();
  65. });
  66. ipcMain.on("getStartupLog", (event, _cmd, _data) =>
  67. {
  68. event.returnValue = this._log.startUpLog || [];
  69. });
  70. ipcMain.on("getOpDir", (event, data) =>
  71. {
  72. let opName = opsUtil.getOpNameById(data.opId);
  73. if (!opName) opName = data.opName;
  74. event.returnValue = opsUtil.getOpAbsolutePath(opName);
  75. });
  76. ipcMain.on("getOpModuleDir", (event, data) =>
  77. {
  78. let opName = opsUtil.getOpNameById(data.opId);
  79. if (!opName) opName = data.opName;
  80. const opDir = opsUtil.getOpAbsolutePath(opName);
  81. event.returnValue = path.join(opDir, "node_modules", data.moduleName);
  82. });
  83. ipcMain.on("getOpModuleLocation", (event, data) =>
  84. {
  85. let opName = opsUtil.getOpNameById(data.opId);
  86. if (!opName) opName = data.opName;
  87. const opDir = opsUtil.getOpAbsolutePath(opName);
  88. const moduleDir = path.join(opDir, "node_modules");
  89. const moduleRequire = createRequire(moduleDir);
  90. if (moduleRequire)
  91. {
  92. try
  93. {
  94. let location = moduleRequire.resolve(data.moduleName);
  95. if (data.asUrl) location = helper.pathToFileURL(location);
  96. event.returnValue = location;
  97. }
  98. catch (e)
  99. {
  100. this._log.error(data.moduleName + " " + moduleDir);
  101. event.returnValue = null;
  102. }
  103. }
  104. else
  105. {
  106. event.returnValue = null;
  107. }
  108. });
  109. ipcMain.on("getOpModules", (event, data) =>
  110. {
  111. let deps = [];
  112. if (!data.opName) return [];
  113. const opName = data.opName;
  114. const opDocFile = opsUtil.getOpAbsoluteJsonFilename(opName);
  115. if (fs.existsSync(opDocFile))
  116. {
  117. let opDoc = jsonfile.readFileSync(opDocFile);
  118. if (opDoc)
  119. {
  120. deps = opDoc.dependencies || [];
  121. }
  122. }
  123. event.returnValue = deps.filter((dep) => { return dep.type === "npm"; }).map((dep) => { return dep.src[0]; });
  124. });
  125. ipcMain.on("closeApplication", () =>
  126. {
  127. electronApp.quit();
  128. });
  129. ipcMain.handle("getDesktopCaptureSources", async (event, opts) =>
  130. {
  131. try
  132. {
  133. return await this._getRawDesktopSources(opts);
  134. }
  135. catch (e)
  136. {
  137. console.error("[Main] Error in getDesktopCaptureSources IPC:", e);
  138. throw e;
  139. }
  140. });
  141. }
  142. async _getRawDesktopSources(data)
  143. {
  144. const opts = data || {
  145. "types": ["window", "screen"],
  146. "thumbnailSize": { "width": 0, "height": 0 }
  147. };
  148. const sources = await desktopCapturer.getSources(opts);
  149. return sources.map((source) =>
  150. {
  151. return {
  152. "id": source.id,
  153. "name": source.name,
  154. };
  155. });
  156. }
  157. async getDesktopCaptureSources(data)
  158. {
  159. const result = await this._getRawDesktopSources(data);
  160. return this.success("OK", result, true);
  161. }
  162. async talkerMessage(cmd, data, topicConfig = {})
  163. {
  164. let response = null;
  165. if (!cmd) return this.error("UNKNOWN_COMMAND", null, "warn");
  166. if (typeof this[cmd] === "function")
  167. {
  168. if (topicConfig.needsProjectFile)
  169. {
  170. const projectFile = settings.getCurrentProjectFile();
  171. if (!projectFile)
  172. {
  173. const newName = data ? data.name : projectsUtil.getNewProjectName();
  174. const newProjectFile = await electronApp.saveProjectFileDialog(newName);
  175. if (newProjectFile)
  176. {
  177. let patchData = null;
  178. let currentProject = settings.getCurrentProject();
  179. if (cmd === "savePatch" && data)
  180. {
  181. patchData = data;
  182. }
  183. projectsUtil.writeProjectToFile(newProjectFile, currentProject, patchData);
  184. this.loadProject(newProjectFile);
  185. }
  186. else
  187. {
  188. return this.error("CANCELLED", "no patch selected", "info");
  189. }
  190. }
  191. }
  192. return this[cmd](data);
  193. }
  194. else
  195. {
  196. this._log.warn("no method for talkerMessage", cmd);
  197. }
  198. return response;
  199. }
  200. getOpInfo(data)
  201. {
  202. const opName = opsUtil.getOpNameById(data.opName) || data.opName;
  203. let warns = [];
  204. try
  205. {
  206. const currentProject = settings.getCurrentProject();
  207. if (currentProject)
  208. {
  209. let opDocs = projectsUtil.getOpDocsInProjectDirs(currentProject);
  210. opDocs = opDocs.filter((opDoc) => { return opDoc.name === opName; });
  211. opDocs.forEach((opDoc) =>
  212. {
  213. if (opDoc.overrides)
  214. {
  215. opDoc.overrides.forEach((override) =>
  216. {
  217. warns.push({
  218. "type": "project",
  219. "id": "",
  220. "text": "<a onclick=\"CABLESUILOADER.talkerAPI.send('openDir', { 'dir': '" + override + "'});\"><span class=\"icon icon-folder\"></span> this op overrides another op</a>"
  221. });
  222. });
  223. }
  224. });
  225. }
  226. warns = warns.concat(opsUtil.getOpCodeWarnings(opName));
  227. if (opsUtil.isOpNameValid(opName))
  228. {
  229. const result = { "warns": warns };
  230. result.attachmentFiles = opsUtil.getAttachmentFiles(opName);
  231. const opDocs = doc.getDocForOp(opName);
  232. let changelogEntries = [];
  233. if (opDocs && opDocs.changelog)
  234. {
  235. // copy array to not modify reference
  236. changelogEntries = changelogEntries.concat(opDocs.changelog);
  237. if (data.sort === "asc")
  238. {
  239. changelogEntries.sort((a, b) => { return a.date - b.date; });
  240. }
  241. else
  242. {
  243. changelogEntries.sort((a, b) => { return b.date - a.date; });
  244. }
  245. const numChangelogEntries = data.cl || 5;
  246. result.changelog = changelogEntries.slice(0, numChangelogEntries);
  247. }
  248. return this.success("OK", result, true);
  249. }
  250. else
  251. {
  252. const result = { "warns": [] };
  253. result.attachmentFiles = [];
  254. return this.success("OK", result, true);
  255. }
  256. }
  257. catch (e)
  258. {
  259. this._log.warn("error when getting opinfo", opName, e.message);
  260. const result = { "warns": warns };
  261. result.attachmentFiles = [];
  262. return this.success("OK", result, true);
  263. }
  264. }
  265. async savePatch(patch)
  266. {
  267. const currentProject = settings.getCurrentProject();
  268. const currentProjectFile = settings.getCurrentProjectFile();
  269. const re = {
  270. "msg": "PROJECT_SAVED"
  271. };
  272. currentProject.updated = Date.now();
  273. currentProject.updatedByUser = settings.getCurrentUser().username;
  274. projectsUtil.writeProjectToFile(currentProjectFile, currentProject, patch);
  275. this.loadProject(currentProjectFile);
  276. re.updated = currentProject.updated;
  277. re.updatedByUser = currentProject.updatedByUser;
  278. re.currentPatchDir = currentProjectFile;
  279. return this.success("OK", re, true);
  280. }
  281. async patchCreateBackup()
  282. {
  283. const re = {
  284. "msg": "BACKUP_CREATED"
  285. };
  286. const currentProject = settings.getCurrentProject();
  287. const projectFile = await electronApp.saveProjectFileDialog();
  288. if (!projectFile)
  289. {
  290. logger.info("no backup file chosen");
  291. return this.error("no backup file chosen", null, "info");
  292. }
  293. const backupProject = projectsUtil.getBackup(currentProject);
  294. fs.writeFileSync(projectFile, JSON.stringify(backupProject));
  295. return this.success("OK", re, true);
  296. }
  297. getPatch()
  298. {
  299. const patchPath = settings.getCurrentProjectFile();
  300. const currentUser = settings.getCurrentUser();
  301. let currentProject = settings.getCurrentProject();
  302. const templateProject = projectsUtil.generateNewProject(settings.getCurrentUser());
  303. if (patchPath && fs.existsSync(patchPath))
  304. {
  305. currentProject = fs.readFileSync(patchPath);
  306. currentProject = JSON.parse(currentProject.toString("utf-8"));
  307. currentProject = { ...templateProject, ...currentProject };
  308. if (!currentProject.hasOwnProperty("userList")) currentProject.userList = [currentUser];
  309. if (!currentProject.hasOwnProperty("teams")) currentProject.teams = [];
  310. }
  311. else
  312. {
  313. if (!currentProject)
  314. {
  315. this.loadProject(patchPath, templateProject);
  316. currentProject = templateProject;
  317. }
  318. }
  319. currentProject.allowEdit = true;
  320. currentProject.summary = currentProject.summary || {};
  321. currentProject.summary.title = currentProject.name;
  322. currentProject.summary.allowEdit = true;
  323. return this.success("OK", currentProject, true);
  324. }
  325. getPatchSummary()
  326. {
  327. return this.success("OK", projectsUtil.getSummary(settings.getCurrentProject()));
  328. }
  329. async newPatch()
  330. {
  331. await electronApp.openPatch(null, false);
  332. return this.success("OK", true, true);
  333. }
  334. fileUpload(data)
  335. {
  336. const target = cables.getAssetPath();
  337. if (!data.fileStr) return;
  338. if (!data.filename)
  339. {
  340. return;
  341. }
  342. let saveAs = data.filename;
  343. if (!path.isAbsolute(data.filename)) saveAs = path.join(target, path.join("/", data.filename));
  344. const buffer = Buffer.from(data.fileStr.split(",")[1], "base64");
  345. fs.writeFileSync(saveAs, buffer);
  346. return this.success("OK", { "filename": path.basename(saveAs) }, true);
  347. }
  348. async getAllProjectOps()
  349. {
  350. const currentUser = settings.getCurrentUser();
  351. const project = settings.getCurrentProject();
  352. let opDocs = [];
  353. if (!project)
  354. {
  355. return this.success("OK", opDocs, true);
  356. }
  357. let projectOps = [];
  358. let projectNamespaces = [];
  359. let usedOpIds = [];
  360. // add all ops that are used in the toplevel of the project, save them as used
  361. project.ops.forEach((projectOp) =>
  362. {
  363. projectOps.push((opsUtil.getOpNameById(projectOp.opId)));
  364. usedOpIds.push(projectOp.opId);
  365. });
  366. // add all ops in any of the project op directory
  367. const otherDirsOps = projectsUtil.getOpDocsInProjectDirs(project, true, true).map((opDoc) => { return opDoc.name; });
  368. projectOps = projectOps.concat(otherDirsOps);
  369. // now we should have all the ops that are used in the project, walk subPatchOps
  370. // recursively to get their opdocs
  371. const subPatchOps = subPatchOpUtil.getOpsUsedInSubPatches(project);
  372. subPatchOps.forEach((subPatchOp) =>
  373. {
  374. const opName = opsUtil.getOpNameById(subPatchOp.opId);
  375. const nsName = opsUtil.getCollectionNamespace(opName);
  376. projectOps.push(opName);
  377. if (opsUtil.isCollection(nsName)) projectNamespaces.push(nsName);
  378. usedOpIds.push(subPatchOp.opId);
  379. });
  380. projectOps = helper.uniqueArray(projectOps);
  381. usedOpIds = helper.uniqueArray(usedOpIds);
  382. projectNamespaces = helper.uniqueArray(projectNamespaces);
  383. const coreOpDocs = doc.getOpDocs();
  384. projectOps.forEach((opName) =>
  385. {
  386. let opDoc = doc.getDocForOp(opName, coreOpDocs);
  387. if (opDoc)
  388. {
  389. if (!opDoc.name) opDoc.name = opName;
  390. opDocs.push(opDoc);
  391. }
  392. });
  393. // get opdocs for all the collected ops
  394. opDocs = opsUtil.addOpDocsForCollections(projectNamespaces, opDocs);
  395. opDocs.forEach((opDoc) =>
  396. {
  397. if (usedOpIds.includes(opDoc.id)) opDoc.usedInProject = true;
  398. });
  399. opDocs = opsUtil.addPermissionsToOps(opDocs, currentUser, [], project);
  400. opDocs = opsUtil.addVersionInfoToOps(opDocs);
  401. opDocs = doc.makeReadable(opDocs);
  402. return this.success("OK", opDocs, true);
  403. }
  404. async getOpDocsAll()
  405. {
  406. const currentUser = settings.getCurrentUser();
  407. const currentProject = settings.getCurrentProject();
  408. let opDocs = doc.getOpDocs(true, true);
  409. opDocs = opDocs.concat(doc.getCollectionOpDocs("Ops.Extension.Standalone", currentUser));
  410. opDocs = opDocs.concat(projectsUtil.getOpDocsInProjectDirs(currentProject, true, true));
  411. const cleanDocs = doc.makeReadable(opDocs);
  412. opsUtil.addPermissionsToOps(cleanDocs, null);
  413. const extensions = await doc.getAllExtensionDocs(true, true);
  414. const libs = projectsUtil.getAvailableLibs(currentProject);
  415. const coreLibs = projectsUtil.getCoreLibs();
  416. return this.success("OK", {
  417. "opDocs": cleanDocs,
  418. "extensions": extensions,
  419. "teamNamespaces": [],
  420. "libs": libs,
  421. "coreLibs": coreLibs
  422. }, true);
  423. }
  424. async getOpDocs(data)
  425. {
  426. const opName = opsUtil.getOpNameById(data) || data;
  427. if (!opName)
  428. {
  429. return {};
  430. }
  431. const result = {};
  432. result.opDocs = [];
  433. const opDoc = doc.getDocForOp(opName);
  434. result.content = "No docs yet...";
  435. const opDocs = [];
  436. if (opDoc)
  437. {
  438. const currentProject = settings.getCurrentProject();
  439. const projectOps = projectsUtil.getOpDocsInProjectDirs(currentProject);
  440. const projectOp = projectOps.find((op) => { return op.name === opName; });
  441. if (projectOp)
  442. {
  443. opDocs.push(projectOp);
  444. }
  445. else
  446. {
  447. opDocs.push(opDoc);
  448. }
  449. if (opDoc.dependencies)
  450. {
  451. const opPackages = opsUtil.getOpNpmPackages(opName);
  452. const packageDir = opsUtil.getOpAbsolutePath(opName);
  453. result.dependenciesOutput = await electronApp.installPackages(packageDir, opPackages, opName);
  454. }
  455. result.opDocs = doc.makeReadable(opDocs);
  456. result.opDocs = opsUtil.addVersionInfoToOps(opDocs);
  457. result.opDocs = opsUtil.addPermissionsToOps(result.opDocs, null);
  458. return this.success("OK", result, true);
  459. }
  460. else
  461. {
  462. let text = "Could not find op with id " + data + " in:";
  463. const footer = "Try adding other directories via 'Manage Op Directories' after loading the patch.";
  464. const reasons = [];
  465. const errorVars = {
  466. "text": text,
  467. "footer": footer,
  468. "reasons": reasons,
  469. "hideEnvButton": true
  470. };
  471. const currentProject = settings.getCurrentProject();
  472. const projectOpDirs = projectsUtil.getProjectOpDirs(currentProject, true);
  473. projectOpDirs.forEach((projectOpDir) =>
  474. {
  475. const link = "<a onclick=\"CABLESUILOADER.talkerAPI.send('openDir', { 'dir': '" + projectOpDir + "'});\"><span class=\"icon icon-folder\"></span> " + projectOpDir + "</a>";
  476. reasons.push(link);
  477. });
  478. if (net.isOnline())
  479. {
  480. const getOpEnvironmentDocs = promisify(opsUtil.getOpEnvironmentDocs.bind(opsUtil));
  481. try
  482. {
  483. const envDocs = await getOpEnvironmentDocs(data);
  484. if (envDocs && envDocs.environments && envDocs.environments.length > 0)
  485. {
  486. const otherEnvName = envDocs.environments[0];
  487. errorVars.editorLink = "https://" + otherEnvName + "/op/" + envDocs.name;
  488. errorVars.otherEnvButton = "Visit " + otherEnvName;
  489. text = "Could not find <a href=\"" + errorVars.editorLink + "\" target=\"_blank\">" + envDocs.name + "</a> in:";
  490. envDocs.environments.forEach((envName) =>
  491. {
  492. const opLink = "https://" + envName + "/op/" + envDocs.name;
  493. reasons.push("Found <a href=\"" + opLink + "\" target=\"_blank\">" + envDocs.name + "</a> on " + envName);
  494. });
  495. }
  496. errorVars.text = text;
  497. errorVars.reasons = reasons;
  498. errorVars.hideEnvButton = false;
  499. }
  500. catch (e)
  501. { // something went wrong, no internet or something, this is informational anyhow}
  502. }
  503. }
  504. return this.error("OP_NOT_FOUND", errorVars, "warn");
  505. }
  506. }
  507. saveOpCode(data)
  508. {
  509. const opName = opsUtil.getOpNameById(data.opname);
  510. const code = data.code;
  511. let returnedCode = code;
  512. const format = opsUtil.validateAndFormatOpCode(code);
  513. if (format.error)
  514. {
  515. const {
  516. line,
  517. message
  518. } = format.message;
  519. this._log.info({
  520. line,
  521. message
  522. });
  523. return {
  524. "error": {
  525. line,
  526. message
  527. }
  528. };
  529. }
  530. const formatedCode = format.formatedCode;
  531. if (data.format || opsUtil.isCoreOp(opName))
  532. {
  533. returnedCode = formatedCode;
  534. }
  535. returnedCode = opsUtil.updateOpCode(opName, settings.getCurrentUser(), returnedCode);
  536. return this.success("OK", { "opFullCode": returnedCode }, true);
  537. }
  538. getOpCode(data)
  539. {
  540. const opName = opsUtil.getOpNameById(data.opId || data.opname);
  541. if (opsUtil.opExists(opName))
  542. {
  543. filesUtil.registerOpChangeListeners([opName]);
  544. let code = opsUtil.getOpCode(opName);
  545. return this.success("OK", {
  546. "name": opName,
  547. "id": data.opId,
  548. "code": code
  549. }, true);
  550. }
  551. else
  552. {
  553. let code = "//empty file...";
  554. return this.success("OK", {
  555. "name": opName,
  556. "id": null,
  557. "code": code
  558. }, true);
  559. }
  560. }
  561. async opAttachmentAdd(data)
  562. {
  563. const opName = opsUtil.getOpNameById(data.opname) || data.opname;
  564. const attName = data.name;
  565. const p = opsUtil.addAttachment(opName, "att_" + attName, "hello attachment");
  566. this._log.info("created attachment!", p);
  567. doc.updateOpDocs(opName);
  568. this.success("OK");
  569. }
  570. async opAttachmentDelete(data)
  571. {
  572. const opName = opsUtil.getOpNameById(data.opname) || data.opname;
  573. const attName = data.name;
  574. opsUtil.deleteAttachment(opName, attName);
  575. this.success("OK");
  576. }
  577. async opAddCoreLib(data)
  578. {
  579. const opName = opsUtil.getOpNameById(data.opname) || data.opname;
  580. const libName = sanitizeFileName(data.name);
  581. const opFilename = opsUtil.getOpJsonPath(data.opname);
  582. const libFilename = cables.getCoreLibsPath() + libName;
  583. const existsLib = fs.existsSync(libFilename + ".js");
  584. if (!existsLib)
  585. {
  586. this.error("LIB_NOT_FOUND");
  587. return;
  588. }
  589. try
  590. {
  591. const obj = jsonfile.readFileSync(opFilename);
  592. obj.coreLibs = obj.coreLibs || [];
  593. if (obj.coreLibs.indexOf(libName) === -1) obj.coreLibs.push(libName);
  594. try
  595. {
  596. jsonfile.writeFileSync(opFilename, obj, {
  597. "encoding": "utf-8",
  598. "spaces": 4
  599. });
  600. doc.updateOpDocs(opName);
  601. this.success("OK", {});
  602. }
  603. catch (writeErr)
  604. {
  605. this.error("WRITE_ERROR");
  606. }
  607. }
  608. catch (err)
  609. {
  610. this.error("UNKNOWN_ERROR");
  611. }
  612. }
  613. async opAddLib(data)
  614. {
  615. const opName = opsUtil.getOpNameById(data.opname) || data.opname;
  616. const libName = sanitizeFileName(data.name);
  617. const filename = opsUtil.getOpJsonPath(opName);
  618. const libExists = libsUtil.libExists(libName);
  619. if (!libExists)
  620. {
  621. this.error("LIB_NOT_FOUND", 400);
  622. return;
  623. }
  624. try
  625. {
  626. const obj = jsonfile.readFileSync(filename);
  627. obj.libs = obj.libs || [];
  628. if (obj.libs.indexOf(libName) === -1) obj.libs.push(libName);
  629. try
  630. {
  631. jsonfile.writeFileSync(filename, obj, {
  632. "encoding": "utf-8",
  633. "spaces": 4
  634. });
  635. doc.updateOpDocs(opName);
  636. this.success("OK");
  637. }
  638. catch (writeErr)
  639. {
  640. this.error("WRITE_ERROR", 500);
  641. }
  642. }
  643. catch (err)
  644. {
  645. this.error("UNKNOWN_ERROR", 500);
  646. }
  647. }
  648. async opRemoveLib(data)
  649. {
  650. const opName = opsUtil.getOpNameById(data.opname) || data.opname;
  651. const libName = sanitizeFileName(data.name);
  652. const filename = opsUtil.getOpJsonPath(opName);
  653. try
  654. {
  655. const obj = jsonfile.readFileSync(filename);
  656. obj.libs = obj.libs || [];
  657. if (obj.libs.includes(libName)) obj.libs = obj.libs.filter((lib) => { return lib !== libName; });
  658. try
  659. {
  660. jsonfile.writeFileSync(filename, obj, {
  661. "encoding": "utf-8",
  662. "spaces": 4
  663. });
  664. doc.updateOpDocs(opName);
  665. this.success("OK");
  666. }
  667. catch (writeErr)
  668. {
  669. this.error("WRITE_ERROR", 500);
  670. }
  671. }
  672. catch (err)
  673. {
  674. this.error("UNKNOWN_ERROR", 500);
  675. }
  676. }
  677. async opRemoveCoreLib(data)
  678. {
  679. const opName = opsUtil.getOpNameById(data.opname) || data.opname;
  680. const libName = sanitizeFileName(data.name);
  681. const opFilename = opsUtil.getOpJsonPath(opName);
  682. try
  683. {
  684. const obj = jsonfile.readFileSync(opFilename);
  685. obj.coreLibs = obj.coreLibs || [];
  686. if (obj.coreLibs.includes(libName)) obj.coreLibs = obj.coreLibs.filter((lib) => { return lib !== libName; });
  687. try
  688. {
  689. jsonfile.writeFileSync(opFilename, obj, {
  690. "encoding": "utf-8",
  691. "spaces": 4
  692. });
  693. doc.updateOpDocs(opName);
  694. this.success("OK");
  695. }
  696. catch (writeErr)
  697. {
  698. this.error("WRITE_ERROR", 500);
  699. }
  700. }
  701. catch (err)
  702. {
  703. this.error("UNKNOWN_ERROR", 500);
  704. }
  705. }
  706. async opAttachmentGet(data)
  707. {
  708. const opName = opsUtil.getOpNameById(data.opname) || data.opname;
  709. const attName = data.name;
  710. const content = opsUtil.getAttachment(opName, attName);
  711. return this.success("OK", { "content": content }, true);
  712. }
  713. async getCollectionOpDocs(data)
  714. {
  715. let opDocs = [];
  716. const collectionName = data.name;
  717. const currentUser = settings.getCurrentUser();
  718. if (collectionName)
  719. {
  720. const opNames = opsUtil.getCollectionOpNames(collectionName, true);
  721. opDocs = opsUtil.addOpDocsForCollections(opNames, opDocs);
  722. opDocs = opsUtil.addVersionInfoToOps(opDocs);
  723. opDocs = opsUtil.addPermissionsToOps(opDocs, currentUser);
  724. }
  725. return this.success("OK", { "opDocs": doc.makeReadable(opDocs) }, true);
  726. }
  727. getBuildInfo()
  728. {
  729. return this.success("OK", settings.getBuildInfo(), true);
  730. }
  731. formatOpCode(data)
  732. {
  733. const code = data.code;
  734. if (code)
  735. {
  736. // const format = opsUtil.validateAndFormatOpCode(code);
  737. // if (format.error)
  738. // {
  739. // const {
  740. // line,
  741. // message
  742. // } = format.message;
  743. // return {
  744. // "error": {
  745. // line,
  746. // message
  747. // }
  748. // };
  749. // }
  750. // else
  751. // {
  752. // return {
  753. // "opFullCode": format.formatedCode,
  754. // "success": true
  755. // };
  756. // }
  757. return this.success("OK", {
  758. "opFullCode": code
  759. }, true);
  760. }
  761. else
  762. {
  763. return this.success("OK", {
  764. "opFullCode": ""
  765. }, true);
  766. }
  767. }
  768. saveUserSettings(data)
  769. {
  770. if (data && data.settings)
  771. {
  772. settings.setUserSettings(data.settings);
  773. }
  774. }
  775. checkProjectUpdated(data)
  776. {
  777. const project = settings.getCurrentProject();
  778. if (project)
  779. {
  780. return this.success("OK", {
  781. "updated": null,
  782. "updatedByUser": project.updatedByUser,
  783. "buildInfo": project.buildInfo,
  784. "maintenance": false,
  785. "disallowSave": false
  786. }, true);
  787. }
  788. else
  789. {
  790. return this.success("OK", {
  791. "updated": "",
  792. "updatedByUser": "",
  793. "buildInfo": settings.getBuildInfo(),
  794. "maintenance": false,
  795. "disallowSave": false
  796. }, true);
  797. }
  798. }
  799. getChangelog(data)
  800. {
  801. const obj = {};
  802. obj.items = [];
  803. obj.ts = Date.now();
  804. return this.success("OK", obj, true);
  805. }
  806. opAttachmentSave(data)
  807. {
  808. let opName = data.opname;
  809. if (opsUtil.isOpId(data.opname)) opName = opsUtil.getOpNameById(data.opname);
  810. const result = opsUtil.updateAttachment(opName, data.name, data.content, false);
  811. return this.success("OK", result, true);
  812. }
  813. setIconSaved()
  814. {
  815. electronApp.setDocumentEdited(false);
  816. electronApp.updateTitle();
  817. }
  818. setIconUnsaved()
  819. {
  820. if (settings.getCurrentProject()) electronApp.setDocumentEdited(true);
  821. electronApp.updateTitle(true);
  822. }
  823. saveScreenshot(data)
  824. {
  825. const currentProject = settings.getCurrentProject();
  826. if (!currentProject || !data || !data.screenshot)
  827. {
  828. return this.error("NO_PROJECT");
  829. }
  830. currentProject.screenshot = data.screenshot;
  831. projectsUtil.writeProjectToFile(settings.getCurrentProjectFile(), currentProject);
  832. return this.success("OK", { "msg": "OK" }, true);
  833. }
  834. getFilelist(data)
  835. {
  836. let files;
  837. switch (data.source)
  838. {
  839. case "patch":
  840. files = filesUtil.getPatchFiles();
  841. break;
  842. case "lib":
  843. files = filesUtil.getLibraryFiles();
  844. break;
  845. default:
  846. files = [];
  847. break;
  848. }
  849. return this.success("OK", files, true);
  850. }
  851. getFileDetails(data)
  852. {
  853. let filePath = helper.fileURLToPath(data.filename);
  854. const fileDb = filesUtil.getFileDb(filePath, settings.getCurrentProject(), settings.getCurrentUser(), new Date().getTime());
  855. return this.success("OK", filesUtil.getFileInfo(fileDb), true);
  856. }
  857. getLibraryFileInfo(data)
  858. {
  859. const fileName = filesUtil.realSanitizeFilename(data.filename);
  860. const fileCategory = filesUtil.realSanitizeFilename(data.fileCategory);
  861. const filePath = path.join(fileCategory, fileName);
  862. const libraryPath = cables.getAssetLibraryPath();
  863. const finalPath = path.join(libraryPath, filePath);
  864. if (!fs.existsSync(finalPath))
  865. {
  866. return this.success("OK", {}, true);
  867. }
  868. else
  869. {
  870. const infoFileName = finalPath + ".fileinfo.json";
  871. let filename = "";
  872. if (fs.existsSync(infoFileName)) filename = infoFileName;
  873. if (filename === "")
  874. {
  875. return this.success("OK", {}, true);
  876. }
  877. else
  878. {
  879. const fileInfo = JSON.parse(fs.readFileSync(filename));
  880. return this.success("OK", fileInfo, true);
  881. }
  882. }
  883. }
  884. checkOpName(data)
  885. {
  886. const opDocs = doc.getOpDocs(false, false);
  887. const newName = encodeURIComponent(data.v);
  888. const sourceName = data.sourceName || null;
  889. const currentUser = settings.getCurrentUser();
  890. const project = settings.getCurrentProject();
  891. const fromRename = data.rename;
  892. const result = this._getFullRenameResponse(opDocs, newName, sourceName, currentUser, project, true, fromRename, data.opTargetDir);
  893. result.checkedName = newName;
  894. return this.success("OK", result, true);
  895. }
  896. getRecentPatches()
  897. {
  898. const recents = settings.getRecentProjects();
  899. const result = [];
  900. for (let i = 0; i < recents.length; i++)
  901. {
  902. const recentProject = recents[i];
  903. let screenShot = recentProject.screenshot;
  904. if (!screenShot)
  905. {
  906. screenShot = projectsUtil.getScreenShotFileName(recentProject, "png");
  907. if (!fs.existsSync(screenShot)) screenShot = path.join(cables.getUiDistPath(), "/img/placeholder_dark.png");
  908. }
  909. result[i] = recentProject;
  910. result[i].thumbnail = screenShot;
  911. }
  912. return this.success("OK", result.slice(0, 10), true);
  913. }
  914. async opCreate(data)
  915. {
  916. let opName = data.opname;
  917. const currentUser = settings.getCurrentUser();
  918. const opDocDefaults = {
  919. "layout": data.layout,
  920. "libs": data.libs,
  921. "coreLibs": data.coreLibs
  922. };
  923. let targetDir = data.opTargetDir;
  924. if (!targetDir)
  925. {
  926. const projectOpDirs = projectsUtil.getOpDirs(settings.getCurrentProject());
  927. if (projectOpDirs && projectOpDirs.length > 0)
  928. {
  929. targetDir = projectOpDirs[0].dir;
  930. }
  931. }
  932. if (targetDir === cables.getExtensionOpsPath())
  933. {
  934. let opNamespace = opsUtil.getNamespace(opName, true);
  935. if (opNamespace.endsWith(".")) opNamespace = opNamespace.substring(0, opNamespace.length - 1);
  936. targetDir = path.join(targetDir, opNamespace);
  937. }
  938. const result = opsUtil.createOp(opName, currentUser, data.code, opDocDefaults, data.attachments, targetDir);
  939. filesUtil.registerOpChangeListeners([opName]);
  940. projectsUtil.invalidateProjectCaches();
  941. return this.success("OK", result, true);
  942. }
  943. opUpdate(data)
  944. {
  945. let opName = data.opname;
  946. if (opsUtil.isOpId(data.opname)) opName = opsUtil.getOpNameById(data.opname);
  947. const currentUser = settings.getCurrentUser();
  948. const result = opsUtil.updateOp(currentUser, opName, data.update, { "formatCode": data.formatCode });
  949. return this.success("OK", { "data": result }, true);
  950. }
  951. opSaveLayout(data)
  952. {
  953. const layout = data.layout;
  954. const opName = opsUtil.getOpNameById(data.opname) || layout.name;
  955. return this.success("OK", opsUtil.saveLayout(opName, layout), true);
  956. }
  957. opSetSummary(data)
  958. {
  959. const opName = opsUtil.getOpNameById(data.opId) || data.name;
  960. let summary = data.summary || "";
  961. if (summary === "No Summary") summary = "";
  962. const opDocFile = opsUtil.getOpAbsoluteJsonFilename(opName);
  963. if (fs.existsSync(opDocFile))
  964. {
  965. let opDoc = jsonfile.readFileSync(opDocFile);
  966. if (opDoc)
  967. {
  968. opDoc.summary = summary;
  969. opDoc = doc.cleanOpDocData(opDoc);
  970. jsonfile.writeFileSync(opDocFile, opDoc, {
  971. "encoding": "utf-8",
  972. "spaces": 4
  973. });
  974. doc.updateOpDocs();
  975. }
  976. return this.success("OK", opDoc, true);
  977. }
  978. else
  979. {
  980. return this.error("UNKNOWN_OP", null, "error");
  981. }
  982. }
  983. opClone(data)
  984. {
  985. const newName = data.name;
  986. const oldName = opsUtil.getOpNameById(data.opname) || data.opname;
  987. const currentUser = settings.getCurrentUser();
  988. let targetDir = data.opTargetDir;
  989. if (!targetDir)
  990. {
  991. const projectOpDirs = projectsUtil.getOpDirs(settings.getCurrentProject());
  992. if (projectOpDirs && projectOpDirs.length > 0)
  993. {
  994. targetDir = projectOpDirs[0].dir;
  995. }
  996. }
  997. if (targetDir === cables.getExtensionOpsPath())
  998. {
  999. let opNamespace = opsUtil.getNamespace(newName, true);
  1000. if (opNamespace.endsWith(".")) opNamespace = opNamespace.substring(0, opNamespace.length - 1);
  1001. targetDir = path.join(targetDir, opNamespace);
  1002. }
  1003. const cloned = opsUtil.cloneOp(oldName, newName, currentUser, targetDir);
  1004. projectsUtil.invalidateProjectCaches();
  1005. return this.success("OK", cloned, true);
  1006. }
  1007. opRename(data)
  1008. {
  1009. projectsUtil.invalidateProjectCaches();
  1010. const oldId = data.opname;
  1011. const newName = data.name;
  1012. const oldName = opsUtil.getOpNameById(oldId);
  1013. const currentUser = settings.getCurrentUser();
  1014. const currentProject = settings.getCurrentProject();
  1015. let opNamespace = opsUtil.getNamespace(newName);
  1016. const opDocs = doc.getOpDocs(false, false);
  1017. const renameResults = this._getFullRenameResponse(opDocs, newName, oldName, currentUser, currentProject, opsUtil.isPrivateOp(newName), true);
  1018. if (!oldName)
  1019. {
  1020. renameResults.problems.push("No name for source op given.");
  1021. }
  1022. const result = renameResults;
  1023. result.title = "rename - " + oldName + " - " + newName;
  1024. result.objName = newName;
  1025. result.oldName = oldName;
  1026. result.opId = oldId;
  1027. result.opname = oldName;
  1028. result.opNamespace = opNamespace;
  1029. result.newopname = newName;
  1030. result.shortname = opsUtil.getOpShortName(newName);
  1031. result.oldShortName = opsUtil.getOpShortName(oldName);
  1032. const versions = opsUtil.getOpVersionNumbers(oldName, opDocs);
  1033. result.otherVersions = versions.length > 1 ? versions.filter((v) => { return v.name !== oldName; }) : [];
  1034. result.renamePossible = renameResults.problems.length === 0;
  1035. if (Object.keys(renameResults.problems).length > 0)
  1036. {
  1037. result.problems = Object.values(renameResults.problems);
  1038. return this.success("PROBLEMS", result);
  1039. }
  1040. const start = Date.now();
  1041. result.user = currentUser;
  1042. result.showresult = true;
  1043. let renameSuccess = false;
  1044. if (opsUtil.isUserOp(newName))
  1045. {
  1046. renameSuccess = opsUtil.renameToUserOp(oldName, newName, currentUser);
  1047. }
  1048. else if (opsUtil.isTeamOp(newName))
  1049. {
  1050. renameSuccess = opsUtil.renameToTeamOp(oldName, newName, currentUser);
  1051. }
  1052. else if (opsUtil.isExtensionOp(newName))
  1053. {
  1054. renameSuccess = opsUtil.renameToExtensionOp(oldName, newName, currentUser);
  1055. }
  1056. else if (opsUtil.isPatchOp(newName))
  1057. {
  1058. renameSuccess = opsUtil.renameToPatchOp(oldName, newName, currentUser, true, false);
  1059. }
  1060. else
  1061. {
  1062. renameSuccess = opsUtil.renameToCoreOp(oldName, newName, currentUser);
  1063. }
  1064. projectsUtil.invalidateProjectCaches();
  1065. if (!renameSuccess)
  1066. {
  1067. return this.error("ERROR", 500);
  1068. }
  1069. else
  1070. {
  1071. this._log.verbose("*" + currentUser.username + " finished after " + Math.round((Date.now() - start) / 1000) + " seconds ");
  1072. return this.success("OK", result);
  1073. }
  1074. }
  1075. opDelete(data)
  1076. {
  1077. const opName = opsUtil.getOpNameById(data.opId) || data.opName;
  1078. opsUtil.deleteOp(opName);
  1079. return this.success("OP_DELETED", { "opNames": [opName] });
  1080. }
  1081. async _installOpDependencies(opName)
  1082. {
  1083. const results = [];
  1084. if (opName)
  1085. {
  1086. const targetDir = opsUtil.getOpAbsolutePath(opName);
  1087. const opPackages = opsUtil.getOpNpmPackages(opName);
  1088. if (opPackages.length === 0)
  1089. {
  1090. const nodeModulesDir = path.join(targetDir, "node_modules");
  1091. if (fs.existsSync(nodeModulesDir)) fs.rmSync(nodeModulesDir, { "recursive": true });
  1092. results.push({
  1093. "stdout": "nothing to install",
  1094. "packages": []
  1095. });
  1096. return this.success("EMPTY", results, false);
  1097. }
  1098. else
  1099. {
  1100. const npmResults = await electronApp.installPackages(targetDir, opPackages, opName);
  1101. if (npmResults.stderr)
  1102. {
  1103. return this.error("NPM_ERROR", npmResults, "error");
  1104. }
  1105. else
  1106. {
  1107. return this.success("OK", npmResults);
  1108. }
  1109. }
  1110. }
  1111. else
  1112. {
  1113. results.push({
  1114. "stdout": "nothing to install",
  1115. "packages": []
  1116. });
  1117. return this.success("EMPTY", results, false);
  1118. }
  1119. }
  1120. async installProjectDependencies()
  1121. {
  1122. const currentProject = settings.getCurrentProject();
  1123. if (!currentProject)
  1124. {
  1125. return this.error("UNSAVED_PROJECT", [{
  1126. "stdout": "please save your project first",
  1127. "packages": []
  1128. }]);
  1129. }
  1130. const results = [];
  1131. let projectPackages = {};
  1132. currentProject.ops.forEach((op) =>
  1133. {
  1134. const opName = opsUtil.getOpNameById(op.opId);
  1135. if (opName)
  1136. {
  1137. const targetDir = opsUtil.getOpAbsolutePath(opName);
  1138. const opPackages = opsUtil.getOpNpmPackages(opName);
  1139. if (opPackages.length > 0)
  1140. {
  1141. if (!projectPackages.hasOwnProperty(targetDir)) projectPackages[targetDir] = [];
  1142. projectPackages[targetDir] = {
  1143. "opName": opName,
  1144. "packages": opPackages
  1145. };
  1146. }
  1147. }
  1148. });
  1149. if (Object.keys(projectPackages).length === 0)
  1150. {
  1151. results.push({
  1152. "stdout": "nothing to install",
  1153. "packages": []
  1154. });
  1155. return this.success("EMPTY", results, false);
  1156. }
  1157. else
  1158. {
  1159. const allNpmInstalls = [];
  1160. for (let targetDir in projectPackages)
  1161. {
  1162. const opData = projectPackages[targetDir];
  1163. allNpmInstalls.push(electronApp.installPackages(targetDir, opData.packages, opData.opName));
  1164. }
  1165. const npmResults = await Promise.all(allNpmInstalls);
  1166. if (npmResults.some((result) => { return result.error; }))
  1167. {
  1168. return this.error("NPM_ERROR", npmResults, "error");
  1169. }
  1170. else
  1171. {
  1172. return this.success("OK", npmResults);
  1173. }
  1174. }
  1175. }
  1176. async addOpPackage(data)
  1177. {
  1178. const currentProjectDir = settings.getCurrentProjectDir();
  1179. const targetDir = data.targetDir || currentProjectDir;
  1180. const npmResults = await electronApp.addOpPackage(targetDir, data.package);
  1181. return this.success("OK", npmResults);
  1182. }
  1183. async openDir(options = {})
  1184. {
  1185. await shell.openPath(options.dir || app.getPath("home"));
  1186. return this.success("OK", {}, true);
  1187. }
  1188. async openOpDir(options)
  1189. {
  1190. const opName = opsUtil.getOpNameById(options.opId) || options.opName;
  1191. if (!opName) return;
  1192. const opDir = opsUtil.getOpAbsoluteFileName(opName);
  1193. if (opDir)
  1194. {
  1195. shell.showItemInFolder(opDir);
  1196. return this.success("OK", {}, true);
  1197. }
  1198. }
  1199. async openProjectDir()
  1200. {
  1201. const projectFile = settings.getCurrentProjectFile();
  1202. if (projectFile)
  1203. {
  1204. shell.showItemInFolder(projectFile);
  1205. return this.success("OK", {});
  1206. }
  1207. }
  1208. async openFileManager(data)
  1209. {
  1210. let assetPath = helper.fileURLToPath(data.url, true);
  1211. if (fs.existsSync(assetPath))
  1212. {
  1213. const stats = fs.statSync(assetPath);
  1214. if (stats.isDirectory())
  1215. {
  1216. shell.openPath(assetPath);
  1217. return this.success("OK", {});
  1218. }
  1219. else
  1220. {
  1221. shell.showItemInFolder(assetPath);
  1222. return this.success("OK", {});
  1223. }
  1224. }
  1225. else
  1226. {
  1227. shell.openPath(cables.getAssetPath());
  1228. return this.success("OK", {});
  1229. }
  1230. }
  1231. async selectFile(data)
  1232. {
  1233. if (data)
  1234. {
  1235. let pickedFile = null;
  1236. let filters = [{
  1237. "name": "All Files",
  1238. "extensions": ["*"]
  1239. }];
  1240. if (data.filter)
  1241. {
  1242. if (Array.isArray(data.filter))
  1243. {
  1244. filters.unshift({
  1245. "name": "Filtered",
  1246. "extensions": data.filter
  1247. });
  1248. }
  1249. else
  1250. {
  1251. filters.unshift({
  1252. "name": data.filter.charAt(0).toUpperCase() + data.filter.slice(1),
  1253. "extensions": filesUtil.FILETYPES[data.filter] || ["*"]
  1254. });
  1255. }
  1256. }
  1257. if (data.url)
  1258. {
  1259. let assetUrl = helper.fileURLToPath(data.url, true);
  1260. pickedFile = await electronApp.pickFileDialog(assetUrl, false, filters);
  1261. }
  1262. else
  1263. {
  1264. let file = data.dir;
  1265. pickedFile = await electronApp.pickFileDialog(file, false, filters);
  1266. }
  1267. const assetPath = cables.getAssetPath();
  1268. if (pickedFile) pickedFile = pickedFile.replace(assetPath, "./");
  1269. let pickedFileUrl = pickedFile;
  1270. if (pickedFile && !pickedFile.startsWith("./")) pickedFileUrl = helper.pathToFileURL(pickedFile);
  1271. return this.success("OK", pickedFileUrl, true);
  1272. }
  1273. else
  1274. {
  1275. return this.error("NO_FILE_SELECTED", null, "info");
  1276. }
  1277. }
  1278. async selectDir(data)
  1279. {
  1280. const pickedFileUrl = await electronApp.pickDirDialog(data.dir);
  1281. return this.success("OK", pickedFileUrl, true);
  1282. }
  1283. checkNumAssetPatches()
  1284. {
  1285. return this.success("OK", {
  1286. "assets": [],
  1287. "countPatches": 0,
  1288. "countOps": 0
  1289. }, true);
  1290. }
  1291. async saveProjectAs(data)
  1292. {
  1293. const projectFile = await electronApp.saveProjectFileDialog(data.name);
  1294. if (!projectFile)
  1295. {
  1296. return this.error("no project dir chosen", null, "info");
  1297. }
  1298. let collaborators = [];
  1299. let usersReadOnly = [];
  1300. const currentUser = settings.getCurrentUser();
  1301. const origProject = settings.getCurrentProject() || projectsUtil.generateNewProject(currentUser);
  1302. origProject._id = helper.generateRandomId();
  1303. origProject.name = path.basename(projectFile);
  1304. origProject.summary = origProject.summary || {};
  1305. origProject.summary.title = origProject.name;
  1306. origProject.userId = currentUser._id;
  1307. origProject.cachedUsername = currentUser.username;
  1308. origProject.created = Date.now();
  1309. origProject.cloneOf = origProject._id;
  1310. origProject.updated = Date.now();
  1311. origProject.users = collaborators;
  1312. origProject.usersReadOnly = usersReadOnly;
  1313. origProject.visibility = "private";
  1314. origProject.shortId = helper.generateShortId(origProject._id, Date.now());
  1315. let patch = {};
  1316. if (data.dataB64) patch.dataB64 = data.dataB64;
  1317. projectsUtil.writeProjectToFile(projectFile, origProject, patch);
  1318. this.loadProject(projectFile);
  1319. electronApp.reload();
  1320. return this.success("OK", origProject, true);
  1321. }
  1322. async gotoPatch(data)
  1323. {
  1324. let project = null;
  1325. let projectFile = null;
  1326. if (data && data.id)
  1327. {
  1328. projectFile = settings.getRecentProjectFile(data.id);
  1329. if (projectFile) project = settings.getProjectFromFile(projectFile);
  1330. }
  1331. if (project && projectFile)
  1332. {
  1333. electronApp.openPatch(projectFile);
  1334. return this.success("OK", true, true);
  1335. }
  1336. else
  1337. {
  1338. const file = await electronApp.pickProjectFileDialog();
  1339. return this.success("OK", { "projectFile": file });
  1340. }
  1341. }
  1342. updateFile(data)
  1343. {
  1344. this._log.info("file edit...");
  1345. if (!data || !data.fileName)
  1346. {
  1347. return this.error("UNKNOWN_FILE");
  1348. }
  1349. const newPath = helper.fileURLToPath(data.fileName, true);
  1350. if (!fs.existsSync(newPath)) mkdirp.sync(newPath);
  1351. try
  1352. {
  1353. if (fs.existsSync(newPath))
  1354. {
  1355. this._log.info("delete old file ", newPath);
  1356. fs.unlinkSync(newPath);
  1357. }
  1358. }
  1359. catch (e)
  1360. {}
  1361. this._log.info("edit file", newPath);
  1362. fs.writeFileSync(newPath, data.content);
  1363. return this.success("OK", { "filename": newPath }, true);
  1364. }
  1365. getProjectOpDirs()
  1366. {
  1367. const currentProject = settings.getCurrentProject();
  1368. const dirInfos = projectsUtil.getOpDirs(currentProject);
  1369. const opDirs = {};
  1370. if (currentProject && currentProject.ops)
  1371. {
  1372. currentProject.ops.forEach((op) =>
  1373. {
  1374. const opName = opsUtil.getOpNameById(op.opId);
  1375. const opPath = opsUtil.getOpAbsolutePath(opName);
  1376. if (opPath)
  1377. {
  1378. if (!opDirs.hasOwnProperty(opPath)) opDirs[opPath] = 0;
  1379. opDirs[opPath]++;
  1380. }
  1381. });
  1382. }
  1383. dirInfos.forEach((dirInfo) =>
  1384. {
  1385. if (!dirInfo.hasOwnProperty("numUsedOps")) dirInfo.numUsedOps = 0;
  1386. for (const opDir in opDirs)
  1387. {
  1388. const count = opDirs[opDir];
  1389. if (opDir.startsWith(dirInfo.dir))
  1390. {
  1391. dirInfo.numUsedOps += count;
  1392. }
  1393. }
  1394. });
  1395. return this.success("OK", dirInfos);
  1396. }
  1397. async addProjectOpDir()
  1398. {
  1399. let currentProject = settings.getCurrentProject();
  1400. if (!currentProject) return this.error("Please save your project before adding op directories", null, "warn");
  1401. const opDir = await electronApp.pickOpDirDialog();
  1402. const response = [];
  1403. if (opDir && fs.existsSync(opDir))
  1404. {
  1405. try
  1406. {
  1407. const files = helper.getFileNamesRecursive(opDir, ".json");
  1408. const maxFiles = 200;
  1409. if (files && files.length > maxFiles)
  1410. {
  1411. return this.error("Directory too large, more than " + maxFiles + " possible ops found.", null, "error");
  1412. }
  1413. currentProject = projectsUtil.addOpDir(currentProject, opDir, true);
  1414. projectsUtil.writeProjectToFile(settings.getCurrentProjectFile(), currentProject);
  1415. const opDirs = projectsUtil.getProjectOpDirs(currentProject, true);
  1416. opDirs.forEach((dir) =>
  1417. {
  1418. response.push({
  1419. "path": dir,
  1420. "new": dir === opDir
  1421. });
  1422. });
  1423. }
  1424. catch (e)
  1425. {
  1426. return this.error(e.message, null, "error");
  1427. }
  1428. }
  1429. return this.success("OK", response);
  1430. }
  1431. async removeProjectOpDir(dirName)
  1432. {
  1433. let currentProject = settings.getCurrentProject();
  1434. if (!currentProject || !dirName) return this.success("OK", projectsUtil.getProjectOpDirs(currentProject, true));
  1435. dirName = path.resolve(dirName);
  1436. currentProject = projectsUtil.removeOpDir(currentProject, dirName);
  1437. projectsUtil.writeProjectToFile(settings.getCurrentProjectFile(), currentProject);
  1438. return this.success("OK", projectsUtil.getProjectOpDirs(currentProject, true));
  1439. }
  1440. saveProjectOpDirOrder(order)
  1441. {
  1442. let currentProject = settings.getCurrentProject();
  1443. if (!currentProject || !order) return this.error("NO_PROJECT", null, "warn");
  1444. currentProject = projectsUtil.reorderOpDirs(currentProject, order);
  1445. return this.success("OK", projectsUtil.getProjectOpDirs(currentProject, true));
  1446. }
  1447. setProjectName(options)
  1448. {
  1449. const oldFile = settings.getCurrentProjectFile();
  1450. let project = settings.getCurrentProject();
  1451. project.name = options.name;
  1452. const newFile = path.join(settings.getCurrentProjectDir(), projectsUtil.getProjectFileName(project));
  1453. project.name = path.basename(newFile);
  1454. project.summary = project.summary || {};
  1455. project.summary.title = project.name;
  1456. fs.renameSync(oldFile, newFile);
  1457. settings.replaceInRecentProjects(oldFile, newFile);
  1458. projectsUtil.writeProjectToFile(newFile, project);
  1459. this.loadProject(newFile);
  1460. const summary = projectsUtil.getSummary(settings.getCurrentProject());
  1461. electronApp.updateTitle();
  1462. return this.success("OK", {
  1463. "name": project.name,
  1464. "summary": summary
  1465. });
  1466. }
  1467. cycleFullscreen()
  1468. {
  1469. electronApp.cycleFullscreen();
  1470. }
  1471. collectAssets()
  1472. {
  1473. const currentProject = settings.getCurrentProject();
  1474. const assetPorts = projectsUtil.getProjectAssetPorts(currentProject, true);
  1475. const oldNew = {};
  1476. let projectAssetPath = cables.getAssetPath();
  1477. projectAssetPath = path.join(projectAssetPath, "assets");
  1478. if (!fs.existsSync(projectAssetPath)) mkdirp.sync(projectAssetPath);
  1479. assetPorts.forEach((assetPort) =>
  1480. {
  1481. const portValue = assetPort.value;
  1482. let oldFile = helper.fileURLToPath(portValue, true);
  1483. if (!helper.isLocalAssetPath(oldFile) && !oldNew.hasOwnProperty(portValue) && fs.existsSync(oldFile))
  1484. {
  1485. const baseName = path.basename(oldFile);
  1486. const newName = projectsUtil.findNewAssetFilename(projectAssetPath, baseName);
  1487. const newLocation = path.join(projectAssetPath, newName);
  1488. fs.copyFileSync(oldFile, newLocation);
  1489. // cant use path.join here since we need to keep the ./
  1490. oldNew[assetPort.value] = projectsUtil.getAssetPathUrl(currentProject._id) + newName;
  1491. }
  1492. });
  1493. return this.success("OK", oldNew);
  1494. }
  1495. collectOps()
  1496. {
  1497. const currentProject = settings.getCurrentProject();
  1498. const movedOps = {};
  1499. const allOpNames = [];
  1500. if (currentProject && currentProject.ops)
  1501. {
  1502. currentProject.ops.forEach((op) =>
  1503. {
  1504. const opName = opsUtil.getOpNameById(op.opId);
  1505. allOpNames.push(opName);
  1506. if (!movedOps.hasOwnProperty(opName))
  1507. {
  1508. const opPath = opsUtil.getOpAbsolutePath(opName);
  1509. if (!opPath.startsWith(cables.getOpsPath()))
  1510. {
  1511. const targetPath = opsUtil.getOpTargetDir(opName, true);
  1512. const newOpLocation = path.join(cables.getProjectOpsPath(true), targetPath);
  1513. if (opPath !== newOpLocation)
  1514. {
  1515. fs.cpSync(opPath, newOpLocation, { "recursive": true });
  1516. movedOps[opName] = newOpLocation;
  1517. }
  1518. }
  1519. }
  1520. });
  1521. }
  1522. filesUtil.registerOpChangeListeners(allOpNames, true);
  1523. return this.success("OK", movedOps);
  1524. }
  1525. loadProject(projectFile, newProject = null, rebuildCache = true)
  1526. {
  1527. let project = newProject;
  1528. if (projectFile)
  1529. {
  1530. project = settings.getProjectFromFile(projectFile);
  1531. if (project)
  1532. {
  1533. settings.setProject(projectFile, project);
  1534. if (rebuildCache) projectsUtil.invalidateProjectCaches();
  1535. try
  1536. {
  1537. // add ops in project dirs to lookup
  1538. projectsUtil.getOpDocsInProjectDirs(project, false, false, true);
  1539. }
  1540. catch (e)
  1541. {
  1542. const err = new Error(e.message);
  1543. err.name = "OpDirsError";
  1544. err.dir = e.dir;
  1545. throw err;
  1546. }
  1547. filesUtil.registerAssetChangeListeners(project, true);
  1548. if (project.ops)
  1549. {
  1550. const opNames = [];
  1551. project.ops.forEach((op) =>
  1552. {
  1553. const opName = opsUtil.getOpNameById(op.opId);
  1554. if (opName)
  1555. {
  1556. opNames.push(opName);
  1557. }
  1558. });
  1559. filesUtil.registerOpChangeListeners(opNames);
  1560. }
  1561. }
  1562. }
  1563. else
  1564. {
  1565. settings.setProject(null, null);
  1566. projectsUtil.getOpDocsInProjectDirs(project);
  1567. }
  1568. electronApp.updateTitle();
  1569. }
  1570. saveOpDependency(options)
  1571. {
  1572. this.opAttachmentSave(options);
  1573. }
  1574. async getOpDependency(data)
  1575. {
  1576. const opName = opsUtil.getOpNameById(data.opname) || data.opname;
  1577. const depName = data.name;
  1578. const content = opsUtil.getOpDependencyCode(opName, depName);
  1579. return this.success("OK", { "content": content });
  1580. }
  1581. async addOpDependency(options)
  1582. {
  1583. let opName = options.opName;
  1584. if (opsUtil.isOpId(opName)) opName = opsUtil.getOpNameById(opName);
  1585. if (!options.opName || !options.type) return this.error("INVALID_DATA");
  1586. const dep = this._getOpDepFromRequest({ "body": options });
  1587. const added = opsUtil.addOpDependency(opName, dep);
  1588. if (added)
  1589. {
  1590. this._log.info("added dependency!", opName, dep.src);
  1591. doc.updateOpDocs(opName);
  1592. return await this._installOpDependencies(opName);
  1593. }
  1594. else
  1595. {
  1596. return this.error("FAILED_TO_ADD_DEPENDENCY");
  1597. }
  1598. }
  1599. async removeOpDependency(options)
  1600. {
  1601. if (!options.opName || !options.type) return this.error("INVALID_DATA");
  1602. let opName = options.opName;
  1603. if (opsUtil.isOpId(opName)) opName = opsUtil.getOpNameById(opName);
  1604. const opDocFile = opsUtil.getOpAbsoluteJsonFilename(opName);
  1605. if (fs.existsSync(opDocFile))
  1606. {
  1607. let opDoc = jsonfile.readFileSync(opDocFile);
  1608. if (opDoc)
  1609. {
  1610. const newDeps = [];
  1611. const deps = opDoc.dependencies || [];
  1612. deps.forEach((dep) =>
  1613. {
  1614. if (dep.src !== options.src) newDeps.push(dep);
  1615. });
  1616. opDoc.dependencies = newDeps;
  1617. const libPath = path.join(opsUtil.getOpAbsolutePath(opName), options.src);
  1618. if (fs.existsSync(libPath)) fs.unlinkSync(libPath);
  1619. if (opDoc.dependencies) jsonfile.writeFileSync(opDocFile, opDoc, opsUtil.OPJSON_FORMAT);
  1620. doc.updateOpDocs(opName);
  1621. await this._installOpDependencies(opName);
  1622. return this.success("OK");
  1623. }
  1624. else
  1625. {
  1626. return this.error("OP_NOT_FOUND");
  1627. }
  1628. }
  1629. else
  1630. {
  1631. return this.error("OP_NOT_FOUND");
  1632. }
  1633. }
  1634. async createFile(data)
  1635. {
  1636. let file = data.name;
  1637. let pickedFileUrl = await electronApp.saveFileDialog(file);
  1638. if (pickedFileUrl)
  1639. {
  1640. fs.writeFileSync(pickedFileUrl, "");
  1641. return this.success("OK", pickedFileUrl, true);
  1642. }
  1643. else
  1644. {
  1645. return this.success("NO_DIR_CHOSEN", null, true);
  1646. }
  1647. }
  1648. async exportPatch()
  1649. {
  1650. const service = new HtmlExportElectron(utilProvider);
  1651. const exportPromise = promisify(service.doExport.bind(service));
  1652. try
  1653. {
  1654. const result = await exportPromise(settings.getCurrentProject());
  1655. return this.success("OK", result);
  1656. }
  1657. catch (e)
  1658. {
  1659. return this.error("ERROR", e);
  1660. }
  1661. }
  1662. async exportPatchBundle()
  1663. {
  1664. const service = new PatchExportElectron(utilProvider);
  1665. const exportPromise = promisify(service.doExport.bind(service));
  1666. try
  1667. {
  1668. const result = await exportPromise(settings.getCurrentProject());
  1669. return this.success("OK", result);
  1670. }
  1671. catch (e)
  1672. {
  1673. return this.error("ERROR", e);
  1674. }
  1675. }
  1676. async uploadFileToOp(params)
  1677. {
  1678. const fileName = params.filename;
  1679. const opId = params.opId;
  1680. const dataUrl = params.fileStr;
  1681. const opName = opsUtil.getOpNameById(opId);
  1682. if (fileName && opId && dataUrl && opName)
  1683. {
  1684. let data = atob(dataUrl.split(",")[1]);
  1685. let buffer = Buffer.from(data);
  1686. const newFileName = opsUtil.addOpDependencyFile(opName, fileName, buffer);
  1687. if (newFileName)
  1688. {
  1689. this._log.info("added op file!", opName, newFileName);
  1690. doc.updateOpDocs(opName);
  1691. return this.success("OK", { "filename": newFileName }, true);
  1692. }
  1693. else
  1694. {
  1695. return this.error("FAILED_TO_ADD_DEPENDENCY");
  1696. }
  1697. }
  1698. else
  1699. {
  1700. return this.error("ERROR", {
  1701. "fileName": fileName,
  1702. "opId": opId,
  1703. "opName": opName
  1704. });
  1705. }
  1706. }
  1707. errorReport(report)
  1708. {
  1709. try
  1710. {
  1711. const communityUrl = cables.getCommunityUrl();
  1712. if (cables.sendErrorReports() && communityUrl)
  1713. {
  1714. try
  1715. {
  1716. const errorReportSend = net.request({
  1717. "url": path.join(communityUrl, "/api/errorReport"),
  1718. "method": "POST"
  1719. });
  1720. delete report.url;
  1721. delete report.file;
  1722. if (report.log)
  1723. {
  1724. report.log.forEach((log) =>
  1725. {
  1726. if (log.errorStack)
  1727. {
  1728. log.errorStack.forEach((stack) =>
  1729. {
  1730. if (stack.fileName)
  1731. {
  1732. stack.fileName = path.basename(stack.fileName);
  1733. }
  1734. if (stack.source)
  1735. {
  1736. delete stack.source;
  1737. }
  1738. });
  1739. }
  1740. });
  1741. }
  1742. report.username = "electron";
  1743. errorReportSend.setHeader("Content-Type", "application/json");
  1744. errorReportSend.write(JSON.stringify(report), "utf-8");
  1745. errorReportSend.end();
  1746. }
  1747. catch (e)
  1748. {
  1749. this._log.debug("failed to send error report", e);
  1750. }
  1751. }
  1752. }
  1753. catch (e)
  1754. {
  1755. this._log.info("failed to parse error report", e);
  1756. }
  1757. this.success("OK");
  1758. }
  1759. success(msg, data = null, raw = false)
  1760. {
  1761. if (raw)
  1762. {
  1763. if (data && typeof data === "object") data.success = true;
  1764. return data;
  1765. }
  1766. else
  1767. {
  1768. return {
  1769. "success": true,
  1770. "msg": msg,
  1771. "data": data || {}
  1772. };
  1773. }
  1774. }
  1775. error(msg, data = null, level = "warn")
  1776. {
  1777. const error = {
  1778. "error": true,
  1779. "msg": msg,
  1780. "level": level
  1781. };
  1782. if (data) error.data = data;
  1783. return error;
  1784. }
  1785. _getFullRenameResponse(opDocs, newName, oldName, currentUser, project = null, ignoreVersionGap = false, fromRename = false, targetDir = false)
  1786. {
  1787. let opNamespace = opsUtil.getNamespace(newName, true);
  1788. let oldOpNamespace = opsUtil.getNamespace(oldName, true);
  1789. let availableNamespaces = [];
  1790. if (project)
  1791. {
  1792. const projectOpDocs = projectsUtil.getOpDocsInProjectDirs(project);
  1793. availableNamespaces = projectOpDocs.map((opDoc) => { return opsUtil.getNamespace(opDoc.name, true); });
  1794. }
  1795. availableNamespaces = availableNamespaces.map((availableNamespace) => { return availableNamespace.endsWith(".") ? availableNamespace : availableNamespace + "."; });
  1796. availableNamespaces = helper.uniqueArray(availableNamespaces);
  1797. availableNamespaces = availableNamespaces.sort((a, b) => { return a.localeCompare(b); });
  1798. if (opNamespace && !availableNamespaces.includes(opNamespace)) availableNamespaces.unshift(opNamespace);
  1799. if (oldOpNamespace && !availableNamespaces.includes(oldOpNamespace)) availableNamespaces.unshift(oldOpNamespace);
  1800. availableNamespaces = availableNamespaces.filter((availableNamespace) => { return availableNamespace.startsWith(opsUtil.PREFIX_OPS); });
  1801. let suggestedNamespaces = [...availableNamespaces];
  1802. if (project)
  1803. {
  1804. suggestedNamespaces.unshift(opsUtil.getPatchOpsNamespaceForProject(project));
  1805. }
  1806. suggestedNamespaces.unshift("Your namespaces..");
  1807. suggestedNamespaces = suggestedNamespaces.map((suggestedNamespace) => { return opsUtil.getNamespace(suggestedNamespace, true); });
  1808. suggestedNamespaces = helper.uniqueArray(suggestedNamespaces);
  1809. const result = {
  1810. "namespaces": suggestedNamespaces,
  1811. "problems": [],
  1812. "consequences": [],
  1813. "action": fromRename ? "Rename" : "Create"
  1814. };
  1815. if (!newName)
  1816. {
  1817. result.problems.push("No name for new op given.");
  1818. return result;
  1819. }
  1820. if (fromRename) targetDir = opsUtil.getOpSourceDir(oldName);
  1821. const problems = opsUtil.getOpRenameProblems(newName, oldName, currentUser, [], null, null, [], true, targetDir);
  1822. const hints = {};
  1823. const consequences = opsUtil.getOpRenameConsequences(newName, oldName, targetDir);
  1824. const newNamespace = opsUtil.getNamespace(newName);
  1825. const existingNamespace = opsUtil.namespaceExists(newNamespace, opDocs);
  1826. if (!existingNamespace && !opsUtil.isPatchOp(newNamespace))
  1827. {
  1828. hints.new_namespace = "New op will create a new namespace " + newNamespace;
  1829. }
  1830. let newOpDocs = opDocs;
  1831. if (!opsUtil.isCoreOp(newName)) newOpDocs = doc.getCollectionOpDocs(newName, currentUser);
  1832. const nextOpName = opsUtil.getNextVersionOpName(newName, newOpDocs);
  1833. const nextShort = opsUtil.getOpShortName(nextOpName);
  1834. let nextVersion = null;
  1835. let suggestVersion = false;
  1836. if (problems.target_exists)
  1837. {
  1838. suggestVersion = true;
  1839. }
  1840. if (!ignoreVersionGap)
  1841. {
  1842. const wantedVersion = opsUtil.getVersionFromOpName(newName);
  1843. const currentHighest = opsUtil.getHighestVersionNumber(newName, newOpDocs);
  1844. const versionTolerance = currentHighest ? 1 : 2;
  1845. if ((wantedVersion - versionTolerance) > currentHighest)
  1846. {
  1847. hints.version_gap = "Gap in version numbers!";
  1848. suggestVersion = true;
  1849. }
  1850. }
  1851. if (problems.illegal_ops)
  1852. {
  1853. suggestVersion = false;
  1854. }
  1855. if (suggestVersion)
  1856. {
  1857. const text = "Try creating a new version <a class='button-small versionSuggestion' data-short-name='" + nextShort + "' data-next-name='" + nextOpName + "'>" + nextOpName + "</a>";
  1858. nextVersion = {
  1859. "fullName": nextOpName,
  1860. "namespace": opsUtil.getNamespace(nextOpName),
  1861. "shortName": nextShort
  1862. };
  1863. if (problems.target_exists)
  1864. {
  1865. problems.version_suggestion = text;
  1866. }
  1867. else
  1868. {
  1869. hints.version_suggestion = text;
  1870. }
  1871. }
  1872. result.problems = Object.values(problems);
  1873. result.hints = Object.values(hints);
  1874. result.consequences = Object.values(consequences);
  1875. if (nextVersion) result.nextVersion = nextVersion;
  1876. return result;
  1877. }
  1878. _getOpDepFromRequest(req)
  1879. {
  1880. const validTypes = ["commonjs", "module", "npm", "op", "lib", "corelib"];
  1881. const data = req.body;
  1882. const dep = {};
  1883. if (data.src) dep.src = data.src.trim();
  1884. if (data.type && validTypes.includes(data.type.trim())) dep.type = data.type.trim();
  1885. if (data.export && dep.type === "module") dep.export = data.export.trim();
  1886. return dep;
  1887. }
  1888. }
  1889. export default new ElectronApi();