index.js 32 KB

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