You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1068 lines
38 KiB
JavaScript
1068 lines
38 KiB
JavaScript
|
|
/* eslint "no-global-assign": ["error", {"exceptions": [ "TextDecoder", "TextEncoder", "URLSearchParams" ] } ] */
|
|
/* global view */
|
|
|
|
var host = {};
|
|
|
|
host.BrowserHost = class {
|
|
|
|
constructor() {
|
|
this._document = window.document;
|
|
this._window = window;
|
|
this._navigator = navigator;
|
|
if (this._window.location.hostname.endsWith('.github.io')) {
|
|
this._window.location.replace('https://netron.app');
|
|
}
|
|
this._window.eval = () => {
|
|
throw new Error('window.eval() not supported.');
|
|
};
|
|
this._meta = {};
|
|
for (const element of Array.from(this._document.getElementsByTagName('meta'))) {
|
|
if (element.content) {
|
|
this._meta[element.name] = this._meta[element.name] || [];
|
|
this._meta[element.name].push(element.content);
|
|
}
|
|
}
|
|
this._type = this._meta.type ? this._meta.type[0] : 'Browser';
|
|
this._version = this._meta.version ? this._meta.version[0] : null;
|
|
this._telemetry = this._version && this._version !== '0.0.0';
|
|
this._environment = new Map();
|
|
this._environment.set('zoom', 'scroll');
|
|
// this._environment.set('zoom', 'drag');
|
|
}
|
|
|
|
get window() {
|
|
return this._window;
|
|
}
|
|
|
|
get document() {
|
|
return this._document;
|
|
}
|
|
|
|
get version() {
|
|
return this._version;
|
|
}
|
|
|
|
get type() {
|
|
return this._type;
|
|
}
|
|
|
|
get agent() {
|
|
const userAgent = this._navigator.userAgent.toLowerCase();
|
|
if (userAgent.indexOf('safari') !== -1 && userAgent.indexOf('chrome') === -1) {
|
|
return 'safari';
|
|
}
|
|
return 'any';
|
|
}
|
|
|
|
initialize(view) {
|
|
this._view = view;
|
|
return new Promise((resolve /*, reject */) => {
|
|
const accept = () => {
|
|
if (this._telemetry) {
|
|
const script = this.document.createElement('script');
|
|
script.setAttribute('type', 'text/javascript');
|
|
script.setAttribute('src', 'https://www.google-analytics.com/analytics.js');
|
|
script.onload = () => {
|
|
if (this.window.ga) {
|
|
this.window.ga.l = 1 * new Date();
|
|
this.window.ga('create', 'UA-54146-13', 'auto');
|
|
this.window.ga('set', 'anonymizeIp', true);
|
|
}
|
|
resolve();
|
|
};
|
|
script.onerror = () => {
|
|
resolve();
|
|
};
|
|
this.document.body.appendChild(script);
|
|
}
|
|
else {
|
|
resolve();
|
|
}
|
|
};
|
|
const request = () => {
|
|
this._view.show('welcome consent');
|
|
const acceptButton = this.document.getElementById('consent-accept-button');
|
|
if (acceptButton) {
|
|
acceptButton.addEventListener('click', () => {
|
|
this._setCookie('consent', 'yes', 30);
|
|
accept();
|
|
});
|
|
}
|
|
};
|
|
if (this._getCookie('consent')) {
|
|
accept();
|
|
}
|
|
else {
|
|
this._request('https://ipinfo.io/json', { 'Content-Type': 'application/json' }, 'utf-8', 2000).then((text) => {
|
|
try {
|
|
const json = JSON.parse(text);
|
|
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'];
|
|
if (json && json.country && !countries.indexOf(json.country) !== -1) {
|
|
this._setCookie('consent', Date.now(), 30);
|
|
accept();
|
|
}
|
|
else {
|
|
request();
|
|
}
|
|
}
|
|
catch (err) {
|
|
request();
|
|
}
|
|
}).catch(() => {
|
|
request();
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
start() {
|
|
this.window.addEventListener('error', (e) => {
|
|
this.exception(e.error, true);
|
|
});
|
|
|
|
const params = new URLSearchParams(this.window.location.search);
|
|
this._environment.set('zoom', params.has('zoom') ? params.get('zoom') : this._environment.get('zoom'));
|
|
|
|
this._menu = new host.Dropdown(this, 'menu-button', 'menu-dropdown');
|
|
this._menu.add({
|
|
label: 'Properties...',
|
|
accelerator: 'CmdOrCtrl+Enter',
|
|
click: () => this._view.showModelProperties()
|
|
});
|
|
this._menu.add({});
|
|
this._menu.add({
|
|
label: 'Find...',
|
|
accelerator: 'CmdOrCtrl+F',
|
|
click: () => this._view.find()
|
|
});
|
|
this._menu.add({});
|
|
this._menu.add({
|
|
label: () => this._view.options.attributes ? 'Hide Attributes' : 'Show Attributes',
|
|
accelerator: 'CmdOrCtrl+D',
|
|
click: () => this._view.toggle('attributes')
|
|
});
|
|
this._menu.add({
|
|
label: () => this._view.options.initializers ? 'Hide Initializers' : 'Show Initializers',
|
|
accelerator: 'CmdOrCtrl+I',
|
|
click: () => this._view.toggle('initializers')
|
|
});
|
|
this._menu.add({
|
|
label: () => this._view.options.names ? 'Hide Names' : 'Show Names',
|
|
accelerator: 'CmdOrCtrl+U',
|
|
click: () => this._view.toggle('names')
|
|
});
|
|
this._menu.add({
|
|
label: () => this._view.options.direction === 'vertical' ? 'Show Horizontal' : 'Show Vertical',
|
|
accelerator: 'CmdOrCtrl+K',
|
|
click: () => this._view.toggle('direction')
|
|
});
|
|
this._menu.add({
|
|
label: () => this._view.options.mousewheel === 'scroll' ? 'Mouse Wheel: Zoom' : 'Mouse Wheel: Scroll',
|
|
accelerator: 'CmdOrCtrl+M',
|
|
click: () => this._view.toggle('mousewheel')
|
|
});
|
|
this._menu.add({});
|
|
this._menu.add({
|
|
label: 'Zoom In',
|
|
accelerator: 'Shift+Up',
|
|
click: () => this.document.getElementById('zoom-in-button').click()
|
|
});
|
|
this._menu.add({
|
|
label: 'Zoom Out',
|
|
accelerator: 'Shift+Down',
|
|
click: () => this.document.getElementById('zoom-out-button').click()
|
|
});
|
|
this._menu.add({
|
|
label: 'Actual Size',
|
|
accelerator: 'Shift+Backspace',
|
|
click: () => this._view.resetZoom()
|
|
});
|
|
this._menu.add({});
|
|
this._menu.add({
|
|
label: 'Export as PNG',
|
|
accelerator: 'CmdOrCtrl+Shift+E',
|
|
click: () => this._view.export(document.title + '.png')
|
|
});
|
|
this._menu.add({
|
|
label: 'Export as SVG',
|
|
accelerator: 'CmdOrCtrl+Alt+E',
|
|
click: () => this._view.export(document.title + '.svg')
|
|
});
|
|
this.document.getElementById('menu-button').addEventListener('click', (e) => {
|
|
this._menu.toggle();
|
|
e.preventDefault();
|
|
});
|
|
this._menu.add({});
|
|
this._menu.add({
|
|
label: 'About ' + this.document.title,
|
|
click: () => this._about()
|
|
});
|
|
|
|
this.document.getElementById('version').innerText = this.version;
|
|
|
|
if (this._meta.file) {
|
|
const url = this._meta.file[0];
|
|
if (this._view.accept(url)) {
|
|
this._openModel(this._url(url), null);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const url = params.get('url');
|
|
if (url) {
|
|
const identifier = params.get('identifier') || null;
|
|
const location = url.replace(new RegExp('^https://github.com/([\\w]*/[\\w]*)/blob/([\\w/_.]*)(\\?raw=true)?$'), 'https://raw.githubusercontent.com/$1/$2');
|
|
if (this._view.accept(identifier || location)) {
|
|
this._openModel(location, identifier);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const gist = params.get('gist');
|
|
if (gist) {
|
|
this._openGist(gist);
|
|
return;
|
|
}
|
|
|
|
const openFileButton = this.document.getElementById('open-file-button');
|
|
const openFileDialog = this.document.getElementById('open-file-dialog');
|
|
if (openFileButton && openFileDialog) {
|
|
openFileButton.addEventListener('click', () => {
|
|
openFileDialog.value = '';
|
|
openFileDialog.click();
|
|
});
|
|
openFileDialog.addEventListener('change', (e) => {
|
|
if (e.target && e.target.files && e.target.files.length > 0) {
|
|
const files = Array.from(e.target.files);
|
|
const file = files.find((file) => this._view.accept(file.name));
|
|
// console.log(files);
|
|
if (file) {
|
|
this._open(file, files);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
const githubButton = this.document.getElementById('github-button');
|
|
const githubLink = this.document.getElementById('logo-github');
|
|
if (githubButton && githubLink) {
|
|
githubButton.style.opacity = 1;
|
|
githubButton.addEventListener('click', () => {
|
|
this.openURL(githubLink.href);
|
|
});
|
|
}
|
|
this.document.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
});
|
|
this.document.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
});
|
|
this.document.body.addEventListener('drop', (e) => {
|
|
e.preventDefault();
|
|
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
|
const files = Array.from(e.dataTransfer.files);
|
|
const file = files.find((file) => this._view.accept(file.name));
|
|
if (file) {
|
|
this._open(file, files);
|
|
}
|
|
}
|
|
});
|
|
|
|
this._view.show('welcome');
|
|
}
|
|
|
|
environment(name) {
|
|
return this._environment.get(name);
|
|
}
|
|
|
|
error(message, detail) {
|
|
alert((message == 'Error' ? '' : message + ' ') + detail);
|
|
}
|
|
|
|
confirm(message, detail) {
|
|
return confirm(message + ' ' + detail);
|
|
}
|
|
|
|
require(id) {
|
|
const url = this._url(id + '.js');
|
|
this.window.__modules__ = this.window.__modules__ || {};
|
|
// console.log(this.window.__modules__)
|
|
// console.log(url) // file:///C:/Users/ZhangGe/Desktop/netron/source/./onnx.js
|
|
// console.log(this.window.__modules__[url]) // undefined
|
|
if (this.window.__modules__[url]) {
|
|
return Promise.resolve(this.window.__exports__[url]);
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
// console.log('here')
|
|
this.window.module = { exports: {} };
|
|
const script = document.createElement('script');
|
|
script.setAttribute('id', id);
|
|
script.setAttribute('type', 'text/javascript');
|
|
// script.setAttribute('src', url);
|
|
script.setAttribute('src', "{{url_for('static', filename='onnx.js')}}");
|
|
script.onload = (e) => {
|
|
if (this.window.module && this.window.module.exports) {
|
|
const exports = this.window.module.exports;
|
|
delete this.window.module;
|
|
this.window.__modules__[id] = exports;
|
|
// console.log('here')
|
|
// console.log(exports)
|
|
resolve(exports);
|
|
}
|
|
else {
|
|
reject(new Error('The script \'' + e.target.src + '\' has no exports.'));
|
|
}
|
|
};
|
|
script.onerror = (e) => {
|
|
delete this.window.module;
|
|
reject(new Error('The script \'' + e.target.src + '\' failed to load.'));
|
|
};
|
|
this.document.head.appendChild(script);
|
|
});
|
|
}
|
|
|
|
save(name, extension, defaultPath, callback) {
|
|
callback(defaultPath + '.' + extension);
|
|
}
|
|
|
|
export(file, blob) {
|
|
const element = this.document.createElement('a');
|
|
element.download = file;
|
|
element.href = URL.createObjectURL(blob);
|
|
this.document.body.appendChild(element);
|
|
element.click();
|
|
this.document.body.removeChild(element);
|
|
}
|
|
|
|
request(file, encoding, base) {
|
|
const url = base ? (base + '/' + file) : this._url(file);
|
|
return this._request(url, null, encoding);
|
|
}
|
|
|
|
openURL(url) {
|
|
this.window.location = url;
|
|
}
|
|
|
|
exception(error, fatal) {
|
|
if (this._telemetry && this.window.ga && error.telemetry !== false) {
|
|
const description = [];
|
|
description.push((error && error.name ? (error.name + ': ') : '') + (error && error.message ? error.message : '(null)'));
|
|
if (error.stack) {
|
|
const match = error.stack.match(/\n {4}at (.*)\((.*)\)/);
|
|
if (match) {
|
|
description.push(match[1] + '(' + match[2].split('/').pop() + ')');
|
|
}
|
|
else {
|
|
description.push(error.stack.split('\n').shift());
|
|
}
|
|
}
|
|
this.window.ga('send', 'exception', {
|
|
exDescription: description.join(' @ '),
|
|
exFatal: fatal,
|
|
appName: this.type,
|
|
appVersion: this.version
|
|
});
|
|
}
|
|
}
|
|
|
|
screen(name) {
|
|
if (this._telemetry && this.window.ga) {
|
|
this.window.ga('send', 'screenview', {
|
|
screenName: name,
|
|
appName: this.type,
|
|
appVersion: this.version
|
|
});
|
|
}
|
|
}
|
|
|
|
event(category, action, label, value) {
|
|
if (this._telemetry && this.window.ga) {
|
|
this.window.ga('send', 'event', {
|
|
eventCategory: category,
|
|
eventAction: action,
|
|
eventLabel: label,
|
|
eventValue: value,
|
|
appName: this.type,
|
|
appVersion: this.version
|
|
});
|
|
}
|
|
}
|
|
|
|
_request(url, headers, encoding, timeout) {
|
|
return new Promise((resolve, reject) => {
|
|
const request = new XMLHttpRequest();
|
|
if (!encoding) {
|
|
request.responseType = 'arraybuffer';
|
|
}
|
|
if (timeout) {
|
|
request.timeout = timeout;
|
|
}
|
|
const error = (status) => {
|
|
const err = new Error("The web request failed with status code " + status + " at '" + url + "'.");
|
|
err.type = 'error';
|
|
err.url = url;
|
|
return err;
|
|
};
|
|
request.onload = () => {
|
|
if (request.status == 200) {
|
|
if (request.responseType == 'arraybuffer') {
|
|
resolve(new host.BrowserHost.BinaryStream(new Uint8Array(request.response)));
|
|
}
|
|
else {
|
|
resolve(request.responseText);
|
|
}
|
|
}
|
|
else {
|
|
reject(error(request.status));
|
|
}
|
|
};
|
|
request.onerror = (e) => {
|
|
const err = error(request.status);
|
|
err.type = e.type;
|
|
reject(err);
|
|
};
|
|
request.ontimeout = () => {
|
|
request.abort();
|
|
const err = new Error("The web request timed out in '" + url + "'.");
|
|
err.type = 'timeout';
|
|
err.url = url;
|
|
reject(err);
|
|
};
|
|
request.open('GET', url, true);
|
|
if (headers) {
|
|
for (const name of Object.keys(headers)) {
|
|
request.setRequestHeader(name, headers[name]);
|
|
}
|
|
}
|
|
request.send();
|
|
});
|
|
}
|
|
|
|
_url(file) {
|
|
let url = file;
|
|
if (this.window && this.window.location && this.window.location.href) {
|
|
let location = this.window.location.href.split('?').shift();
|
|
if (location.endsWith('.html')) {
|
|
location = location.split('/').slice(0, -1).join('/');
|
|
}
|
|
if (location.endsWith('/')) {
|
|
location = location.slice(0, -1);
|
|
}
|
|
url = location + '/' + (file.startsWith('/') ? file.substring(1) : file);
|
|
}
|
|
return url;
|
|
}
|
|
|
|
_openModel(url, identifier) {
|
|
url = url + ((/\?/).test(url) ? '&' : '?') + 'cb=' + (new Date()).getTime();
|
|
this._view.show('welcome spinner');
|
|
this._request(url).then((buffer) => {
|
|
const context = new host.BrowserHost.BrowserContext(this, url, identifier, buffer);
|
|
this._view.open(context).then(() => {
|
|
this.document.title = identifier || context.identifier;
|
|
}).catch((err) => {
|
|
if (err) {
|
|
this._view.error(err, null, 'welcome');
|
|
}
|
|
});
|
|
}).catch((err) => {
|
|
this.error('Model load request failed.', err.message);
|
|
this._view.show('welcome');
|
|
});
|
|
}
|
|
|
|
_open(file, files) {
|
|
this._view.show('welcome spinner');
|
|
const context = new host.BrowserHost.BrowserFileContext(this, file, files);
|
|
// console.log(context);
|
|
// console.log(model);
|
|
context.open().then(() => {
|
|
// console.log(context);
|
|
// console.log(model); // model is not defined
|
|
return this._view.open(context).then((model) => {
|
|
// console.log("_open() in index.js is called");
|
|
// console.log(model);
|
|
this._view.show(null);
|
|
this.document.title = files[0].name;
|
|
return model;
|
|
});
|
|
}).catch((error) => {
|
|
this._view.error(error, null, null);
|
|
});
|
|
}
|
|
|
|
_openGist(gist) {
|
|
this._view.show('welcome spinner');
|
|
const url = 'https://api.github.com/gists/' + gist;
|
|
this._request(url, { 'Content-Type': 'application/json' }, 'utf-8').then((text) => {
|
|
const json = JSON.parse(text);
|
|
if (json.message) {
|
|
this.error('Error while loading Gist.', json.message);
|
|
return;
|
|
}
|
|
const key = Object.keys(json.files).find((key) => this._view.accept(json.files[key].filename));
|
|
if (!key) {
|
|
this.error('Error while loading Gist.', 'Gist does not contain a model file.');
|
|
return;
|
|
}
|
|
const file = json.files[key];
|
|
const identifier = file.filename;
|
|
const encoder = new TextEncoder();
|
|
const buffer = encoder.encode(file.content);
|
|
const context = new host.BrowserHost.BrowserContext(this, '', identifier, buffer);
|
|
this._view.open(context).then(() => {
|
|
this.document.title = identifier;
|
|
}).catch((error) => {
|
|
if (error) {
|
|
this._view.show(error.name, error, 'welcome');
|
|
}
|
|
});
|
|
}).catch((err) => {
|
|
this._view.show('Model load request failed.', err, 'welcome');
|
|
});
|
|
}
|
|
|
|
_setCookie(name, value, days) {
|
|
const date = new Date();
|
|
date.setTime(date.getTime() + ((typeof days !== "number" ? 365 : days) * 24 * 60 * 60 * 1000));
|
|
document.cookie = name + "=" + value + ";path=/;expires=" + date.toUTCString();
|
|
}
|
|
|
|
_getCookie(name) {
|
|
const cookie = '; ' + document.cookie;
|
|
const parts = cookie.split('; ' + name + '=');
|
|
return parts.length < 2 ? undefined : parts.pop().split(';').shift();
|
|
}
|
|
|
|
_about() {
|
|
const self = this;
|
|
const eventHandler = () => {
|
|
this.window.removeEventListener('keydown', eventHandler);
|
|
self.document.body.removeEventListener('click', eventHandler);
|
|
self._view.show('default');
|
|
};
|
|
this.window.addEventListener('keydown', eventHandler);
|
|
this.document.body.addEventListener('click', eventHandler);
|
|
this._view.show('about');
|
|
}
|
|
};
|
|
|
|
host.Dropdown = class {
|
|
|
|
constructor(host, button, dropdown) {
|
|
this._host = host;
|
|
this._dropdown = this._host.document.getElementById(dropdown);
|
|
this._button = this._host.document.getElementById(button);
|
|
this._items = [];
|
|
this._apple = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
|
|
this._acceleratorMap = {};
|
|
this._host.window.addEventListener('keydown', (e) => {
|
|
let code = e.keyCode;
|
|
code |= ((e.ctrlKey && !this._apple) || (e.metaKey && this._apple)) ? 0x0400 : 0;
|
|
code |= e.altKey ? 0x0200 : 0;
|
|
code |= e.shiftKey ? 0x0100 : 0;
|
|
if (code == 0x001b) { // Escape
|
|
this.close();
|
|
return;
|
|
}
|
|
const item = this._acceleratorMap[code.toString()];
|
|
if (item) {
|
|
item.click();
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
this._host.document.body.addEventListener('click', (e) => {
|
|
if (!this._button.contains(e.target)) {
|
|
this.close();
|
|
}
|
|
});
|
|
}
|
|
|
|
add(item) {
|
|
const accelerator = item.accelerator;
|
|
if (accelerator) {
|
|
let cmdOrCtrl = false;
|
|
let alt = false;
|
|
let shift = false;
|
|
let key = '';
|
|
for (const part of item.accelerator.split('+')) {
|
|
switch (part) {
|
|
case 'CmdOrCtrl': cmdOrCtrl = true; break;
|
|
case 'Alt': alt = true; break;
|
|
case 'Shift': shift = true; break;
|
|
default: key = part; break;
|
|
}
|
|
}
|
|
if (key !== '') {
|
|
item.accelerator = {};
|
|
item.accelerator.text = '';
|
|
if (this._apple) {
|
|
item.accelerator.text += alt ? '⌥' : '';
|
|
item.accelerator.text += shift ? '⇧' : '';
|
|
item.accelerator.text += cmdOrCtrl ? '⌘' : '';
|
|
const keyTable = { 'Enter': '⏎', 'Up': '↑', 'Down': '↓', 'Backspace': '⌫' };
|
|
item.accelerator.text += keyTable[key] ? keyTable[key] : key;
|
|
}
|
|
else {
|
|
const list = [];
|
|
if (cmdOrCtrl) {
|
|
list.push('Ctrl');
|
|
}
|
|
if (alt) {
|
|
list.push('Alt');
|
|
}
|
|
if (shift) {
|
|
list.push('Shift');
|
|
}
|
|
list.push(key);
|
|
item.accelerator.text = list.join('+');
|
|
}
|
|
let code = 0;
|
|
switch (key) {
|
|
case 'Backspace': code = 0x08; break;
|
|
case 'Enter': code = 0x0D; break;
|
|
case 'Up': code = 0x26; break;
|
|
case 'Down': code = 0x28; break;
|
|
default: code = key.charCodeAt(0); break;
|
|
}
|
|
code |= cmdOrCtrl ? 0x0400 : 0;
|
|
code |= alt ? 0x0200 : 0;
|
|
code |= shift ? 0x0100 : 0;
|
|
this._acceleratorMap[code.toString()] = item;
|
|
}
|
|
}
|
|
this._items.push(item);
|
|
}
|
|
|
|
toggle() {
|
|
|
|
if (this._dropdown.style.display === 'block') {
|
|
this.close();
|
|
return;
|
|
}
|
|
|
|
while (this._dropdown.lastChild) {
|
|
this._dropdown.removeChild(this._dropdown.lastChild);
|
|
}
|
|
|
|
for (const item of this._items) {
|
|
if (Object.keys(item).length > 0) {
|
|
const button = this._host.document.createElement('button');
|
|
button.innerText = (typeof item.label == 'function') ? item.label() : item.label;
|
|
button.addEventListener('click', () => {
|
|
this.close();
|
|
setTimeout(() => {
|
|
item.click();
|
|
}, 10);
|
|
});
|
|
this._dropdown.appendChild(button);
|
|
if (item.accelerator) {
|
|
const accelerator = this._host.document.createElement('span');
|
|
accelerator.style.float = 'right';
|
|
accelerator.innerHTML = item.accelerator.text;
|
|
button.appendChild(accelerator);
|
|
}
|
|
}
|
|
else {
|
|
const separator = this._host.document.createElement('div');
|
|
separator.setAttribute('class', 'separator');
|
|
this._dropdown.appendChild(separator);
|
|
}
|
|
}
|
|
|
|
this._dropdown.style.display = 'block';
|
|
}
|
|
|
|
close() {
|
|
this._dropdown.style.display = 'none';
|
|
}
|
|
};
|
|
|
|
host.BrowserHost.BinaryStream = class {
|
|
|
|
constructor(buffer) {
|
|
this._buffer = buffer;
|
|
this._length = buffer.length;
|
|
this._position = 0;
|
|
}
|
|
|
|
get position() {
|
|
return this._position;
|
|
}
|
|
|
|
get length() {
|
|
return this._length;
|
|
}
|
|
|
|
stream(length) {
|
|
const buffer = this.read(length);
|
|
return new host.BrowserHost.BinaryStream(buffer.slice(0));
|
|
}
|
|
|
|
seek(position) {
|
|
this._position = position >= 0 ? position : this._length + position;
|
|
}
|
|
|
|
skip(offset) {
|
|
this._position += offset;
|
|
}
|
|
|
|
peek(length) {
|
|
if (this._position === 0 && length === undefined) {
|
|
return this._buffer;
|
|
}
|
|
const position = this._position;
|
|
this.skip(length !== undefined ? length : this._length - this._position);
|
|
const end = this._position;
|
|
this.seek(position);
|
|
return this._buffer.subarray(position, end);
|
|
}
|
|
|
|
read(length) {
|
|
if (this._position === 0 && length === undefined) {
|
|
this._position = this._length;
|
|
return this._buffer;
|
|
}
|
|
const position = this._position;
|
|
this.skip(length !== undefined ? length : this._length - this._position);
|
|
return this._buffer.subarray(position, this._position);
|
|
}
|
|
|
|
byte() {
|
|
const position = this._position;
|
|
this.skip(1);
|
|
return this._buffer[position];
|
|
}
|
|
};
|
|
|
|
host.BrowserHost.BrowserFileContext = class {
|
|
|
|
constructor(host, file, blobs) {
|
|
this._host = host;
|
|
this._file = file;
|
|
this._blobs = {};
|
|
for (const blob of blobs) {
|
|
this._blobs[blob.name] = blob;
|
|
}
|
|
}
|
|
|
|
get identifier() {
|
|
return this._file.name;
|
|
}
|
|
|
|
get stream() {
|
|
return this._stream;
|
|
}
|
|
|
|
request(file, encoding, base) {
|
|
// console.log(this)
|
|
// console.log(file);
|
|
// console.log(encoding);
|
|
// console.log(base);
|
|
|
|
if (base !== undefined) {
|
|
return this._host.request(file, encoding, base);
|
|
}
|
|
const blob = this._blobs[file];
|
|
// console.log(blob);
|
|
|
|
if (!blob) {
|
|
return Promise.reject(new Error("File not found '" + file + "'."));
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
resolve(encoding ? e.target.result : new host.BrowserHost.BinaryStream(new Uint8Array(e.target.result)));
|
|
};
|
|
reader.onerror = (e) => {
|
|
e = e || this.window.event;
|
|
let message = '';
|
|
const error = e.target.error;
|
|
switch(error.code) {
|
|
case error.NOT_FOUND_ERR:
|
|
message = "File not found '" + file + "'.";
|
|
break;
|
|
case error.NOT_READABLE_ERR:
|
|
message = "File not readable '" + file + "'.";
|
|
break;
|
|
case error.SECURITY_ERR:
|
|
message = "File access denied '" + file + "'.";
|
|
break;
|
|
default:
|
|
message = error.message ? error.message : "File read '" + error.code.toString() + "' error '" + file + "'.";
|
|
break;
|
|
}
|
|
reject(new Error(message));
|
|
};
|
|
if (encoding === 'utf-8') {
|
|
reader.readAsText(blob, encoding);
|
|
}
|
|
else {
|
|
reader.readAsArrayBuffer(blob);
|
|
}
|
|
});
|
|
}
|
|
|
|
require(id) {
|
|
return this._host.require(id);
|
|
}
|
|
|
|
exception(error, fatal) {
|
|
this._host.exception(error, fatal);
|
|
}
|
|
|
|
open() {
|
|
return this.request(this._file.name, null).then((stream) => {
|
|
this._stream = stream;
|
|
// console.log(this)
|
|
});
|
|
}
|
|
};
|
|
|
|
host.BrowserHost.BrowserContext = class {
|
|
|
|
constructor(host, url, identifier, stream) {
|
|
this._host = host;
|
|
this._stream = stream;
|
|
if (identifier) {
|
|
this._identifier = identifier;
|
|
this._base = url;
|
|
if (this._base.endsWith('/')) {
|
|
this._base.substring(0, this._base.length - 1);
|
|
}
|
|
}
|
|
else {
|
|
const parts = url.split('?')[0].split('/');
|
|
this._identifier = parts.pop();
|
|
this._base = parts.join('/');
|
|
}
|
|
}
|
|
|
|
get identifier() {
|
|
return this._identifier;
|
|
}
|
|
|
|
get stream() {
|
|
return this._stream;
|
|
}
|
|
|
|
request(file, encoding, base) {
|
|
return this._host.request(file, encoding, base === undefined ? this._base : base);
|
|
}
|
|
|
|
require(id) {
|
|
return this._host.require(id);
|
|
}
|
|
|
|
exception(error, fatal) {
|
|
this._host.exception(error, fatal);
|
|
}
|
|
};
|
|
|
|
if (typeof TextDecoder === "undefined") {
|
|
TextDecoder = function TextDecoder(encoding) {
|
|
this._encoding = encoding;
|
|
};
|
|
TextDecoder.prototype.decode = function decode(buffer) {
|
|
let result = '';
|
|
const length = buffer.length;
|
|
let i = 0;
|
|
switch (this._encoding) {
|
|
case 'utf-8':
|
|
while (i < length) {
|
|
const c = buffer[i++];
|
|
switch(c >> 4) {
|
|
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: {
|
|
result += String.fromCharCode(c);
|
|
break;
|
|
}
|
|
case 12: case 13: {
|
|
const c2 = buffer[i++];
|
|
result += String.fromCharCode(((c & 0x1F) << 6) | (c2 & 0x3F));
|
|
break;
|
|
}
|
|
case 14: {
|
|
const c2 = buffer[i++];
|
|
const c3 = buffer[i++];
|
|
result += String.fromCharCode(((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | ((c3 & 0x3F) << 0));
|
|
break;
|
|
}
|
|
case 15: {
|
|
const c2 = buffer[i++];
|
|
const c3 = buffer[i++];
|
|
const c4 = buffer[i++];
|
|
result += String.fromCodePoint(((c & 0x07) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F));
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 'ascii':
|
|
while (i < length) {
|
|
result += String.fromCharCode(buffer[i++]);
|
|
}
|
|
break;
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
|
|
if (typeof TextEncoder === 'undefined') {
|
|
TextEncoder = function TextEncoder() {
|
|
};
|
|
TextEncoder.prototype.encode = function encode(str) {
|
|
"use strict";
|
|
const length = str.length;
|
|
let resPos = -1;
|
|
const resArr = typeof Uint8Array === "undefined" ? new Array(length * 2) : new Uint8Array(length * 3);
|
|
for (let point = 0, nextcode = 0, i = 0; i !== length; ) {
|
|
point = str.charCodeAt(i);
|
|
i += 1;
|
|
if (point >= 0xD800 && point <= 0xDBFF) {
|
|
if (i === length) {
|
|
resArr[resPos += 1] = 0xef; resArr[resPos += 1] = 0xbf;
|
|
resArr[resPos += 1] = 0xbd; break;
|
|
}
|
|
nextcode = str.charCodeAt(i);
|
|
if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) {
|
|
point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000;
|
|
i += 1;
|
|
if (point > 0xffff) {
|
|
resArr[resPos += 1] = (0x1e<<3) | (point>>>18);
|
|
resArr[resPos += 1] = (0x2<<6) | ((point>>>12)&0x3f);
|
|
resArr[resPos += 1] = (0x2<<6) | ((point>>>6)&0x3f);
|
|
resArr[resPos += 1] = (0x2<<6) | (point&0x3f);
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
resArr[resPos += 1] = 0xef; resArr[resPos += 1] = 0xbf;
|
|
resArr[resPos += 1] = 0xbd; continue;
|
|
}
|
|
}
|
|
if (point <= 0x007f) {
|
|
resArr[resPos += 1] = (0x0<<7) | point;
|
|
}
|
|
else if (point <= 0x07ff) {
|
|
resArr[resPos += 1] = (0x6<<5) | (point>>>6);
|
|
resArr[resPos += 1] = (0x2<<6) | (point&0x3f);
|
|
}
|
|
else {
|
|
resArr[resPos += 1] = (0xe<<4) | (point>>>12);
|
|
resArr[resPos += 1] = (0x2<<6) | ((point>>>6)&0x3f);
|
|
resArr[resPos += 1] = (0x2<<6) | (point&0x3f);
|
|
}
|
|
}
|
|
if (typeof Uint8Array!=="undefined") {
|
|
return new Uint8Array(resArr.buffer.slice(0, resPos+1));
|
|
}
|
|
else {
|
|
return resArr.length === resPos + 1 ? resArr : resArr.slice(0, resPos + 1);
|
|
}
|
|
};
|
|
TextEncoder.prototype.toString = function() {
|
|
return "[object TextEncoder]";
|
|
};
|
|
try {
|
|
Object.defineProperty(TextEncoder.prototype,"encoding", {
|
|
get:function() {
|
|
if (Object.prototype.isPrototypeOf.call(TextEncoder.prototype, this)) {
|
|
return"utf-8";
|
|
}
|
|
else {
|
|
throw TypeError("Illegal invocation");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
catch (e) {
|
|
TextEncoder.prototype.encoding = "utf-8";
|
|
}
|
|
if (typeof Symbol !== "undefined") {
|
|
TextEncoder.prototype[Symbol.toStringTag] = "TextEncoder";
|
|
}
|
|
}
|
|
|
|
if (typeof URLSearchParams === 'undefined') {
|
|
URLSearchParams = function URLSearchParams(search) {
|
|
const decode = (str) => {
|
|
return str.replace(/[ +]/g, '%20').replace(/(%[a-f0-9]{2})+/ig, (match) => { return decodeURIComponent(match); });
|
|
};
|
|
this._dict = {};
|
|
if (typeof search === 'string') {
|
|
search = search.indexOf('?') === 0 ? search.substring(1) : search;
|
|
const properties = search.split('&');
|
|
for (const property of properties) {
|
|
const index = property.indexOf('=');
|
|
const name = (index > -1) ? decode(property.substring(0, index)) : decode(property);
|
|
const value = (index > -1) ? decode(property.substring(index + 1)) : '';
|
|
if (!Object.prototype.hasOwnProperty.call(this._dict, name)) {
|
|
this._dict[name] = [];
|
|
}
|
|
this._dict[name].push(value);
|
|
}
|
|
}
|
|
};
|
|
URLSearchParams.prototype.get = function(name) {
|
|
return Object.prototype.hasOwnProperty.call(this._dict, name) ? this._dict[name][0] : null;
|
|
};
|
|
}
|
|
|
|
if (!HTMLCanvasElement.prototype.toBlob) {
|
|
HTMLCanvasElement.prototype.toBlob = function(callback, type, quality) {
|
|
const canvas = this;
|
|
setTimeout(function() {
|
|
const data = atob(canvas.toDataURL(type, quality).split(',')[1]);
|
|
const length = data.length;
|
|
const buffer = new Uint8Array(length);
|
|
for (let i = 0; i < length; i++) {
|
|
buffer[i] = data.charCodeAt(i);
|
|
}
|
|
callback(new Blob([ buffer ], { type: type || 'image/png' }));
|
|
});
|
|
};
|
|
}
|
|
|
|
if (!('scrollBehavior' in window.document.documentElement.style)) {
|
|
const __scrollTo__ = Element.prototype.scrollTo;
|
|
Element.prototype.scrollTo = function(options) {
|
|
if (options === undefined) {
|
|
return;
|
|
}
|
|
if (options === null || typeof options !== 'object' || options.behavior === undefined || arguments[0].behavior === 'auto' || options.behavior === 'instant') {
|
|
if (__scrollTo__) {
|
|
__scrollTo__.apply(this, arguments);
|
|
}
|
|
return;
|
|
}
|
|
const now = () => {
|
|
return window.performance && window.performance.now ? window.performance.now() : Date.now();
|
|
};
|
|
const ease = (k) => {
|
|
return 0.5 * (1 - Math.cos(Math.PI * k));
|
|
};
|
|
const step = (context) => {
|
|
const value = ease(Math.min((now() - context.startTime) / 468, 1));
|
|
const x = context.startX + (context.x - context.startX) * value;
|
|
const y = context.startY + (context.y - context.startY) * value;
|
|
context.element.scrollLeft = x;
|
|
context.element.scrollTop = y;
|
|
if (x !== context.x || y !== context.y) {
|
|
window.requestAnimationFrame(step.bind(window, context));
|
|
}
|
|
};
|
|
const context = {
|
|
element: this,
|
|
x: typeof options.left === 'undefined' ? this.scrollLeft : ~~options.left,
|
|
y: typeof options.top === 'undefined' ? this.scrollTop : ~~options.top,
|
|
startX: this.scrollLeft,
|
|
startY: this.scrollTop,
|
|
startTime: now()
|
|
};
|
|
step(context);
|
|
};
|
|
}
|
|
|
|
window.addEventListener('load', () => {
|
|
window.__view__ = new view.View(new host.BrowserHost());
|
|
});
|