Home Reference Source

cables_dev/cables_electron/src/electron/electron_api.js

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