index.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910
  1. /* jshint esversion: 6 */
  2. /* eslint "no-global-assign": ["error", {"exceptions": [ "TextDecoder", "TextEncoder", "URLSearchParams" ] } ] */
  3. /* global view */
  4. var host = {};
  5. host.BrowserHost = class {
  6. constructor() {
  7. window.eval = () => {
  8. throw new Error('window.eval() not supported.');
  9. };
  10. this._document = window.document;
  11. this._meta = {};
  12. for (const element of Array.from(this._document.getElementsByTagName('meta'))) {
  13. if (element.content) {
  14. this._meta[element.name] = this._meta[element.name] || [];
  15. this._meta[element.name].push(element.content);
  16. }
  17. }
  18. this._type = this._meta.type ? this._meta.type[0] : 'Browser';
  19. this._version = this._meta.version ? this._meta.version[0] : null;
  20. this._telemetry = this._version && this._version !== '0.0.0';
  21. }
  22. get document() {
  23. return this._document;
  24. }
  25. get version() {
  26. return this._version;
  27. }
  28. get type() {
  29. return this._type;
  30. }
  31. get browser() {
  32. return true;
  33. }
  34. initialize(view) {
  35. this._view = view;
  36. return new Promise((resolve /*, reject */) => {
  37. const accept = () => {
  38. if (this._telemetry) {
  39. const script = this.document.createElement('script');
  40. script.setAttribute('type', 'text/javascript');
  41. script.setAttribute('src', 'https://www.google-analytics.com/analytics.js');
  42. script.onload = () => {
  43. if (window.ga) {
  44. window.ga.l = 1 * new Date();
  45. window.ga('create', 'UA-54146-13', 'auto');
  46. window.ga('set', 'anonymizeIp', true);
  47. }
  48. resolve();
  49. };
  50. script.onerror = () => {
  51. resolve();
  52. };
  53. this.document.body.appendChild(script);
  54. }
  55. else {
  56. resolve();
  57. }
  58. };
  59. const request = () => {
  60. this._view.show('welcome consent');
  61. const acceptButton = this.document.getElementById('consent-accept-button');
  62. if (acceptButton) {
  63. acceptButton.addEventListener('click', () => {
  64. this._setCookie('consent', 'yes', 30);
  65. accept();
  66. });
  67. }
  68. };
  69. if (this._getCookie('consent')) {
  70. accept();
  71. }
  72. else {
  73. this._request('https://ipinfo.io/json', { 'Content-Type': 'application/json' }, 'utf-8', 2000).then((text) => {
  74. try {
  75. const json = JSON.parse(text);
  76. 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'];
  77. if (json && json.country && !countries.indexOf(json.country) !== -1) {
  78. this._setCookie('consent', Date.now(), 30);
  79. accept();
  80. }
  81. else {
  82. request();
  83. }
  84. }
  85. catch (err) {
  86. request();
  87. }
  88. }).catch(() => {
  89. request();
  90. });
  91. }
  92. });
  93. }
  94. start() {
  95. window.addEventListener('error', (e) => {
  96. this.exception(e.error, true);
  97. });
  98. const params = new URLSearchParams(window.location.search);
  99. this._zoom = params.get('zoom') || 'd3';
  100. this._menu = new host.Dropdown(this.document, 'menu-button', 'menu-dropdown');
  101. this._menu.add({
  102. label: 'Properties...',
  103. accelerator: 'CmdOrCtrl+Enter',
  104. click: () => this._view.showModelProperties()
  105. });
  106. this._menu.add({});
  107. this._menu.add({
  108. label: 'Find...',
  109. accelerator: 'CmdOrCtrl+F',
  110. click: () => this._view.find()
  111. });
  112. this._menu.add({});
  113. this._menu.add({
  114. label: () => this._view.showAttributes ? 'Hide Attributes' : 'Show Attributes',
  115. accelerator: 'CmdOrCtrl+D',
  116. click: () => this._view.toggleAttributes()
  117. });
  118. this._menu.add({
  119. label: () => this._view.showInitializers ? 'Hide Initializers' : 'Show Initializers',
  120. accelerator: 'CmdOrCtrl+I',
  121. click: () => this._view.toggleInitializers()
  122. });
  123. this._menu.add({
  124. label: () => this._view.showNames ? 'Hide Names' : 'Show Names',
  125. accelerator: 'CmdOrCtrl+U',
  126. click: () => this._view.toggleNames()
  127. });
  128. this._menu.add({
  129. label: () => !this._view.showHorizontal ? 'Show Horizontal' : 'Show Vertical',
  130. accelerator: 'CmdOrCtrl+K',
  131. click: () => this._view.toggleDirection()
  132. });
  133. this._menu.add({});
  134. this._menu.add({
  135. label: 'Zoom In',
  136. accelerator: 'Shift+Up',
  137. click: () => this.document.getElementById('zoom-in-button').click()
  138. });
  139. this._menu.add({
  140. label: 'Zoom Out',
  141. accelerator: 'Shift+Down',
  142. click: () => this.document.getElementById('zoom-out-button').click()
  143. });
  144. this._menu.add({
  145. label: 'Actual Size',
  146. accelerator: 'Shift+Backspace',
  147. click: () => this._view.resetZoom()
  148. });
  149. this._menu.add({});
  150. this._menu.add({
  151. label: 'Export as PNG',
  152. accelerator: 'CmdOrCtrl+Shift+E',
  153. click: () => this._view.export(document.title + '.png')
  154. });
  155. this._menu.add({
  156. label: 'Export as SVG',
  157. accelerator: 'CmdOrCtrl+Alt+E',
  158. click: () => this._view.export(document.title + '.svg')
  159. });
  160. this.document.getElementById('menu-button').addEventListener('click', (e) => {
  161. this._menu.toggle();
  162. e.preventDefault();
  163. });
  164. this._menu.add({});
  165. this._menu.add({
  166. label: 'About ' + this.document.title,
  167. click: () => this._about()
  168. });
  169. this.document.getElementById('version').innerText = this.version;
  170. if (this._meta.file) {
  171. const url = this._meta.file[0];
  172. if (this._view.accept(url)) {
  173. this._openModel(url, null);
  174. return;
  175. }
  176. }
  177. const url = params.get('url');
  178. if (url) {
  179. const identifier = params.get('identifier') || null;
  180. const location = url.replace(new RegExp('^https://github.com/([\\w]*/[\\w]*)/blob/([\\w/_.]*)(\\?raw=true)?$'), 'https://raw.githubusercontent.com/$1/$2');
  181. if (this._view.accept(identifier || location)) {
  182. this._openModel(location, identifier);
  183. return;
  184. }
  185. }
  186. const gist = params.get('gist');
  187. if (gist) {
  188. this._openGist(gist);
  189. return;
  190. }
  191. this._view.show('welcome');
  192. const openFileButton = this.document.getElementById('open-file-button');
  193. const openFileDialog = this.document.getElementById('open-file-dialog');
  194. if (openFileButton && openFileDialog) {
  195. openFileButton.addEventListener('click', () => {
  196. openFileDialog.value = '';
  197. openFileDialog.click();
  198. });
  199. openFileDialog.addEventListener('change', (e) => {
  200. if (e.target && e.target.files && e.target.files.length > 0) {
  201. const files = Array.from(e.target.files);
  202. const file = files.find((file) => this._view.accept(file.name));
  203. if (file) {
  204. this._open(file, files);
  205. }
  206. }
  207. });
  208. }
  209. const githubButton = this.document.getElementById('github-button');
  210. const githubLink = this.document.getElementById('logo-github');
  211. if (githubButton && githubLink) {
  212. githubButton.style.opacity = 1;
  213. githubButton.addEventListener('click', () => {
  214. this.openURL(githubLink.href);
  215. });
  216. }
  217. this.document.addEventListener('dragover', (e) => {
  218. e.preventDefault();
  219. });
  220. this.document.addEventListener('drop', (e) => {
  221. e.preventDefault();
  222. });
  223. this.document.body.addEventListener('drop', (e) => {
  224. e.preventDefault();
  225. if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
  226. const files = Array.from(e.dataTransfer.files);
  227. const file = files.find((file) => this._view.accept(file.name));
  228. if (file) {
  229. this._open(file, files);
  230. }
  231. }
  232. });
  233. }
  234. environment(name) {
  235. if (name == 'zoom') {
  236. return this._zoom;
  237. }
  238. return null;
  239. }
  240. error(message, detail) {
  241. alert((message == 'Error' ? '' : message + ' ') + detail);
  242. }
  243. confirm(message, detail) {
  244. return confirm(message + ' ' + detail);
  245. }
  246. require(id) {
  247. const url = this._url(id + '.js');
  248. window.__modules__ = window.__modules__ || {};
  249. if (window.__modules__[url]) {
  250. return Promise.resolve(window.__exports__[url]);
  251. }
  252. return new Promise((resolve, reject) => {
  253. window.module = { exports: {} };
  254. const script = document.createElement('script');
  255. script.setAttribute('id', id);
  256. script.setAttribute('type', 'text/javascript');
  257. script.setAttribute('src', url);
  258. script.onload = (e) => {
  259. if (window.module && window.module.exports) {
  260. const exports = window.module.exports;
  261. delete window.module;
  262. window.__modules__[id] = exports;
  263. resolve(exports);
  264. }
  265. else {
  266. reject(new Error('The script \'' + e.target.src + '\' has no exports.'));
  267. }
  268. };
  269. script.onerror = (e) => {
  270. delete window.module;
  271. reject(new Error('The script \'' + e.target.src + '\' failed to load.'));
  272. };
  273. this.document.head.appendChild(script);
  274. });
  275. }
  276. save(name, extension, defaultPath, callback) {
  277. callback(defaultPath + '.' + extension);
  278. }
  279. export(file, blob) {
  280. const element = this.document.createElement('a');
  281. element.download = file;
  282. element.href = URL.createObjectURL(blob);
  283. this.document.body.appendChild(element);
  284. element.click();
  285. this.document.body.removeChild(element);
  286. }
  287. request(base, file, encoding) {
  288. const url = base ? (base + '/' + file) : this._url(file);
  289. return this._request(url, null, encoding);
  290. }
  291. openURL(url) {
  292. window.location = url;
  293. }
  294. exception(error, fatal) {
  295. if (this._telemetry && window.ga) {
  296. const description = [];
  297. description.push((error && error.name ? (error.name + ': ') : '') + (error && error.message ? error.message : '(null)'));
  298. if (error.stack) {
  299. const match = error.stack.match(/\n {4}at (.*)\((.*)\)/);
  300. if (match) {
  301. description.push(match[1] + '(' + match[2].split('/').pop() + ')');
  302. }
  303. else {
  304. description.push(error.stack.split('\n').shift());
  305. }
  306. }
  307. window.ga('send', 'exception', {
  308. exDescription: description.join(' @ '),
  309. exFatal: fatal,
  310. appName: this.type,
  311. appVersion: this.version
  312. });
  313. }
  314. }
  315. screen(name) {
  316. if (this._telemetry && window.ga) {
  317. window.ga('send', 'screenview', {
  318. screenName: name,
  319. appName: this.type,
  320. appVersion: this.version
  321. });
  322. }
  323. }
  324. event(category, action, label, value) {
  325. if (this._telemetry && window.ga) {
  326. window.ga('send', 'event', {
  327. eventCategory: category,
  328. eventAction: action,
  329. eventLabel: label,
  330. eventValue: value,
  331. appName: this.type,
  332. appVersion: this.version
  333. });
  334. }
  335. }
  336. _request(url, headers, encoding, timeout) {
  337. return new Promise((resolve, reject) => {
  338. const request = new XMLHttpRequest();
  339. if (!encoding) {
  340. request.responseType = 'arraybuffer';
  341. }
  342. if (timeout) {
  343. request.timeout = timeout;
  344. }
  345. const error = (status) => {
  346. const err = new Error("The web request failed with status code " + status + " at '" + url + "'.");
  347. err.type = 'error';
  348. err.url = url;
  349. return err;
  350. };
  351. request.onload = () => {
  352. if (request.status == 200) {
  353. if (request.responseType == 'arraybuffer') {
  354. resolve(new Uint8Array(request.response));
  355. }
  356. else {
  357. resolve(request.responseText);
  358. }
  359. }
  360. else {
  361. reject(error(request.status));
  362. }
  363. };
  364. request.onerror = (e) => {
  365. const err = error(request.status);
  366. err.type = e.type;
  367. reject(err);
  368. };
  369. request.ontimeout = () => {
  370. request.abort();
  371. const err = new Error("The web request timed out in '" + url + "'.");
  372. err.type = 'timeout';
  373. err.url = url;
  374. reject(err);
  375. };
  376. request.open('GET', url, true);
  377. if (headers) {
  378. for (const name of Object.keys(headers)) {
  379. request.setRequestHeader(name, headers[name]);
  380. }
  381. }
  382. request.send();
  383. });
  384. }
  385. _url(file) {
  386. let url = file;
  387. if (window && window.location && window.location.href) {
  388. let location = window.location.href.split('?').shift();
  389. if (location.endsWith('.html')) {
  390. location = location.split('/').slice(0, -1).join('/');
  391. }
  392. if (location.endsWith('/')) {
  393. location = location.slice(0, -1);
  394. }
  395. url = location + '/' + file;
  396. }
  397. return url;
  398. }
  399. _openModel(url, identifier) {
  400. url = url + ((/\?/).test(url) ? "&" : "?") + (new Date()).getTime();
  401. this._view.show('welcome spinner');
  402. this._request(url).then((buffer) => {
  403. const context = new host.BrowserHost.BrowserContext(this, url, identifier, buffer);
  404. this._view.open(context).then(() => {
  405. this.document.title = identifier || context.identifier;
  406. }).catch((err) => {
  407. if (err) {
  408. this._view.error(err, null, 'welcome');
  409. }
  410. });
  411. }).catch((err) => {
  412. this.error('Model load request failed.', err.message);
  413. this._view.show('welcome');
  414. });
  415. }
  416. _open(file, files) {
  417. this._view.show('welcome spinner');
  418. const context = new host.BrowserHost.BrowserFileContext(file, files);
  419. context.open().then(() => {
  420. return this._view.open(context).then((model) => {
  421. this._view.show(null);
  422. this.document.title = files[0].name;
  423. return model;
  424. });
  425. }).catch((error) => {
  426. this._view.error(error, null, null);
  427. });
  428. }
  429. _openGist(gist) {
  430. this._view.show('welcome spinner');
  431. const url = 'https://api.github.com/gists/' + gist;
  432. this._request(url, { 'Content-Type': 'application/json' }, 'utf-8').then((text) => {
  433. const json = JSON.parse(text);
  434. if (json.message) {
  435. this.error('Error while loading Gist.', json.message);
  436. return;
  437. }
  438. const key = Object.keys(json.files).find((key) => this._view.accept(json.files[key].filename));
  439. if (!key) {
  440. this.error('Error while loading Gist.', 'Gist does not contain a model file.');
  441. return;
  442. }
  443. const file = json.files[key];
  444. const identifier = file.filename;
  445. const encoder = new TextEncoder();
  446. const buffer = encoder.encode(file.content);
  447. const context = new host.BrowserHost.BrowserContext(this, '', identifier, buffer);
  448. this._view.open(context).then(() => {
  449. this.document.title = identifier;
  450. }).catch((error) => {
  451. if (error) {
  452. this._view.show(error.name, error, 'welcome');
  453. }
  454. });
  455. }).catch((err) => {
  456. this._view.show('Model load request failed.', err, 'welcome');
  457. });
  458. }
  459. _setCookie(name, value, days) {
  460. const date = new Date();
  461. date.setTime(date.getTime() + ((typeof days !== "number" ? 365 : days) * 24 * 60 * 60 * 1000));
  462. document.cookie = name + "=" + value + ";path=/;expires=" + date.toUTCString();
  463. }
  464. _getCookie(name) {
  465. const cookie = '; ' + document.cookie;
  466. const parts = cookie.split('; ' + name + '=');
  467. return parts.length < 2 ? undefined : parts.pop().split(';').shift();
  468. }
  469. _about() {
  470. const self = this;
  471. const eventHandler = () => {
  472. window.removeEventListener('keydown', eventHandler);
  473. self.document.body.removeEventListener('click', eventHandler);
  474. self._view.show('default');
  475. };
  476. window.addEventListener('keydown', eventHandler);
  477. this.document.body.addEventListener('click', eventHandler);
  478. this._view.show('about');
  479. }
  480. };
  481. if (typeof TextDecoder === "undefined") {
  482. TextDecoder = function TextDecoder(encoding) {
  483. this._encoding = encoding;
  484. };
  485. TextDecoder.prototype.decode = function decode(buffer) {
  486. let result = '';
  487. const length = buffer.length;
  488. let i = 0;
  489. switch (this._encoding) {
  490. case 'utf-8':
  491. while (i < length) {
  492. const c = buffer[i++];
  493. switch(c >> 4) {
  494. case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: {
  495. result += String.fromCharCode(c);
  496. break;
  497. }
  498. case 12: case 13: {
  499. const c2 = buffer[i++];
  500. result += String.fromCharCode(((c & 0x1F) << 6) | (c2 & 0x3F));
  501. break;
  502. }
  503. case 14: {
  504. const c2 = buffer[i++];
  505. const c3 = buffer[i++];
  506. result += String.fromCharCode(((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | ((c3 & 0x3F) << 0));
  507. break;
  508. }
  509. case 15: {
  510. const c2 = buffer[i++];
  511. const c3 = buffer[i++];
  512. const c4 = buffer[i++];
  513. result += String.fromCodePoint(((c & 0x07) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F));
  514. }
  515. }
  516. }
  517. break;
  518. case 'ascii':
  519. while (i < length) {
  520. result += String.fromCharCode(buffer[i++]);
  521. }
  522. break;
  523. }
  524. return result;
  525. };
  526. }
  527. if (typeof TextEncoder === 'undefined') {
  528. TextEncoder = function TextEncoder() {
  529. };
  530. TextEncoder.prototype.encode = function encode(str) {
  531. "use strict";
  532. const length = str.length;
  533. let resPos = -1;
  534. const resArr = typeof Uint8Array === "undefined" ? new Array(length * 2) : new Uint8Array(length * 3);
  535. for (let point = 0, nextcode = 0, i = 0; i !== length; ) {
  536. point = str.charCodeAt(i);
  537. i += 1;
  538. if (point >= 0xD800 && point <= 0xDBFF) {
  539. if (i === length) {
  540. resArr[resPos += 1] = 0xef; resArr[resPos += 1] = 0xbf;
  541. resArr[resPos += 1] = 0xbd; break;
  542. }
  543. nextcode = str.charCodeAt(i);
  544. if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
  545. point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000;
  546. i += 1;
  547. if (point > 0xffff) {
  548. resArr[resPos += 1] = (0x1e<<3) | (point>>>18);
  549. resArr[resPos += 1] = (0x2<<6) | ((point>>>12)&0x3f);
  550. resArr[resPos += 1] = (0x2<<6) | ((point>>>6)&0x3f);
  551. resArr[resPos += 1] = (0x2<<6) | (point&0x3f);
  552. continue;
  553. }
  554. }
  555. else {
  556. resArr[resPos += 1] = 0xef; resArr[resPos += 1] = 0xbf;
  557. resArr[resPos += 1] = 0xbd; continue;
  558. }
  559. }
  560. if (point <= 0x007f) {
  561. resArr[resPos += 1] = (0x0<<7) | point;
  562. }
  563. else if (point <= 0x07ff) {
  564. resArr[resPos += 1] = (0x6<<5) | (point>>>6);
  565. resArr[resPos += 1] = (0x2<<6) | (point&0x3f);
  566. }
  567. else {
  568. resArr[resPos += 1] = (0xe<<4) | (point>>>12);
  569. resArr[resPos += 1] = (0x2<<6) | ((point>>>6)&0x3f);
  570. resArr[resPos += 1] = (0x2<<6) | (point&0x3f);
  571. }
  572. }
  573. if (typeof Uint8Array!=="undefined") {
  574. return new Uint8Array(resArr.buffer.slice(0, resPos+1));
  575. }
  576. else {
  577. return resArr.length === resPos + 1 ? resArr : resArr.slice(0, resPos + 1);
  578. }
  579. };
  580. TextEncoder.prototype.toString = function() {
  581. return "[object TextEncoder]";
  582. };
  583. try {
  584. Object.defineProperty(TextEncoder.prototype,"encoding", {
  585. get:function() {
  586. if (Object.prototype.isPrototypeOf.call(TextEncoder.prototype, this)) {
  587. return"utf-8";
  588. }
  589. else {
  590. throw TypeError("Illegal invocation");
  591. }
  592. }
  593. });
  594. }
  595. catch (e) {
  596. TextEncoder.prototype.encoding = "utf-8";
  597. }
  598. if (typeof Symbol !== "undefined") {
  599. TextEncoder.prototype[Symbol.toStringTag] = "TextEncoder";
  600. }
  601. }
  602. if (typeof URLSearchParams === 'undefined') {
  603. URLSearchParams = function URLSearchParams(search) {
  604. const decode = (str) => {
  605. return str.replace(/[ +]/g, '%20').replace(/(%[a-f0-9]{2})+/ig, (match) => { return decodeURIComponent(match); });
  606. };
  607. this._dict = {};
  608. if (typeof search === 'string') {
  609. search = search.indexOf('?') === 0 ? search.substring(1) : search;
  610. const properties = search.split('&');
  611. for (const property of properties) {
  612. const index = property.indexOf('=');
  613. const name = (index > -1) ? decode(property.substring(0, index)) : decode(property);
  614. const value = (index > -1) ? decode(property.substring(index + 1)) : '';
  615. if (!Object.prototype.hasOwnProperty.call(this._dict, name)) {
  616. this._dict[name] = [];
  617. }
  618. this._dict[name].push(value);
  619. }
  620. }
  621. };
  622. URLSearchParams.prototype.get = function(name) {
  623. return Object.prototype.hasOwnProperty.call(this._dict, name) ? this._dict[name][0] : null;
  624. };
  625. }
  626. if (!HTMLCanvasElement.prototype.toBlob) {
  627. HTMLCanvasElement.prototype.toBlob = function(callback, type, quality) {
  628. const canvas = this;
  629. setTimeout(function() {
  630. const data = atob(canvas.toDataURL(type, quality).split(',')[1]);
  631. const length = data.length;
  632. const buffer = new Uint8Array(length);
  633. for (let i = 0; i < length; i++) {
  634. buffer[i] = data.charCodeAt(i);
  635. }
  636. callback(new Blob([ buffer ], { type: type || 'image/png' }));
  637. });
  638. };
  639. }
  640. host.Dropdown = class {
  641. constructor(document, button, dropdown) {
  642. this._document = document;
  643. this._dropdown = document.getElementById(dropdown);
  644. this._button = document.getElementById(button);
  645. this._items = [];
  646. this._apple = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
  647. this._acceleratorMap = {};
  648. window.addEventListener('keydown', (e) => {
  649. let code = e.keyCode;
  650. code |= ((e.ctrlKey && !this._apple) || (e.metaKey && this._apple)) ? 0x0400 : 0;
  651. code |= e.altKey ? 0x0200 : 0;
  652. code |= e.shiftKey ? 0x0100 : 0;
  653. if (code == 0x001b) { // Escape
  654. this.close();
  655. return;
  656. }
  657. const item = this._acceleratorMap[code.toString()];
  658. if (item) {
  659. item.click();
  660. e.preventDefault();
  661. }
  662. });
  663. this._document.body.addEventListener('click', (e) => {
  664. if (!this._button.contains(e.target)) {
  665. this.close();
  666. }
  667. });
  668. }
  669. add(item) {
  670. const accelerator = item.accelerator;
  671. if (accelerator) {
  672. let cmdOrCtrl = false;
  673. let alt = false;
  674. let shift = false;
  675. let key = '';
  676. for (const part of item.accelerator.split('+')) {
  677. switch (part) {
  678. case 'CmdOrCtrl': cmdOrCtrl = true; break;
  679. case 'Alt': alt = true; break;
  680. case 'Shift': shift = true; break;
  681. default: key = part; break;
  682. }
  683. }
  684. if (key !== '') {
  685. item.accelerator = {};
  686. item.accelerator.text = '';
  687. if (this._apple) {
  688. item.accelerator.text += alt ? '&#x2325;' : '';
  689. item.accelerator.text += shift ? '&#x21e7;' : '';
  690. item.accelerator.text += cmdOrCtrl ? '&#x2318;' : '';
  691. const keyTable = { 'Enter': '&#x23ce;', 'Up': '&#x2191;', 'Down': '&#x2193;', 'Backspace': '&#x232B;' };
  692. item.accelerator.text += keyTable[key] ? keyTable[key] : key;
  693. }
  694. else {
  695. const list = [];
  696. if (cmdOrCtrl) {
  697. list.push('Ctrl');
  698. }
  699. if (alt) {
  700. list.push('Alt');
  701. }
  702. if (shift) {
  703. list.push('Shift');
  704. }
  705. list.push(key);
  706. item.accelerator.text = list.join('+');
  707. }
  708. let code = 0;
  709. switch (key) {
  710. case 'Backspace': code = 0x08; break;
  711. case 'Enter': code = 0x0D; break;
  712. case 'Up': code = 0x26; break;
  713. case 'Down': code = 0x28; break;
  714. default: code = key.charCodeAt(0); break;
  715. }
  716. code |= cmdOrCtrl ? 0x0400 : 0;
  717. code |= alt ? 0x0200 : 0;
  718. code |= shift ? 0x0100 : 0;
  719. this._acceleratorMap[code.toString()] = item;
  720. }
  721. }
  722. this._items.push(item);
  723. }
  724. toggle() {
  725. if (this._dropdown.style.display === 'block') {
  726. this.close();
  727. return;
  728. }
  729. while (this._dropdown.lastChild) {
  730. this._dropdown.removeChild(this._dropdown.lastChild);
  731. }
  732. for (const item of this._items) {
  733. if (Object.keys(item).length > 0) {
  734. const button = this._document.createElement('button');
  735. button.innerText = (typeof item.label == 'function') ? item.label() : item.label;
  736. button.addEventListener('click', () => {
  737. this.close();
  738. setTimeout(() => {
  739. item.click();
  740. }, 10);
  741. });
  742. this._dropdown.appendChild(button);
  743. if (item.accelerator) {
  744. const accelerator = this._document.createElement('span');
  745. accelerator.style.float = 'right';
  746. accelerator.innerHTML = item.accelerator.text;
  747. button.appendChild(accelerator);
  748. }
  749. }
  750. else {
  751. const separator = this._document.createElement('div');
  752. separator.setAttribute('class', 'separator');
  753. this._dropdown.appendChild(separator);
  754. }
  755. }
  756. this._dropdown.style.display = 'block';
  757. }
  758. close() {
  759. this._dropdown.style.display = 'none';
  760. }
  761. };
  762. host.BrowserHost.BrowserFileContext = class {
  763. constructor(file, blobs) {
  764. this._file = file;
  765. this._blobs = {};
  766. for (const blob of blobs) {
  767. this._blobs[blob.name] = blob;
  768. }
  769. }
  770. get identifier() {
  771. return this._file.name;
  772. }
  773. get buffer() {
  774. return this._buffer;
  775. }
  776. open() {
  777. return this.request(this._file.name, null).then((data) => {
  778. this._buffer = data;
  779. });
  780. }
  781. request(file, encoding) {
  782. const blob = this._blobs[file];
  783. if (!blob) {
  784. return Promise.reject(new Error("File not found '" + file + "'."));
  785. }
  786. return new Promise((resolve, reject) => {
  787. const reader = new FileReader();
  788. reader.onload = (e) => {
  789. resolve(encoding ? e.target.result : new Uint8Array(e.target.result));
  790. };
  791. reader.onerror = (e) => {
  792. e = e || window.event;
  793. let message = '';
  794. switch(e.target.error.code) {
  795. case e.target.error.NOT_FOUND_ERR:
  796. message = "File not found '" + file + "'.";
  797. break;
  798. case e.target.error.NOT_READABLE_ERR:
  799. message = "File not readable '" + file + "'.";
  800. break;
  801. case e.target.error.SECURITY_ERR:
  802. message = "File access denied '" + file + "'.";
  803. break;
  804. default:
  805. message = "File read '" + e.target.error.code.toString() + "' error '" + file + "'.";
  806. break;
  807. }
  808. reject(new Error(message));
  809. };
  810. if (encoding === 'utf-8') {
  811. reader.readAsText(blob, encoding);
  812. }
  813. else {
  814. reader.readAsArrayBuffer(blob);
  815. }
  816. });
  817. }
  818. };
  819. host.BrowserHost.BrowserContext = class {
  820. constructor(host, url, identifier, buffer) {
  821. this._host = host;
  822. this._buffer = buffer;
  823. if (identifier) {
  824. this._identifier = identifier;
  825. this._base = url;
  826. if (this._base.endsWith('/')) {
  827. this._base.substring(0, this._base.length - 1);
  828. }
  829. }
  830. else {
  831. const parts = url.split('?')[0].split('/');
  832. this._identifier = parts.pop();
  833. this._base = parts.join('/');
  834. }
  835. }
  836. request(file, encoding) {
  837. return this._host.request(this._base, file, encoding);
  838. }
  839. get identifier() {
  840. return this._identifier;
  841. }
  842. get buffer() {
  843. return this._buffer;
  844. }
  845. };
  846. window.addEventListener('load', () => {
  847. window.__view__ = new view.View(new host.BrowserHost());
  848. });