app.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. "use strict";
  2. /**
  3. * Constants for application
  4. *
  5. * manifestList is the url that contains all possible manifests
  6. * pollTime is used to check for changes in the serial ports
  7. *
  8. * @type {{manifestList: string, pollTime: number}}
  9. */
  10. const CONSTANTS = {
  11. manifestList: "http://flasher.thingssdk.com/v1/manifest-list.json",
  12. pollTime: 1000
  13. };
  14. var last_notification = "";
  15. /************************
  16. * Backend dependencies.
  17. * Note: Paths are relative to index.html not app.js
  18. ************************/
  19. const remote = require("remote");
  20. const SerialScanner = require("../back-end/serial_scanner");
  21. const PortSelect = require("./js/port_select");
  22. const prepareBinaries = require("../back-end/prepare_binaries");
  23. const log = require("../back-end/logger");
  24. const RomComm = require("../back-end/rom_comm");
  25. const serialScanner = new SerialScanner();
  26. /************************
  27. * UI Elements
  28. ************************/
  29. const flashButton = $("flash-button");
  30. const appStatus = $("status");
  31. const portsSelect = new PortSelect($("ports"));
  32. const manifestsSelect = $("manifests");
  33. const svg = $("Layer_1");
  34. const appWrapper = $("app");
  35. const logoWrapper = $("logo");
  36. const form = $("form");
  37. /************************
  38. * Utility Functions
  39. ************************/
  40. /**
  41. * Simple helper returns HTML element from an element's id
  42. *
  43. * @param id a string of the id of an HTML element
  44. * @returns {Element}
  45. */
  46. function $(id) { return document.getElementById(id); }
  47. /**
  48. * Processes the response from an HTTP fetch and returns the JSON promise.
  49. *
  50. * @param response from a fetch call
  51. * @returns {Promise}
  52. */
  53. function processJSON(response) {
  54. return response.json();
  55. }
  56. /************************
  57. * Handle UI
  58. ************************/
  59. flashButton.addEventListener("click", (event) => {
  60. disableInputs();
  61. prepareUIForFlashing(()=>{
  62. fetch(manifestsSelect.value)
  63. .then(processJSON)
  64. .then(flashWithManifest);
  65. });
  66. });
  67. /************************
  68. * Manage serial port events
  69. ************************/
  70. serialScanner.on("ports", (ports) => {
  71. portsSelect.addAll(ports);
  72. readyToFlash();
  73. });
  74. serialScanner.on("deviceAdded", (port) => {
  75. portsSelect.add(port);
  76. readyToFlash();
  77. new Notification(`Added: ${port}!`);
  78. });
  79. serialScanner.on("deviceRemoved", (port) => {
  80. portsSelect.remove(port);
  81. new Notification(`Removed: ${port}!`);
  82. });
  83. serialScanner.on("error", onError);
  84. /**
  85. * Updates UI to say it's ready
  86. */
  87. function readyToFlash() {
  88. form.style.opacity = 1;
  89. appStatus.textContent = "Ready";
  90. enableInputs();
  91. }
  92. /**
  93. * Enabled the serial port SELECT and flash BUTTON elements.
  94. */
  95. function enableInputs() {
  96. portsSelect.disabled = false;
  97. manifestsSelect.disabled = false;
  98. flashButton.disabled = false;
  99. }
  100. function disableInputs() {
  101. portsSelect.disabled = true;
  102. manifestsSelect.disabled = true;
  103. flashButton.disabled = true;
  104. }
  105. /**
  106. * Generic catch all error. Shows notification at the moment.
  107. * @param error
  108. */
  109. function onError(error){
  110. if(last_notification !== error.message) {
  111. last_notification = error.message;
  112. new Notification(last_notification);
  113. }
  114. appStatus.textContent = error.message;
  115. }
  116. function generateManifestList(manifestsJSON) {
  117. manifestsJSON.options.forEach((option) => {
  118. option.versions.forEach((version) => {
  119. const optionElement = document.createElement("option");
  120. optionElement.textContent = `${option.name} - ${version.version}`;
  121. optionElement.value = version.manifest;
  122. manifestsSelect.appendChild(optionElement);
  123. manifestsSelect.disabled = false;
  124. });
  125. });
  126. }
  127. function getManifests() {
  128. appStatus.textContent = "Getting latest manifests.";
  129. // Break the cache to get the latest
  130. remote.getCurrentWindow().webContents.session.clearCache(() => {
  131. fetch(CONSTANTS.manifestList)
  132. .then(processJSON)
  133. .then(generateManifestList).catch(error => {
  134. setTimeout(getManifests, CONSTANTS.pollTime);
  135. });
  136. });
  137. }
  138. function flashWithManifest(manifest) {
  139. appStatus.textContent = `Flashing ${portsSelect.value}`;
  140. const numberOfSteps = manifest.flash.length * 2;
  141. let correctStepNumber = 1;
  142. prepareBinaries(manifest, (err, flashSpec) => {
  143. if(err) throw err;
  144. const esp = new RomComm({
  145. portName: portsSelect.value,
  146. baudRate: 115200
  147. });
  148. esp.on('progress', (progress) => {
  149. const flashPercent = Math.round((progress.details.flashedBytes/progress.details.totalBytes) * 100);
  150. const processSoFar = 50; //From download and extracting.
  151. const flashProcess = flashPercent / 2; //To add to the overall progress
  152. updateProgressBar(processSoFar + flashProcess, svg);
  153. appStatus.textContent = `${progress.display} - ${flashPercent}%`;
  154. });
  155. esp.open().then((result) => {
  156. appStatus.textContent = `Flashing device connected to ${portsSelect.value}`;
  157. let promise = Promise.resolve();
  158. return esp.flashSpecifications(flashSpec)
  159. .then(() => esp.close())
  160. .then((result) => {
  161. new Notification("Flash Finished!");
  162. readyToFlash();
  163. log.info("Flashed to latest Espruino build!", result);
  164. });
  165. }).catch((error) => {
  166. new Notification("An error occured during flashing.");
  167. readyToFlash();
  168. log.error("Oh noes!", error);
  169. });
  170. }).on("entry", (progress) => {
  171. //For the download/extract progress. The other half is flashing.
  172. const extractPercent = Math.round((correctStepNumber++/numberOfSteps) * 50);
  173. updateProgressBar(extractPercent, svg);
  174. appStatus.textContent = progress.display;
  175. });
  176. }
  177. function cloneSVGNode(node) {
  178. return node.cloneNode(true);
  179. }
  180. function updateClass(node) {
  181. node.setAttribute("class", "bg");
  182. return node;
  183. }
  184. function updateProgressBar(percent, svg){
  185. const line = svg.getElementsByClassName("st0")[0];
  186. const startDot = svg.getElementsByClassName("st1")[0];
  187. const finishDot = svg.getElementsByClassName("st2")[0];
  188. let backgroundElements = svg.getElementsByClassName("bg");
  189. if(backgroundElements.length === 0) {
  190. const g = svg.getElementsByTagName("g")[0];
  191. backgroundElements = [line, startDot, finishDot]
  192. .map(cloneSVGNode)
  193. .map(updateClass);
  194. backgroundElements.forEach(node => g.insertBefore(node, line));
  195. }
  196. const bgLine = backgroundElements[0];
  197. line.points.clear();
  198. if( percent < 1 ) {
  199. startDot.style.opacity = 0;
  200. } else {
  201. startDot.style.opacity = 1;
  202. }
  203. if( percent > 99 ) {
  204. finishDot.style.opacity = 1;
  205. } else {
  206. finishDot.style.opacity = 0;
  207. }
  208. for(var i = 0; i < percent * (bgLine.points.numberOfItems / 100); i ++) {
  209. if(i < bgLine.points.numberOfItems) {
  210. const point = bgLine.points.getItem(i);
  211. const newPoint = svg.createSVGPoint();
  212. newPoint.x = point.x;
  213. newPoint.y = point.y;
  214. line.points.appendItem(newPoint);
  215. }
  216. }
  217. }
  218. function prepareUIForFlashing(callback) {
  219. let marginLeft = 0;
  220. let incrementor = 0.01;
  221. let percent = 100;
  222. let centerLeft = (appWrapper.clientWidth - logoWrapper.clientWidth) / 2;
  223. let interval = setInterval(() => {
  224. incrementor += 0.015;
  225. marginLeft += 1.5 / incrementor;
  226. if(marginLeft <= centerLeft) {
  227. logoWrapper.style.marginLeft = marginLeft + "px";
  228. } else {
  229. clearInterval(interval);
  230. }
  231. }, 10);
  232. let percentInterval = setInterval(() => {
  233. percent -= 1;
  234. form.style.opacity = percent / 100;
  235. updateProgressBar(percent, svg);
  236. if(percent === 0) {
  237. clearInterval(percentInterval);
  238. callback();
  239. }
  240. }, 1);
  241. }
  242. /**
  243. * Get's manifest list for possibilities for flashing,
  244. * scans serial ports and sets up timer for checking for changes.
  245. */
  246. function start() {
  247. getManifests();
  248. serialScanner.scan();
  249. setInterval(serialScanner.checkForChanges.bind(serialScanner), CONSTANTS.pollTime);
  250. }
  251. /**
  252. * Start Application
  253. */
  254. start();