Home Reference Source

cables_dev/cables_electron/src/electron/electron_api.js

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