electron.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. /* jshint esversion: 6 */
  2. var host = host || {};
  3. const electron = require('electron');
  4. const fs = require('fs');
  5. const http = require('http');
  6. const https = require('https');
  7. const process = require('process');
  8. const path = require('path');
  9. const view = require('./view');
  10. global.protobuf = require('./protobuf');
  11. global.flatbuffers = require('./flatbuffers');
  12. host.ElectronHost = class {
  13. constructor() {
  14. process.on('uncaughtException', (err) => {
  15. this.exception(err, true);
  16. });
  17. window.eval = global.eval = () => {
  18. throw new Error('window.eval() not supported.');
  19. };
  20. this._version = electron.remote.app.getVersion();
  21. }
  22. get document() {
  23. return window.document;
  24. }
  25. get version() {
  26. return this._version;
  27. }
  28. get type() {
  29. return 'Electron';
  30. }
  31. get browser() {
  32. return false;
  33. }
  34. initialize(view) {
  35. this._view = view;
  36. return new Promise((resolve /*, reject */) => {
  37. const accept = () => {
  38. if (electron.remote.app.isPackaged) {
  39. this._telemetry = require('universal-analytics')('UA-54146-13', this._getConfiguration('userId'));
  40. this._telemetry.set('anonymizeIp', 1);
  41. }
  42. resolve();
  43. };
  44. const request = () => {
  45. this._view.show('welcome consent');
  46. const acceptButton = this.document.getElementById('consent-accept-button');
  47. if (acceptButton) {
  48. acceptButton.addEventListener('click', () => {
  49. this._setConfiguration('consent', Date.now());
  50. accept();
  51. });
  52. }
  53. };
  54. const time = this._getConfiguration('consent');
  55. if (time && (Date.now() - time) < 30 * 24 * 60 * 60 * 1000) {
  56. accept();
  57. }
  58. else {
  59. this._request('https://ipinfo.io/json', { 'Content-Type': 'application/json' }, 'utf-8', 2000).then((text) => {
  60. try {
  61. const json = JSON.parse(text);
  62. const countries = ['AT', 'BE', 'BG', 'HR', 'CZ', 'CY', 'DK', 'EE', 'FI', 'FR', 'DE', 'EL', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'NO', 'PL', 'PT', 'SK', 'ES', 'SE', 'GB', 'UK', 'GR', 'EU', 'RO'];
  63. if (json && json.country && !countries.indexOf(json.country) !== -1) {
  64. this._setConfiguration('consent', Date.now());
  65. accept();
  66. }
  67. else {
  68. request();
  69. }
  70. }
  71. catch (err) {
  72. request();
  73. }
  74. }).catch(() => {
  75. request();
  76. });
  77. }
  78. });
  79. }
  80. start() {
  81. this._view.show('welcome');
  82. electron.ipcRenderer.on('open', (_, data) => {
  83. if (this._view.accept(data.file)) {
  84. this._openFile(data.file);
  85. }
  86. });
  87. electron.ipcRenderer.on('export', (_, data) => {
  88. this._view.export(data.file);
  89. });
  90. electron.ipcRenderer.on('cut', () => {
  91. this._view.cut();
  92. });
  93. electron.ipcRenderer.on('copy', () => {
  94. this._view.copy();
  95. });
  96. electron.ipcRenderer.on('paste', () => {
  97. this._view.paste();
  98. });
  99. electron.ipcRenderer.on('selectall', () => {
  100. this._view.selectAll();
  101. });
  102. electron.ipcRenderer.on('toggle-attributes', () => {
  103. this._view.toggleAttributes();
  104. this._update('show-attributes', this._view.showAttributes);
  105. });
  106. electron.ipcRenderer.on('toggle-initializers', () => {
  107. this._view.toggleInitializers();
  108. this._update('show-initializers', this._view.showInitializers);
  109. });
  110. electron.ipcRenderer.on('toggle-names', () => {
  111. this._view.toggleNames();
  112. this._update('show-names', this._view.showNames);
  113. });
  114. electron.ipcRenderer.on('toggle-direction', () => {
  115. this._view.toggleDirection();
  116. this._update('show-horizontal', this._view.showHorizontal);
  117. });
  118. electron.ipcRenderer.on('zoom-in', () => {
  119. this.document.getElementById('zoom-in-button').click();
  120. });
  121. electron.ipcRenderer.on('zoom-out', () => {
  122. this.document.getElementById('zoom-out-button').click();
  123. });
  124. electron.ipcRenderer.on('reset-zoom', () => {
  125. this._view.resetZoom();
  126. });
  127. electron.ipcRenderer.on('show-properties', () => {
  128. this.document.getElementById('menu-button').click();
  129. });
  130. electron.ipcRenderer.on('find', () => {
  131. this._view.find();
  132. });
  133. this.document.getElementById('menu-button').addEventListener('click', () => {
  134. this._view.showModelProperties();
  135. });
  136. const openFileButton = this.document.getElementById('open-file-button');
  137. if (openFileButton) {
  138. openFileButton.style.opacity = 1;
  139. openFileButton.addEventListener('click', () => {
  140. electron.ipcRenderer.send('open-file-dialog', {});
  141. });
  142. }
  143. this.document.addEventListener('dragover', (e) => {
  144. e.preventDefault();
  145. });
  146. this.document.addEventListener('drop', (e) => {
  147. e.preventDefault();
  148. });
  149. this.document.body.addEventListener('drop', (e) => {
  150. e.preventDefault();
  151. const files = Array.from(e.dataTransfer.files).map(((file) => file.path));
  152. if (files.length > 0) {
  153. electron.ipcRenderer.send('drop-files', { files: files });
  154. }
  155. return false;
  156. });
  157. }
  158. environment(name) {
  159. if (name == 'zoom') {
  160. return 'd3';
  161. }
  162. return null;
  163. }
  164. error(message, detail) {
  165. const owner = electron.remote.getCurrentWindow();
  166. const options = {
  167. type: 'error',
  168. message: message,
  169. detail: detail,
  170. };
  171. electron.remote.dialog.showMessageBoxSync(owner, options);
  172. }
  173. confirm(message, detail) {
  174. const owner = electron.remote.getCurrentWindow();
  175. const options = {
  176. type: 'question',
  177. message: message,
  178. detail: detail,
  179. buttons: ['Yes', 'No'],
  180. defaultId: 0,
  181. cancelId: 1
  182. };
  183. const result = electron.remote.dialog.showMessageBoxSync(owner, options);
  184. return result == 0;
  185. }
  186. require(id) {
  187. try {
  188. return Promise.resolve(require(id));
  189. }
  190. catch (error) {
  191. return Promise.reject(error);
  192. }
  193. }
  194. save(name, extension, defaultPath, callback) {
  195. const owner = electron.remote.BrowserWindow.getFocusedWindow();
  196. const showSaveDialogOptions = {
  197. title: 'Export Tensor',
  198. defaultPath: defaultPath,
  199. buttonLabel: 'Export',
  200. filters: [ { name: name, extensions: [ extension ] } ]
  201. };
  202. const selectedFile = electron.remote.dialog.showSaveDialogSync(owner, showSaveDialogOptions);
  203. if (selectedFile) {
  204. callback(selectedFile);
  205. }
  206. }
  207. export(file, blob) {
  208. const reader = new FileReader();
  209. reader.onload = (e) => {
  210. const data = new Uint8Array(e.target.result);
  211. fs.writeFile(file, data, null, (err) => {
  212. if (err) {
  213. this.exception(err, false);
  214. this.error('Error writing file.', err.message);
  215. }
  216. });
  217. };
  218. let err = null;
  219. if (!blob) {
  220. err = new Error("Export blob is '" + JSON.stringify(blob) + "'.");
  221. }
  222. else if (!(blob instanceof Blob)) {
  223. err = new Error("Export blob type is '" + (typeof blob) + "'.");
  224. }
  225. if (err) {
  226. this.exception(err, false);
  227. this.error('Error exporting image.', err.message);
  228. }
  229. else {
  230. reader.readAsArrayBuffer(blob);
  231. }
  232. }
  233. request(base, file, encoding) {
  234. return new Promise((resolve, reject) => {
  235. const pathname = path.join(base || __dirname, file);
  236. fs.exists(pathname, (exists) => {
  237. if (!exists) {
  238. reject(new Error("File not found '" + file + "'."));
  239. }
  240. else {
  241. fs.readFile(pathname, encoding, (err, data) => {
  242. if (err) {
  243. reject(err);
  244. }
  245. else {
  246. resolve(data);
  247. }
  248. });
  249. }
  250. });
  251. });
  252. }
  253. openURL(url) {
  254. electron.shell.openExternal(url);
  255. }
  256. exception(error, fatal) {
  257. if (this._telemetry && error && error.telemetry !== false) {
  258. try {
  259. const description = [];
  260. description.push((error && error.name ? (error.name + ': ') : '') + (error && error.message ? error.message : '(null)'));
  261. if (error.stack) {
  262. const match = error.stack.match(/\n {4}at (.*)\((.*)\)/);
  263. if (match) {
  264. description.push(match[1] + '(' + match[2].split('/').pop().split('\\').pop() + ')');
  265. }
  266. }
  267. const params = {
  268. applicationName: this.type,
  269. applicationVersion: this.version,
  270. userAgentOverride: navigator.userAgent
  271. };
  272. this._telemetry.exception(description.join(' @ '), fatal, params, () => { });
  273. }
  274. catch (e) {
  275. // continue regardless of error
  276. }
  277. }
  278. }
  279. screen(name) {
  280. if (this._telemetry) {
  281. try {
  282. const params = {
  283. userAgentOverride: navigator.userAgent
  284. };
  285. this._telemetry.screenview(name, this.type, this.version, null, null, params, () => { });
  286. }
  287. catch (e) {
  288. // continue regardless of error
  289. }
  290. }
  291. }
  292. event(category, action, label, value) {
  293. if (this._telemetry) {
  294. try {
  295. const params = {
  296. applicationName: this.type,
  297. applicationVersion: this.version,
  298. userAgentOverride: navigator.userAgent
  299. };
  300. this._telemetry.event(category, action, label, value, params, () => { });
  301. }
  302. catch (e) {
  303. // continue regardless of error
  304. }
  305. }
  306. }
  307. _openFile(file) {
  308. if (file) {
  309. this._view.show('welcome spinner');
  310. this._readFile(file).then((buffer) => {
  311. const context = new ElectonContext(this, path.dirname(file), path.basename(file), buffer);
  312. this._view.open(context).then((model) => {
  313. this._view.show(null);
  314. if (model) {
  315. this._update('path', file);
  316. }
  317. this._update('show-attributes', this._view.showAttributes);
  318. this._update('show-initializers', this._view.showInitializers);
  319. this._update('show-names', this._view.showNames);
  320. }).catch((error) => {
  321. if (error) {
  322. this._view.error(error, null, null);
  323. this._update('path', null);
  324. }
  325. this._update('show-attributes', this._view.showAttributes);
  326. this._update('show-initializers', this._view.showInitializers);
  327. this._update('show-names', this._view.showNames);
  328. });
  329. }).catch((error) => {
  330. this._view.error(error, 'Error while reading file.', null);
  331. this._update('path', null);
  332. });
  333. }
  334. }
  335. _readFile(file) {
  336. return new Promise((resolve, reject) => {
  337. fs.exists(file, (exists) => {
  338. if (!exists) {
  339. reject(new Error('The file \'' + file + '\' does not exist.'));
  340. }
  341. else {
  342. fs.readFile(file, null, (err, buffer) => {
  343. if (err) {
  344. reject(err);
  345. }
  346. else {
  347. resolve(buffer);
  348. }
  349. });
  350. }
  351. });
  352. });
  353. }
  354. _request(url, headers, encoding, timeout) {
  355. return new Promise((resolve, reject) => {
  356. const httpModule = url.split(':').shift() === 'https' ? https : http;
  357. const options = {
  358. headers: headers
  359. };
  360. const request = httpModule.get(url, options, (response) => {
  361. if (response.statusCode !== 200) {
  362. const err = new Error("The web request failed with status code " + response.statusCode + " at '" + url + "'.");
  363. err.type = 'error';
  364. err.url = url;
  365. err.status = response.statusCode;
  366. reject(err);
  367. }
  368. else {
  369. let data = '';
  370. response.on('data', (chunk) => {
  371. data += chunk;
  372. });
  373. response.on('err', (err) => {
  374. reject(err);
  375. });
  376. response.on('end', () => {
  377. resolve(data);
  378. });
  379. }
  380. }).on("error", (err) => {
  381. reject(err);
  382. });
  383. if (timeout) {
  384. request.setTimeout(timeout, () => {
  385. request.abort();
  386. const err = new Error("The web request timed out at '" + url + "'.");
  387. err.type = 'timeout';
  388. err.url = url;
  389. reject(err);
  390. });
  391. }
  392. });
  393. }
  394. _getConfiguration(name) {
  395. const configuration = electron.remote.getGlobal('global').application.service('configuration');
  396. return configuration && configuration.has(name) ? configuration.get(name) : undefined;
  397. }
  398. _setConfiguration(name, value) {
  399. const configuration = electron.remote.getGlobal('global').application.service('configuration');
  400. if (configuration) {
  401. configuration.set(name, value);
  402. }
  403. }
  404. _update(name, value) {
  405. electron.ipcRenderer.send('update', { name: name, value: value });
  406. }
  407. };
  408. class ElectonContext {
  409. constructor(host, folder, identifier, buffer) {
  410. this._host = host;
  411. this._folder = folder;
  412. this._identifier = identifier;
  413. this._buffer = buffer;
  414. }
  415. request(file, encoding) {
  416. return this._host.request(this._folder, file, encoding);
  417. }
  418. get identifier() {
  419. return this._identifier;
  420. }
  421. get buffer() {
  422. return this._buffer;
  423. }
  424. }
  425. window.__view__ = new view.View(new host.ElectronHost());