| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665 |
- import * as base from './base.js';
- import * as child_process from 'child_process';
- import * as electron from 'electron';
- import * as fs from 'fs';
- import * as http from 'http';
- import * as https from 'https';
- import * as node from './node.js';
- import * as os from 'os';
- import * as path from 'path';
- import * as url from 'url';
- import * as view from './view.js';
- const desktop = {};
- desktop.Host = class {
- constructor() {
- this._document = window.document;
- this._window = window;
- this._global = global;
- this._telemetry = new base.Telemetry(this._window);
- process.on('uncaughtException', (error) => {
- this.exception(error, true);
- this.message(error.message);
- });
- this._global.eval = () => {
- throw new Error('eval.eval() not supported.');
- };
- this._window.eval = () => {
- throw new Error('window.eval() not supported.');
- };
- this._window.addEventListener('unload', () => {
- if (typeof __coverage__ !== 'undefined') {
- const dir = path.join(process.cwd(), 'dist', 'nyc', '.nyc_output');
- if (!fs.existsSync(dir)) {
- fs.mkdirSync(dir, { recursive: true });
- }
- const base = path.basename(window.location.pathname, '.html');
- const file = path.join(dir, `${base}.json`);
- /* eslint-disable-next-line no-undef */
- fs.writeFileSync(file, JSON.stringify(__coverage__));
- }
- });
- this._environment = electron.ipcRenderer.sendSync('get-environment', {});
- this._environment.menu = this._environment.titlebar && this._environment.platform !== 'darwin';
- this._files = [];
- electron.ipcRenderer.on('open', (sender, data) => {
- this._open(data);
- });
- this._element('menu-button').style.opacity = 0;
- if (!/^\d+\.\d+\.\d+$/.test(this.version)) {
- throw new Error('Invalid version.');
- }
- const metadata = [];
- metadata.push(os.arch());
- let packager = '';
- if (process.platform === 'linux') {
- try {
- child_process.execFileSync('dpkg', ['-S', process.execPath]);
- packager = 'deb';
- } catch {
- try {
- child_process.execFileSync("rpm", ["-qf", process.execPath]);
- packager = 'rpm';
- } catch {
- // continue regardless of error
- }
- }
- }
- metadata.push(packager);
- this._metadata = metadata.join('|');
- }
- get window() {
- return this._window;
- }
- get document() {
- return this._document;
- }
- get version() {
- return this._environment.version;
- }
- get type() {
- return 'Electron';
- }
- get metadata() {
- return this._metadata;
- }
- async view(view) {
- this._view = view;
- const age = async () => {
- const days = (new Date() - new Date(this._environment.date)) / (24 * 60 * 60 * 1000);
- if (days > 180) {
- this.document.body.classList.remove('spinner');
- const link = this._element('logo-github').href;
- for (;;) {
- /* eslint-disable-next-line no-await-in-loop */
- await this.message('Please update to the newest version.', null, 'Download');
- this.openURL(link);
- }
- }
- return Promise.resolve();
- };
- const consent = async () => {
- const time = this.get('consent');
- if (!time || (Date.now() - time) > 30 * 24 * 60 * 60 * 1000) {
- let consent = true;
- try {
- const content = await this._request('https://ipinfo.io/json', { 'Content-Type': 'application/json' }, 2000);
- const json = JSON.parse(content);
- 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) {
- consent = false;
- }
- } catch {
- // continue regardless of error
- }
- if (consent) {
- this.document.body.classList.remove('spinner');
- await this.message('This app uses cookies to report errors and anonymous usage information.', null, 'Accept');
- }
- this.set('consent', Date.now());
- }
- };
- const telemetry = async () => {
- if (this._environment.packaged) {
- const measurement_id = '848W2NVWVH';
- const user = this.get('user') || null;
- const session = this.get('session') || null;
- await this._telemetry.start(`G-${measurement_id}`, user && user.indexOf('.') !== -1 ? user : null, session);
- this._telemetry.send('page_view', {
- app_name: this.type,
- app_version: this.version,
- app_metadata: this.metadata
- });
- this._telemetry.send('scroll', {
- percent_scrolled: 90,
- app_name: this.type,
- app_version: this.version,
- app_metadata: this.metadata
- });
- this.set('user', this._telemetry.get('client_id'));
- this.set('session', this._telemetry.session);
- }
- };
- await age();
- await consent();
- await telemetry();
- }
- async start() {
- if (this._files) {
- const files = this._files;
- delete this._files;
- if (files.length > 0) {
- const data = files.pop();
- this._open(data);
- }
- }
- this._window.addEventListener('focus', () => {
- this._document.body.classList.add('active');
- });
- this._window.addEventListener('blur', () => {
- this._document.body.classList.remove('active');
- });
- if (this._document.hasFocus()) {
- this._document.body.classList.add('active');
- }
- electron.ipcRenderer.on('recents', (sender, data) => {
- this._view.recents(data);
- });
- electron.ipcRenderer.on('export', (sender, data) => {
- this._view.export(data.file);
- });
- electron.ipcRenderer.on('cut', () => {
- this.document.execCommand('cut');
- });
- electron.ipcRenderer.on('copy', () => {
- this.document.execCommand('copy');
- });
- electron.ipcRenderer.on('paste', () => {
- if (this.document.queryCommandSupported('paste')) {
- this.document.execCommand('paste');
- } else if (this.document.queryCommandSupported('insertText')) {
- const content = electron.clipboard.readText();
- this.document.execCommand('insertText', false, content);
- }
- });
- electron.ipcRenderer.on('selectall', () => {
- this.document.execCommand('selectall');
- });
- electron.ipcRenderer.on('toggle', (sender, name) => {
- this._view.toggle(name);
- this.update({ ...this._view.options });
- });
- electron.ipcRenderer.on('zoom-in', () => {
- this._element('zoom-in-button').click();
- });
- electron.ipcRenderer.on('zoom-out', () => {
- this._element('zoom-out-button').click();
- });
- electron.ipcRenderer.on('zoom-reset', () => {
- this._view.resetZoom();
- });
- electron.ipcRenderer.on('show-properties', () => {
- this._element('sidebar-target-button').click();
- });
- electron.ipcRenderer.on('find', () => {
- this._view.find();
- });
- electron.ipcRenderer.on('about', () => {
- this._view.about();
- });
- this._element('titlebar-close').addEventListener('click', () => {
- electron.ipcRenderer.sendSync('window-close', {});
- });
- this._element('titlebar-toggle').addEventListener('click', () => {
- electron.ipcRenderer.sendSync('window-toggle', {});
- });
- this._element('titlebar-minimize').addEventListener('click', () => {
- electron.ipcRenderer.sendSync('window-minimize', {});
- });
- electron.ipcRenderer.on('window-state', (sender, data) => {
- if (this._environment.titlebar) {
- this._element('target').style.marginTop = '32px';
- this._element('target').style.height = 'calc(100% - 32px)';
- this._element('sidebar-title').style.marginTop = '24px';
- this._element('sidebar-closebutton').style.marginTop = '24px';
- this._element('titlebar').classList.add('titlebar-visible');
- }
- if (this._environment.titlebar && this._environment.platform !== 'darwin' && !data.fullscreen) {
- this._element('titlebar-control-box').classList.add('titlebar-control-box-visible');
- } else {
- this._element('titlebar-control-box').classList.remove('titlebar-control-box-visible');
- }
- this._element('menu-button').style.opacity = this._environment.menu ? 1 : 0;
- this._element('titlebar-maximize').style.opacity = data.maximized ? 0 : 1;
- this._element('titlebar-restore').style.opacity = data.maximized ? 1 : 0;
- this._element('titlebar-toggle').setAttribute('title', data.maximized ? 'Restore' : 'Maximize');
- });
- electron.ipcRenderer.sendSync('update-window-state', {});
- const openFileButton = this._element('open-file-button');
- if (openFileButton) {
- openFileButton.addEventListener('click', async () => {
- await this.execute('open');
- });
- }
- this.document.addEventListener('dragover', (e) => {
- e.preventDefault();
- });
- this.document.addEventListener('drop', (e) => {
- e.preventDefault();
- });
- this.document.body.addEventListener('drop', (e) => {
- e.preventDefault();
- const files = Array.from(e.dataTransfer.files);
- const paths = files.map((file) => electron.webUtils.getPathForFile(file));
- if (paths.length > 0) {
- electron.ipcRenderer.send('drop-paths', { paths });
- }
- return false;
- });
- this._view.show('welcome');
- }
- environment(name) {
- return this._environment[name];
- }
- async error(message) {
- await this.message(message, true, 'OK');
- }
- async require(id) {
- return import(`${id}.js`);
- }
- worker(id) {
- return new this.window.Worker(`${id}.js`, { type: 'module' });
- }
- async save(name, extension, defaultPath) {
- return new Promise((resolve, reject) => {
- electron.ipcRenderer.once('show-save-dialog-complete', (event, data) => {
- if (data.error) {
- reject(new Error(data.error));
- } else if (data.canceled) {
- resolve(null);
- } else {
- resolve(data.filePath);
- }
- });
- electron.ipcRenderer.send('show-save-dialog', {
- title: 'Export Tensor',
- defaultPath,
- buttonLabel: 'Export',
- filters: [{ name, extensions: [extension] }]
- });
- });
- }
- async export(file, blob) {
- const reader = new FileReader();
- reader.onload = (e) => {
- const data = new Uint8Array(e.target.result);
- fs.writeFile(file, data, null, async (error) => {
- if (error) {
- await this._view.error(error, 'Error writing file.');
- }
- });
- };
- let error = null;
- if (!blob) {
- error = new Error(`Export blob is '${JSON.stringify(blob)}'.`);
- } else if (!(blob instanceof Blob)) {
- error = new Error(`Export blob type is '${typeof blob}'.`);
- }
- if (error) {
- await this._view.error(error, 'Error exporting image.');
- } else {
- reader.readAsArrayBuffer(blob);
- }
- }
- async execute(name, value) {
- return new Promise((resolve, reject) => {
- electron.ipcRenderer.once('execute-complete', (event, data) => {
- if (data.error) {
- reject(new Error(data.error));
- } else {
- resolve(data.value);
- }
- });
- electron.ipcRenderer.send('execute', { name, value });
- });
- }
- async request(file, encoding, basename) {
- return new Promise((resolve, reject) => {
- const dirname = path.dirname(url.fileURLToPath(import.meta.url));
- const pathname = path.join(basename || dirname, file);
- fs.stat(pathname, (err, stat) => {
- if (err && err.code === 'ENOENT') {
- reject(new Error(`The file '${file}' does not exist.`));
- } else if (err) {
- reject(err);
- } else if (!stat.isFile()) {
- reject(new Error(`The path '${file}' is not a file.`));
- } else if (stat && stat.size < 0x40000000) {
- fs.readFile(pathname, encoding, (err, data) => {
- if (err) {
- reject(err);
- } else {
- resolve(encoding ? data : new base.BinaryStream(data));
- }
- });
- } else if (encoding) {
- reject(new Error(`The file '${file}' size (${stat.size.toString()}) for encoding '${encoding}' is greater than 2 GB.`));
- } else {
- const stream = new node.FileStream(pathname, 0, stat.size, stat.mtimeMs);
- resolve(stream);
- }
- });
- });
- }
- openURL(url) {
- electron.shell.openExternal(url);
- }
- exception(error, fatal) {
- if (this._telemetry && error) {
- try {
- const name = error.name ? `${error.name}: ` : '';
- const message = error.message ? error.message : JSON.stringify(error);
- let context = '';
- let stack = '';
- if (error.stack) {
- const format = (file, line, column) => {
- return `${file.split('\\').join('/').split('/').pop()}:${line}:${column}`;
- };
- const match = error.stack.match(/\n {4}at (.*) \((.*):(\d*):(\d*)\)/);
- if (match) {
- stack = `${match[1]} (${format(match[2], match[3], match[4])})`;
- } else {
- const match = error.stack.match(/\n {4}at (.*):(\d*):(\d*)/);
- if (match) {
- stack = `(${format(match[1], match[2], match[3])})`;
- } else {
- const match = error.stack.match(/.*\n\s*(.*)\s*/);
- if (match) {
- [, stack] = match;
- }
- }
- }
- }
- if (error.context) {
- context = typeof error.context === 'string' ? error.context : JSON.stringify(error.context);
- }
- this._telemetry.send('exception', {
- app_name: this.type,
- app_version: this.version,
- app_metadata: this.metadata,
- error_name: name,
- error_message: message,
- error_context: context,
- error_stack: stack,
- error_fatal: fatal ? true : false
- });
- } catch {
- // continue regardless of error
- }
- }
- }
- event(name, params) {
- if (name && params) {
- params.app_name = this.type;
- params.app_version = this.version;
- params.app_metadata = this.metadata;
- this._telemetry.send(name, params);
- }
- }
- async _context(location) {
- const basename = path.basename(location);
- const stat = fs.statSync(location);
- if (stat.isFile()) {
- const dirname = path.dirname(location);
- const stream = await this.request(basename, null, dirname);
- return new desktop.Context(this, dirname, basename, stream);
- } else if (stat.isDirectory()) {
- const entries = new Map();
- const walk = (dir) => {
- for (const item of fs.readdirSync(dir)) {
- const pathname = path.join(dir, item);
- const stat = fs.statSync(pathname);
- if (stat.isDirectory()) {
- walk(pathname);
- } else if (stat.isFile()) {
- const stream = new node.FileStream(pathname, 0, stat.size, stat.mtimeMs);
- const name = pathname.split(path.sep).join(path.posix.sep);
- entries.set(name, stream);
- }
- }
- };
- walk(location);
- return new desktop.Context(this, location, basename, null, entries);
- }
- throw new Error(`Unsupported path stat '${JSON.stringify(stat)}'.`);
- }
- async _open(location) {
- if (this._files) {
- this._files.push(location);
- return;
- }
- const path = location.path;
- const stat = fs.existsSync(path) ? fs.statSync(path) : null;
- const size = stat && stat.isFile() ? stat.size : 0;
- if (path && this._view.accept(path, size)) {
- this._view.show('welcome spinner');
- let context = null;
- try {
- context = await this._context(path);
- this._telemetry.set('session_engaged', 1);
- } catch (error) {
- await this._view.error(error, 'Error while reading file.');
- this.update({ path: null });
- return;
- }
- try {
- const attachment = await this._view.attach(context);
- if (attachment) {
- this._view.show(null);
- } else {
- const model = await this._view.open(context);
- this._view.show(null);
- const options = { ...this._view.options };
- if (model) {
- options.path = path;
- this._title(location.label);
- } else {
- options.path = path;
- this._title('');
- }
- electron.ipcRenderer.send('update-recents', { path });
- this.update(options);
- }
- } catch (error) {
- const options = { ...this._view.options };
- if (error) {
- await this._view.error(error);
- }
- this.update(options);
- }
- }
- }
- _request(location, headers, timeout) {
- return new Promise((resolve, reject) => {
- const url = new URL(location);
- const protocol = url.protocol === 'https:' ? https : http;
- const options = {};
- options.headers = headers;
- if (timeout) {
- options.timeout = timeout;
- }
- const request = protocol.request(location, options, (response) => {
- if (response.statusCode === 200) {
- let data = '';
- response.on('data', (chunk) => {
- data += chunk;
- });
- response.on('error', (err) => {
- reject(err);
- });
- response.on('end', () => {
- resolve(data);
- });
- } else {
- const error = new Error(`The web request failed with status code '${response.statusCode}'.`);
- error.context = location;
- reject(error);
- }
- });
- request.on("error", (err) => {
- reject(err);
- });
- request.on("timeout", () => {
- request.destroy();
- const error = new Error('The web request timed out.');
- error.context = url;
- reject(error);
- });
- request.end();
- });
- }
- get(name) {
- try {
- return electron.ipcRenderer.sendSync('get-configuration', { name });
- } catch {
- // continue regardless of error
- }
- return undefined;
- }
- set(name, value) {
- try {
- electron.ipcRenderer.sendSync('set-configuration', { name, value });
- } catch {
- // continue regardless of error
- }
- }
- delete(name) {
- try {
- electron.ipcRenderer.sendSync('delete-configuration', { name });
- } catch {
- // continue regardless of error
- }
- }
- _title(label) {
- const element = this._element('titlebar-content-text');
- if (element) {
- element.innerHTML = '';
- if (label) {
- const path = label.split(this._environment.separator || '/');
- for (let i = 0; i < path.length; i++) {
- const span = this.document.createElement('span');
- span.innerHTML = ` ${path[i]} ${i === path.length - 1 ? '' : '<svg class="titlebar-icon" aria-hidden="true"><use xlink:href="#icon-arrow-right"></use></svg>'}`;
- element.appendChild(span);
- }
- }
- }
- }
- _element(id) {
- return this.document.getElementById(id);
- }
- update(data) {
- electron.ipcRenderer.send('window-update', data);
- }
- async message(message, alert, action) {
- return new Promise((resolve) => {
- const type = this.document.body.getAttribute('class');
- this._element('message-text').innerText = message || '';
- const button = this._element('message-button');
- if (action) {
- button.style.removeProperty('display');
- button.innerText = action;
- button.onclick = () => {
- button.onclick = null;
- this.document.body.setAttribute('class', type);
- resolve(0);
- };
- } else {
- button.style.display = 'none';
- button.onclick = null;
- }
- if (alert) {
- this.document.body.setAttribute('class', 'alert');
- } else {
- this.document.body.classList.add('notification');
- this.document.body.classList.remove('default');
- }
- if (action) {
- button.focus();
- }
- });
- }
- };
- desktop.Context = class {
- constructor(host, folder, identifier, stream, entries) {
- this._host = host;
- this._folder = folder;
- this._identifier = identifier;
- this._stream = stream;
- this._entries = entries || new Map();
- }
- get identifier() {
- return this._identifier;
- }
- get stream() {
- return this._stream;
- }
- get entries() {
- return this._entries;
- }
- async request(file, encoding, base) {
- return this._host.request(file, encoding, base === undefined ? this._folder : base);
- }
- async require(id) {
- return this._host.require(id);
- }
- error(error, fatal) {
- this._host.exception(error, fatal);
- }
- };
- if (typeof window !== 'undefined') {
- window.addEventListener('load', () => {
- const value = new desktop.Host();
- window.__view__ = new view.View(value);
- window.__view__.start();
- });
- }
|