index.js 32 KB

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