mirror of
https://github.com/iNavFlight/inav-configurator.git
synced 2025-07-13 11:29:53 +03:00
Firmware flasher, BLE, TCP, UDP -> Electron
This commit is contained in:
parent
2f880e218d
commit
7df8253099
36 changed files with 733 additions and 656 deletions
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
|
@ -3,17 +3,30 @@
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Electron Main",
|
"name": "Debug Configurator",
|
||||||
"runtimeExecutable": "${workspaceFolder}/node_modules/@electron-forge/cli/script/vscode.sh",
|
"runtimeExecutable": "${workspaceFolder}/node_modules/@electron-forge/cli/script/vscode.sh",
|
||||||
"windows": {
|
"windows": {
|
||||||
"runtimeExecutable": "${workspaceFolder}/node_modules/@electron-forge/cli/script/vscode.cmd",
|
"runtimeExecutable": "${workspaceFolder}/node_modules/@electron-forge/cli/script/vscode.cmd",
|
||||||
|
|
||||||
},
|
},
|
||||||
|
"args": ["--inspect"],
|
||||||
"env": {
|
"env": {
|
||||||
"NODE_ENV": "development"
|
"NODE_ENV": "development"
|
||||||
},
|
},
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"console": "integratedTerminal"
|
"console": "integratedTerminal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug Main Process",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
|
||||||
|
"windows": {
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
||||||
|
},
|
||||||
|
"args" : ["."],
|
||||||
|
"outputCapture": "std"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -414,7 +414,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal-reconnect" class="is-hidden">
|
<div id="modal-reconnect" class="is-hidden">
|
||||||
<div data-i18n="deviceRebooting"></div>
|
<div data-i18n="deviceRebooting">Device - <span style="color: red">Rebooting</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal-saving-defaults" class="is-hidden">
|
<div id="modal-saving-defaults" class="is-hidden">
|
||||||
<div data-i18n="savingDefaults"></div>
|
<div data-i18n="savingDefaults"></div>
|
||||||
|
|
|
@ -12,7 +12,7 @@ appUpdater.checkRelease = function (currVersion) {
|
||||||
let newPrerelase = releaseData[0].prerelease;
|
let newPrerelase = releaseData[0].prerelease;
|
||||||
|
|
||||||
if (newPrerelase == false && semver.gt(newVersion, currVersion)) {
|
if (newPrerelase == false && semver.gt(newVersion, currVersion)) {
|
||||||
GUI.log(newVersion, chrome.runtime.getManifest().version);
|
GUI.log(newVersion, app.getVersion());
|
||||||
GUI.log(currVersion);
|
GUI.log(currVersion);
|
||||||
|
|
||||||
GUI.log(localization.getMessage('newVersionAvailable'));
|
GUI.log(localization.getMessage('newVersionAvailable'));
|
||||||
|
|
|
@ -100,6 +100,7 @@ class Connection {
|
||||||
|
|
||||||
connect(path, options, callback) {
|
connect(path, options, callback) {
|
||||||
this._openRequested = true;
|
this._openRequested = true;
|
||||||
|
this._openCanceled = false;
|
||||||
this._failed = 0;
|
this._failed = 0;
|
||||||
this.connectImplementation(path, options, connectionInfo => {
|
this.connectImplementation(path, options, connectionInfo => {
|
||||||
if (connectionInfo && !this._openCanceled) {
|
if (connectionInfo && !this._openCanceled) {
|
||||||
|
@ -118,7 +119,7 @@ class Connection {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(connectionInfo);
|
callback(connectionInfo);
|
||||||
}
|
}
|
||||||
} else if (connectionInfo && this.openCanceled) {
|
} else if (connectionInfo && this._openCanceled) {
|
||||||
// connection opened, but this connect sequence was canceled
|
// connection opened, but this connect sequence was canceled
|
||||||
// we will disconnect without triggering any callbacks
|
// we will disconnect without triggering any callbacks
|
||||||
this._connectionId = connectionInfo.connectionId;
|
this._connectionId = connectionInfo.connectionId;
|
||||||
|
|
|
@ -95,9 +95,8 @@ class ConnectionBle extends Connection {
|
||||||
});
|
});
|
||||||
|
|
||||||
return navigator.bluetooth.requestDevice({
|
return navigator.bluetooth.requestDevice({
|
||||||
//acceptAllDevices: true,
|
acceptAllDevices: true,
|
||||||
//optionalServices: ids
|
optionalServices: ids
|
||||||
filters: [{ services: ['generic_attribute'] }],
|
|
||||||
}).then(device => {
|
}).then(device => {
|
||||||
console.log("Found BLE device: " + device.name);
|
console.log("Found BLE device: " + device.name);
|
||||||
this._device = device;
|
this._device = device;
|
||||||
|
@ -251,4 +250,12 @@ class ConnectionBle extends Connection {
|
||||||
removeOnReceiveErrorCallback(callback) {
|
removeOnReceiveErrorCallback(callback) {
|
||||||
this._onDisconnectListeners = this._onDisconnectListeners.filter(listener => listener !== callback);
|
this._onDisconnectListeners = this._onDisconnectListeners.filter(listener => listener !== callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getBleUUIDs() {
|
||||||
|
var ids = [];
|
||||||
|
BleDevices.forEach(device => {
|
||||||
|
ids.push(device.serviceUuid)
|
||||||
|
});
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,23 +16,6 @@ class ConnectionSerial extends Connection {
|
||||||
connectImplementation(path, options, callback) {
|
connectImplementation(path, options, callback) {
|
||||||
try {
|
try {
|
||||||
this._serialport = new SerialPortStream({binding, path: path, baudRate: options.bitrate, autoOpen: true}, () => {
|
this._serialport = new SerialPortStream({binding, path: path, baudRate: options.bitrate, autoOpen: true}, () => {
|
||||||
|
|
||||||
this._serialport.on('data', buffer => {
|
|
||||||
this._onReceiveListeners.forEach(listener => {
|
|
||||||
listener({
|
|
||||||
connectionId: this._connectionId,
|
|
||||||
data: buffer
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
this._serialport.on('error', error => {
|
|
||||||
console.log("Serial error: " + error);
|
|
||||||
this._onReceiveErrorListeners.forEach(listener => {
|
|
||||||
listener(error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback({
|
callback({
|
||||||
connectionId: ++this._connectionId,
|
connectionId: ++this._connectionId,
|
||||||
|
@ -44,6 +27,27 @@ class ConnectionSerial extends Connection {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
callback(false);
|
callback(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._serialport.on('data', buffer => {
|
||||||
|
this._onReceiveListeners.forEach(listener => {
|
||||||
|
listener({
|
||||||
|
connectionId: this._connectionId,
|
||||||
|
data: buffer
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this._serialport.on('close', error => {
|
||||||
|
this.abort();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._serialport.on('error', error => {
|
||||||
|
this.abort();
|
||||||
|
console.log("Serial error: " + error);
|
||||||
|
this._onReceiveErrorListeners.forEach(listener => {
|
||||||
|
listener(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectImplementation(callback) {
|
disconnectImplementation(callback) {
|
||||||
|
@ -52,24 +56,27 @@ class ConnectionSerial extends Connection {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log("Unable to close serial: " + error)
|
console.log("Unable to close serial: " + error)
|
||||||
}
|
}
|
||||||
if (callback) {
|
|
||||||
callback(error ? false : true);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendImplementation(data, callback) {
|
sendImplementation(data, callback) {
|
||||||
if (this._serialport && this._serialport.isOpen) {
|
if (this._serialport && this._serialport.isOpen) {
|
||||||
this._serialport.write(Buffer.from(data), error => {
|
this._serialport.write(Buffer.from(data), error => {
|
||||||
var result = 0;
|
var result = 0;
|
||||||
|
var sent = data.byteLength;
|
||||||
if (error) {
|
if (error) {
|
||||||
result = 1;
|
result = 1;
|
||||||
|
sent = 0;
|
||||||
console.log("Serial wrire error: " + error)
|
console.log("Serial wrire error: " + error)
|
||||||
}
|
}
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback({
|
callback({
|
||||||
bytesSent: data.byteLength,
|
bytesSent: sent,
|
||||||
resultCode: result
|
resultCode: result
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
|
const net = require('net')
|
||||||
|
|
||||||
const STANDARD_TCP_PORT = 5761;
|
const STANDARD_TCP_PORT = 5761;
|
||||||
|
|
||||||
class ConnectionTcp extends Connection {
|
class ConnectionTcp extends Connection {
|
||||||
|
@ -7,8 +9,11 @@ class ConnectionTcp extends Connection {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this._socket = null;
|
||||||
this._connectionIP = "";
|
this._connectionIP = "";
|
||||||
this.connectionPort = 0;
|
this.connectionPort = 0;
|
||||||
|
this._onReceiveListeners = [];
|
||||||
|
this._onErrorListener = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
connectImplementation(address, options, callback) {
|
connectImplementation(address, options, callback) {
|
||||||
|
@ -21,95 +26,62 @@ class ConnectionTcp extends Connection {
|
||||||
this._connectionPort = STANDARD_PORT;
|
this._connectionPort = STANDARD_PORT;
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.sockets.tcp.create({
|
try {
|
||||||
name: "iNavTCP",
|
this._socket = net.connect({ host: this._connectionIP, port: this._connectionPort }, () => {
|
||||||
bufferSize: 65535
|
this._socket.setNoDelay(true);
|
||||||
}, createInfo => {
|
GUI.log(localization.getMessage('connectionConnected', ["tcp://" + this._connectionIP + ":" + this._connectionPort]));
|
||||||
this.checkChromeLastError();
|
|
||||||
if (createInfo && !this._openCanceled) {
|
|
||||||
chrome.sockets.tcp.connect(createInfo.socketId, this._connectionIP, this._connectionPort, result => {
|
|
||||||
this.checkChromeLastError();
|
|
||||||
|
|
||||||
if (result == 0) {
|
|
||||||
// Disable Nagle's algorithm
|
|
||||||
chrome.sockets.tcp.setNoDelay(createInfo.socketId, true, noDelayResult => {
|
|
||||||
this.checkChromeLastError();
|
|
||||||
if (noDelayResult < 0) {
|
|
||||||
console.warn("Unable to set TCP_NODELAY: " + noDelayResult);
|
|
||||||
if (callback) {
|
|
||||||
callback(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addOnReceiveErrorListener(info => {
|
|
||||||
console.error(info);
|
|
||||||
|
|
||||||
let message;
|
|
||||||
switch (info.resultCode) {
|
|
||||||
case -15:
|
|
||||||
// connection is lost, cannot write to it anymore, preventing further disconnect attempts
|
|
||||||
message = 'error: ERR_SOCKET_NOT_CONNECTED';
|
|
||||||
console.log(`TCP: ${message}: ${info.resultCode}`);
|
|
||||||
this._connectionId = false;
|
|
||||||
return;
|
|
||||||
case -21:
|
|
||||||
message = 'error: NETWORK_CHANGED';
|
|
||||||
break;
|
|
||||||
case -100:
|
|
||||||
message = 'error: CONNECTION_CLOSED';
|
|
||||||
break;
|
|
||||||
case -102:
|
|
||||||
message = 'error: CONNECTION_REFUSED';
|
|
||||||
break;
|
|
||||||
case -105:
|
|
||||||
message = 'error: NAME_NOT_RESOLVED';
|
|
||||||
break;
|
|
||||||
case -106:
|
|
||||||
message = 'error: INTERNET_DISCONNECTED';
|
|
||||||
break;
|
|
||||||
case -109:
|
|
||||||
message = 'error: ADDRESS_UNREACHABLE';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let resultMessage = message ? `${message} ${info.resultCode}` : info.resultCode;
|
|
||||||
console.warn(`TCP: ${resultMessage} ID: ${this._connectionId}`);
|
|
||||||
|
|
||||||
this.abort();
|
|
||||||
});
|
|
||||||
|
|
||||||
GUI.log(localization.getMessage('connectionConnected', ["tcp://" + this._connectionIP + ":" + this._connectionPort]));
|
|
||||||
|
|
||||||
if (callback) {
|
|
||||||
callback({
|
|
||||||
bitrate: 115200,
|
|
||||||
connectionId: createInfo.socketId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error("Unable to open TCP socket: " + result);
|
|
||||||
if (callback) {
|
|
||||||
callback(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error("Unable to create TCP socket.");
|
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(false);
|
callback({
|
||||||
|
bitrate: 115200,
|
||||||
|
connectionId: ++this._connectionId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
callback(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._socket.on('data', buffer => {
|
||||||
|
this._onReceiveListeners.forEach(listener => {
|
||||||
|
listener({
|
||||||
|
connectionId: this._connectionId,
|
||||||
|
data: buffer
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
this._socket.on('end', () => {
|
||||||
|
console.log("TCP Remote has closed the connection");
|
||||||
|
if (this._socket) {
|
||||||
|
this.abort();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this._socket.on('error', (error) => {
|
||||||
|
GUI.log("TCP error: " + error);
|
||||||
|
console.log("TCP error: " + error);
|
||||||
|
|
||||||
|
if (this._socket) {
|
||||||
|
this.abort();
|
||||||
|
}
|
||||||
|
this._onReceiveErrorListeners.forEach(listener => {
|
||||||
|
listener(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectImplementation(callback) {
|
disconnectImplementation(callback) {
|
||||||
chrome.sockets.tcp.disconnect(this._connectionId);
|
|
||||||
this.checkChromeLastError();
|
if (this._socket && !this._socket.destroyed) {
|
||||||
|
this._socket.end();
|
||||||
|
}
|
||||||
|
|
||||||
this._connectionIP = "";
|
this._connectionIP = "";
|
||||||
this._connectionPort = 0;
|
this._connectionPort = 0;
|
||||||
|
this._socket = null;
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(true);
|
callback(true);
|
||||||
|
@ -117,22 +89,31 @@ class ConnectionTcp extends Connection {
|
||||||
}
|
}
|
||||||
|
|
||||||
sendImplementation(data, callback) {;
|
sendImplementation(data, callback) {;
|
||||||
chrome.sockets.tcp.send(this._connectionId, data, callback);
|
if (this._socket && !this._socket.destroyed) {
|
||||||
|
this._socket.write(Buffer.from(data), () => {
|
||||||
|
if (callback) {
|
||||||
|
callback({
|
||||||
|
bytesSent: data.byteLength,
|
||||||
|
resultCode: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addOnReceiveCallback(callback){
|
addOnReceiveCallback(callback){
|
||||||
chrome.sockets.tcp.onReceive.addListener(callback);
|
this._onReceiveErrorListeners.push(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeOnReceiveCallback(callback){
|
removeOnReceiveCallback(callback){
|
||||||
chrome.sockets.tcp.onReceive.removeListener(callback);
|
this._onReceiveListeners = this._onReceiveErrorListeners.filter(listener => listener !== callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
addOnReceiveErrorCallback(callback) {
|
addOnReceiveErrorCallback(callback) {
|
||||||
chrome.sockets.tcp.onReceiveError.addListener(callback);
|
this._onReceiveErrorListeners.push(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeOnReceiveErrorCallback(callback) {
|
removeOnReceiveErrorCallback(callback) {
|
||||||
chrome.sockets.tcp.onReceiveError.removeListener(callback);
|
this._onReceiveErrorListeners = this._onReceiveErrorListeners.filter(listener => listener !== callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +1,21 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const STANDARD_UDP_PORT = 5762;
|
const dgram = require('node:dgram');
|
||||||
|
const socket = dgram.createSocket('udp4');
|
||||||
|
|
||||||
|
const STANDARD_UDP_PORT = 5761;
|
||||||
class ConnectionUdp extends Connection {
|
class ConnectionUdp extends Connection {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this._connectionIP = "";
|
this._connectionIP = "";
|
||||||
this._connectionPort = 0;
|
this._connectionPort = 0;
|
||||||
this._timeoutId = false;
|
this._onReceiveListeners = [];
|
||||||
this._isCli = false;
|
this._onErrorListener = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
connectImplementation(address, options, callback) {
|
||||||
* @param {boolean} value
|
|
||||||
*/
|
|
||||||
set isCli(value) {
|
|
||||||
this._isCli = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectImplementation(address, options, callback) {
|
|
||||||
var addr = address.split(':');
|
var addr = address.split(':');
|
||||||
if (addr.length >= 2) {
|
if (addr.length >= 2) {
|
||||||
this._connectionIP = addr[0];
|
this._connectionIP = addr[0];
|
||||||
|
@ -29,120 +25,94 @@ class ConnectionUdp extends Connection {
|
||||||
this._connectionPort = STANDARD_UDP_PORT;
|
this._connectionPort = STANDARD_UDP_PORT;
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.sockets.udp.create({
|
try {
|
||||||
name: "iNavUDP",
|
socket.bind(this._connectionPort, () => {
|
||||||
bufferSize: 65535,
|
GUI.log(localization.getMessage('connectionConnected', ["udp://" + this._connectionIP + ":" + this._connectionPort]));
|
||||||
}, createInfo => {
|
|
||||||
this.checkChromeLastError();
|
|
||||||
if (createInfo && !this._openCanceled) {
|
|
||||||
chrome.sockets.udp.bind(createInfo.socketId, "0.0.0.0", this._connectionPort, result => {
|
|
||||||
this.checkChromeLastError();
|
|
||||||
if (result == 0) {
|
|
||||||
// UDP connections don't trigger an event if they are interrupted, a simple timeout mechanism must suffice here.
|
|
||||||
this.addOnReceiveCallback(() => {
|
|
||||||
if (this._timeoutId) {
|
|
||||||
clearTimeout(this._timeoutId);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._timeoutId = setTimeout(() => {
|
|
||||||
if (!this._isCli) { // Disable timeout for CLI
|
|
||||||
GUI.log(localization.getMessage('connectionUdpTimeout'));
|
|
||||||
this.abort();
|
|
||||||
}
|
|
||||||
}, 10000);
|
|
||||||
})
|
|
||||||
|
|
||||||
// Actually useless, but according to chrome documentation also UDP triggers error events ¯\_(ツ)_/¯
|
|
||||||
this.addOnReceiveErrorListener(info => {
|
|
||||||
console.error(info);
|
|
||||||
|
|
||||||
let message;
|
|
||||||
switch (info.resultCode) {
|
|
||||||
case -15:
|
|
||||||
// connection is lost, cannot write to it anymore, preventing further disconnect attempts
|
|
||||||
message = 'error: ERR_SOCKET_NOT_CONNECTED';
|
|
||||||
console.log(`UDP: ${message}: ${info.resultCode}`);
|
|
||||||
this._connectionId = false;
|
|
||||||
return;
|
|
||||||
case -21:
|
|
||||||
message = 'error: NETWORK_CHANGED';
|
|
||||||
break;
|
|
||||||
case -100:
|
|
||||||
message = 'error: CONNECTION_CLOSED';
|
|
||||||
break;
|
|
||||||
case -102:
|
|
||||||
message = 'error: CONNECTION_REFUSED';
|
|
||||||
break;
|
|
||||||
case -105:
|
|
||||||
message = 'error: NAME_NOT_RESOLVED';
|
|
||||||
break;
|
|
||||||
case -106:
|
|
||||||
message = 'error: INTERNET_DISCONNECTED';
|
|
||||||
break;
|
|
||||||
case -109:
|
|
||||||
message = 'error: ADDRESS_UNREACHABLE';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let resultMessage = message ? `${message} ${info.resultCode}` : info.resultCode;
|
|
||||||
console.warn(`UDP: ${resultMessage} ID: ${this._connectionId}`);
|
|
||||||
|
|
||||||
this.abort();
|
|
||||||
});
|
|
||||||
|
|
||||||
GUI.log(localization.getMessage('connectionConnected', ["udp://" + this._connectionIP + ":" + this._connectionPort]));
|
|
||||||
|
|
||||||
if (callback) {
|
|
||||||
callback({
|
|
||||||
bitrate: 115200,
|
|
||||||
connectionId: createInfo.socketId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error("Unable to open UDP socket: " + result);
|
|
||||||
if (callback) {
|
|
||||||
callback(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error("Unable to create UDP socket.");
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(false);
|
callback({
|
||||||
|
bitrate: 115200,
|
||||||
|
connectionId: ++this._connectionId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
callback(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on('message', (msg, _rinfo) => {
|
||||||
|
this._onReceiveListeners.forEach(listener => {
|
||||||
|
listener({
|
||||||
|
connectionId: ++this._connectionId,
|
||||||
|
data: msg
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('error', (error) => {
|
||||||
|
GUI.log("UDP error: " + error);
|
||||||
|
console.log("UDP error: " + error);
|
||||||
|
this.abort();
|
||||||
|
this._onReceiveErrorListeners.forEach(listener => {
|
||||||
|
listener(error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectImplementation(callback) {
|
disconnectImplementation(callback) {
|
||||||
chrome.sockets.udp.close(this._connectionId);
|
var ret = true;
|
||||||
this.checkChromeLastError();
|
try {
|
||||||
|
socket.disconnect();
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Disconecct error: " + error)
|
||||||
|
ret = false;
|
||||||
|
}
|
||||||
|
|
||||||
this._connectionIP = "";
|
this._connectionIP = "";
|
||||||
this._connectionPort = 0;
|
this._connectionPort = 0;
|
||||||
clearTimeout(this._timeoutId);
|
|
||||||
this._timeoutId = false;
|
if (callback) {
|
||||||
if (callback) {
|
callback(ret);
|
||||||
callback(true);
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendImplementation(data, callback) {;
|
sendImplementation(data, callback) {;
|
||||||
chrome.sockets.udp.send(this._connectionId, data, this._connectionIP, this._connectionPort, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
socket.send(Buffer.from(data), this._connectionPort, this._connectionIP, (error) => {
|
||||||
|
var result = 0;
|
||||||
|
var sent = data.byteLength;
|
||||||
|
if (error) {
|
||||||
|
result = 1;
|
||||||
|
sent = 0;
|
||||||
|
console.log("Serial wrire error: " + error)
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
callback({
|
||||||
|
bytesSent: sent,
|
||||||
|
resultCode: result
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log("UDP write error: " + error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addOnReceiveCallback(callback){
|
addOnReceiveCallback(callback){
|
||||||
chrome.sockets.udp.onReceive.addListener(callback);
|
this._onReceiveErrorListeners.push(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeOnReceiveCallback(callback){
|
removeOnReceiveCallback(callback){
|
||||||
chrome.sockets.udp.onReceive.removeListener(callback);
|
this._onReceiveListeners = this._onReceiveErrorListeners.filter(listener => listener !== callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
addOnReceiveErrorCallback(callback) {
|
addOnReceiveErrorCallback(callback) {
|
||||||
chrome.sockets.udp.onReceiveError.addListener(callback);
|
this._onReceiveErrorListeners.push(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeOnReceiveErrorCallback(callback) {
|
removeOnReceiveErrorCallback(callback) {
|
||||||
chrome.sockets.udp.onReceiveError.removeListener(callback);
|
this._onReceiveErrorListeners = this._onReceiveErrorListeners.filter(listener => listener !== callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1033,7 +1033,7 @@ helper.defaultsDialog = (function () {
|
||||||
privateScope.onPresetClick = function (event) {
|
privateScope.onPresetClick = function (event) {
|
||||||
savingDefaultsModal = new jBox('Modal', {
|
savingDefaultsModal = new jBox('Modal', {
|
||||||
width: 400,
|
width: 400,
|
||||||
height: 100,
|
height: 120,
|
||||||
animation: false,
|
animation: false,
|
||||||
closeOnClick: false,
|
closeOnClick: false,
|
||||||
closeOnEsc: false,
|
closeOnEsc: false,
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
/*global $*/
|
/*global $*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function checkChromeRuntimeError() {
|
function checkChromeRuntimeError() {
|
||||||
if (chrome.runtime.lastError) {
|
|
||||||
console.error(
|
|
||||||
`Chrome API Error: ${chrome.runtime.lastError.message}.\n Traced ${
|
|
||||||
new Error().stack
|
|
||||||
}`
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
js/libraries/bluetooth-device-chooser/index.html
Normal file
18
js/libraries/bluetooth-device-chooser/index.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
||||||
|
<link type="text/css" rel="stylesheet" href="style.css" media="all" />
|
||||||
|
<script src="renderer.js"></script>
|
||||||
|
<title>Bluetooth Device Chooser</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Select Bluetooth device:</h1>
|
||||||
|
<div id="list">
|
||||||
|
<div id="cancel" class="item">
|
||||||
|
Cancel
|
||||||
|
</div>
|
||||||
|
</div
|
||||||
|
</body>
|
||||||
|
</html>
|
6
js/libraries/bluetooth-device-chooser/preload.js
Normal file
6
js/libraries/bluetooth-device-chooser/preload.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
const { contextBridge, ipcRenderer } = require('electron/renderer');
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
|
bleScan: (callback) => ipcRenderer.on('ble-scan', (_event, data) => callback(data)),
|
||||||
|
deviceSelected: (deviceId) => ipcRenderer.send('deviceSelected', deviceId)
|
||||||
|
});
|
22
js/libraries/bluetooth-device-chooser/renderer.js
Normal file
22
js/libraries/bluetooth-device-chooser/renderer.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
window.electronAPI.bleScan(data => {
|
||||||
|
data.forEach(device => {
|
||||||
|
var dev = document.getElementById(device.deviceId)
|
||||||
|
if (dev) {
|
||||||
|
dev.parentElement.removeChild(dev);
|
||||||
|
}
|
||||||
|
var item = document.createElement('div');
|
||||||
|
item.className = 'item'
|
||||||
|
item.id = device.deviceId;
|
||||||
|
item.addEventListener('click', () => {
|
||||||
|
window.electronAPI.deviceSelected(item.id);
|
||||||
|
window.close();
|
||||||
|
});
|
||||||
|
item.appendChild(document.createTextNode(device.deviceName + ' (' + device.deviceId + ')'));
|
||||||
|
document.getElementById('list').prepend(item);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document.getElementById('cancel').addEventListener('click', () => {
|
||||||
|
window.close();
|
||||||
|
});
|
||||||
|
});
|
38
js/libraries/bluetooth-device-chooser/style.css
Normal file
38
js/libraries/bluetooth-device-chooser/style.css
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: black;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#id {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#list {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
margin: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
width: 345px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
border: 1px solid whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:hover {
|
||||||
|
background-color: rgb(88, 88, 192);
|
||||||
|
}
|
||||||
|
|
||||||
|
#cancel {
|
||||||
|
text-align: center;
|
||||||
|
}
|
|
@ -1,87 +0,0 @@
|
||||||
(function() { var g,aa=aa||{},h=this,ba=function(){},ca=function(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&
|
|
||||||
!a.propertyIsEnumerable("call"))return"function"}else return"null";else if("function"==b&&"undefined"==typeof a.call)return"object";return b},m=function(a){return"array"==ca(a)},da=function(a){var b=ca(a);return"array"==b||"object"==b&&"number"==typeof a.length},n=function(a){return"string"==typeof a},ea=function(a){return"number"==typeof a},p=function(a){return"function"==ca(a)},q=function(a){var b=typeof a;return"object"==b&&null!=a||"function"==b},fa=function(a,b,c){return a.call.apply(a.bind,
|
|
||||||
arguments)},ga=function(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var c=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(c,d);return a.apply(b,c)}}return function(){return a.apply(b,arguments)}},r=function(a,b,c){r=Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf("native code")?fa:ga;return r.apply(null,arguments)},ha=function(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var b=
|
|
||||||
c.slice();b.push.apply(b,arguments);return a.apply(this,b)}},s=Date.now||function(){return+new Date},t=function(a,b){var c=a.split("."),d=h;c[0]in d||!d.execScript||d.execScript("var "+c[0]);for(var e;c.length&&(e=c.shift());)c.length||void 0===b?d=d[e]?d[e]:d[e]={}:d[e]=b},u=function(a,b){function c(){}c.prototype=b.prototype;a.L=b.prototype;a.prototype=new c;a.Pc=function(a,c,f){return b.prototype[c].apply(a,Array.prototype.slice.call(arguments,2))}};
|
|
||||||
Function.prototype.bind=Function.prototype.bind||function(a,b){if(1<arguments.length){var c=Array.prototype.slice.call(arguments,1);c.unshift(this,a);return r.apply(null,c)}return r(this,a)};var v=function(a){if(Error.captureStackTrace)Error.captureStackTrace(this,v);else{var b=Error().stack;b&&(this.stack=b)}a&&(this.message=String(a))};u(v,Error);v.prototype.name="CustomError";var ia=function(a,b){return a<b?-1:a>b?1:0};var w=Array.prototype,ja=w.indexOf?function(a,b,c){return w.indexOf.call(a,b,c)}:function(a,b,c){c=null==c?0:0>c?Math.max(0,a.length+c):c;if(n(a))return n(b)&&1==b.length?a.indexOf(b,c):-1;for(;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},ka=w.forEach?function(a,b,c){w.forEach.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=n(a)?a.split(""):a,f=0;f<d;f++)f in e&&b.call(c,e[f],f,a)},la=w.some?function(a,b,c){return w.some.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=n(a)?a.split(""):
|
|
||||||
a,f=0;f<d;f++)if(f in e&&b.call(c,e[f],f,a))return!0;return!1},ma=w.every?function(a,b,c){return w.every.call(a,b,c)}:function(a,b,c){for(var d=a.length,e=n(a)?a.split(""):a,f=0;f<d;f++)if(f in e&&!b.call(c,e[f],f,a))return!1;return!0},oa=function(a){var b;t:{b=na;for(var c=a.length,d=n(a)?a.split(""):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a)){b=e;break t}b=-1}return 0>b?null:n(a)?a.charAt(b):a[b]},pa=function(a,b){var c=ja(a,b),d;(d=0<=c)&&w.splice.call(a,c,1);return d},qa=function(a){return w.concat.apply(w,
|
|
||||||
arguments)},ra=function(a,b,c){return 2>=arguments.length?w.slice.call(a,b):w.slice.call(a,b,c)};var sa="StopIteration"in h?h.StopIteration:Error("StopIteration"),ta=function(){};ta.prototype.next=function(){throw sa;};ta.prototype.vc=function(){return this};var ua=function(a,b,c){for(var d in a)b.call(c,a[d],d,a)},va=function(a){var b=[],c=0,d;for(d in a)b[c++]=a[d];return b},wa=function(a){var b=[],c=0,d;for(d in a)b[c++]=d;return b},xa=function(a,b){var c;t:{for(c in a)if(b.call(void 0,a[c],c,a))break t;c=void 0}return c&&a[c]},ya="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),za=function(a,b){for(var c,d,e=1;e<arguments.length;e++){d=arguments[e];for(c in d)a[c]=d[c];for(var f=0;f<ya.length;f++)c=
|
|
||||||
ya[f],Object.prototype.hasOwnProperty.call(d,c)&&(a[c]=d[c])}};var x=function(a,b){this.p={};this.b=[];this.oa=this.h=0;var c=arguments.length;if(1<c){if(c%2)throw Error("Uneven number of arguments");for(var d=0;d<c;d+=2)this.set(arguments[d],arguments[d+1])}else a&&this.ia(a)};x.prototype.t=function(){Aa(this);for(var a=[],b=0;b<this.b.length;b++)a.push(this.p[this.b[b]]);return a};x.prototype.F=function(){Aa(this);return this.b.concat()};x.prototype.Q=function(a){return y(this.p,a)};
|
|
||||||
x.prototype.remove=function(a){return y(this.p,a)?(delete this.p[a],this.h--,this.oa++,this.b.length>2*this.h&&Aa(this),!0):!1};var Aa=function(a){if(a.h!=a.b.length){for(var b=0,c=0;b<a.b.length;){var d=a.b[b];y(a.p,d)&&(a.b[c++]=d);b++}a.b.length=c}if(a.h!=a.b.length){for(var e={},c=b=0;b<a.b.length;)d=a.b[b],y(e,d)||(a.b[c++]=d,e[d]=1),b++;a.b.length=c}};g=x.prototype;g.get=function(a,b){return y(this.p,a)?this.p[a]:b};
|
|
||||||
g.set=function(a,b){y(this.p,a)||(this.h++,this.b.push(a),this.oa++);this.p[a]=b};g.ia=function(a){var b;a instanceof x?(b=a.F(),a=a.t()):(b=wa(a),a=va(a));for(var c=0;c<b.length;c++)this.set(b[c],a[c])};g.forEach=function(a,b){for(var c=this.F(),d=0;d<c.length;d++){var e=c[d],f=this.get(e);a.call(b,f,e,this)}};g.clone=function(){return new x(this)};g.Jb=function(){Aa(this);for(var a={},b=0;b<this.b.length;b++){var c=this.b[b];a[c]=this.p[c]}return a};
|
|
||||||
g.vc=function(a){Aa(this);var b=0,c=this.b,d=this.p,e=this.oa,f=this,k=new ta;k.next=function(){for(;;){if(e!=f.oa)throw Error("The map has changed since the iterator was createdOn");if(b>=c.length)throw sa;var k=c[b++];return a?k:d[k]}};return k};var y=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)};var Ba,Ca,Da={id:"hitType",name:"t",valueType:"text",maxLength:void 0,defaultValue:void 0},Ea={id:"sessionControl",name:"sc",valueType:"text",maxLength:void 0,defaultValue:void 0},Fa={id:"description",name:"cd",valueType:"text",maxLength:2048,defaultValue:void 0},Ga={id:"eventCategory",name:"ec",valueType:"text",maxLength:150,defaultValue:void 0},Ha={id:"eventAction",name:"ea",valueType:"text",maxLength:500,defaultValue:void 0},Ia={id:"eventLabel",name:"el",valueType:"text",maxLength:500,defaultValue:void 0},
|
|
||||||
Ja={id:"eventValue",name:"ev",valueType:"integer",maxLength:void 0,defaultValue:void 0},Ka={pd:Da,Qc:{id:"anonymizeIp",name:"aip",valueType:"boolean",maxLength:void 0,defaultValue:void 0},Ad:{id:"queueTime",name:"qt",valueType:"integer",maxLength:void 0,defaultValue:void 0},Wc:{id:"cacheBuster",name:"z",valueType:"text",maxLength:void 0,defaultValue:void 0},Gd:Ea,Wd:{id:"userId",name:"uid",valueType:"text",maxLength:void 0,defaultValue:void 0},xd:{id:"nonInteraction",name:"ni",valueType:"boolean",
|
|
||||||
maxLength:void 0,defaultValue:void 0},fd:Fa,Pd:{id:"title",name:"dt",valueType:"text",maxLength:1500,defaultValue:void 0},Sc:{id:"appId",name:"aid",valueType:"text",maxLength:150,defaultValue:void 0},Tc:{id:"appInstallerId",name:"aiid",valueType:"text",maxLength:150,defaultValue:void 0},jd:Ga,hd:Ha,kd:Ia,ld:Ja,Id:{id:"socialNetwork",name:"sn",valueType:"text",maxLength:50,defaultValue:void 0},Hd:{id:"socialAction",name:"sa",valueType:"text",maxLength:50,defaultValue:void 0},Jd:{id:"socialTarget",
|
|
||||||
name:"st",valueType:"text",maxLength:2048,defaultValue:void 0},Sd:{id:"transactionId",name:"ti",valueType:"text",maxLength:500,defaultValue:void 0},Rd:{id:"transactionAffiliation",name:"ta",valueType:"text",maxLength:500,defaultValue:void 0},Td:{id:"transactionRevenue",name:"tr",valueType:"currency",maxLength:void 0,defaultValue:void 0},Ud:{id:"transactionShipping",name:"ts",valueType:"currency",maxLength:void 0,defaultValue:void 0},Vd:{id:"transactionTax",name:"tt",valueType:"currency",maxLength:void 0,
|
|
||||||
defaultValue:void 0},dd:{id:"currencyCode",name:"cu",valueType:"text",maxLength:10,defaultValue:void 0},td:{id:"itemPrice",name:"ip",valueType:"currency",maxLength:void 0,defaultValue:void 0},ud:{id:"itemQuantity",name:"iq",valueType:"integer",maxLength:void 0,defaultValue:void 0},rd:{id:"itemCode",name:"ic",valueType:"text",maxLength:500,defaultValue:void 0},sd:{id:"itemName",name:"in",valueType:"text",maxLength:500,defaultValue:void 0},qd:{id:"itemCategory",name:"iv",valueType:"text",maxLength:500,
|
|
||||||
defaultValue:void 0},bd:{id:"campaignSource",name:"cs",valueType:"text",maxLength:100,defaultValue:void 0},$c:{id:"campaignMedium",name:"cm",valueType:"text",maxLength:50,defaultValue:void 0},ad:{id:"campaignName",name:"cn",valueType:"text",maxLength:100,defaultValue:void 0},Zc:{id:"campaignKeyword",name:"ck",valueType:"text",maxLength:500,defaultValue:void 0},Xc:{id:"campaignContent",name:"cc",valueType:"text",maxLength:500,defaultValue:void 0},Yc:{id:"campaignId",name:"ci",valueType:"text",maxLength:100,
|
|
||||||
defaultValue:void 0},od:{id:"gclid",name:"gclid",valueType:"text",maxLength:void 0,defaultValue:void 0},ed:{id:"dclid",name:"dclid",valueType:"text",maxLength:void 0,defaultValue:void 0},zd:{id:"pageLoadTime",name:"plt",valueType:"integer",maxLength:void 0,defaultValue:void 0},gd:{id:"dnsTime",name:"dns",valueType:"integer",maxLength:void 0,defaultValue:void 0},Kd:{id:"tcpConnectTime",name:"tcp",valueType:"integer",maxLength:void 0,defaultValue:void 0},Fd:{id:"serverResponseTime",name:"srt",valueType:"integer",
|
|
||||||
maxLength:void 0,defaultValue:void 0},yd:{id:"pageDownloadTime",name:"pdt",valueType:"integer",maxLength:void 0,defaultValue:void 0},Bd:{id:"redirectResponseTime",name:"rrt",valueType:"integer",maxLength:void 0,defaultValue:void 0},Ld:{id:"timingCategory",name:"utc",valueType:"text",maxLength:150,defaultValue:void 0},Od:{id:"timingVar",name:"utv",valueType:"text",maxLength:500,defaultValue:void 0},Nd:{id:"timingValue",name:"utt",valueType:"integer",maxLength:void 0,defaultValue:void 0},Md:{id:"timingLabel",
|
|
||||||
name:"utl",valueType:"text",maxLength:500,defaultValue:void 0},md:{id:"exDescription",name:"exd",valueType:"text",maxLength:150,defaultValue:void 0},nd:{id:"exFatal",name:"exf",valueType:"boolean",maxLength:void 0,defaultValue:"1"}},La=function(a){if(1>a||200<a)throw Error("Expected dimension index range 1-200, but was : "+a);return{id:"dimension"+a,name:"cd"+a,valueType:"text",maxLength:150,defaultValue:void 0}},Ma=function(a){if(1>a||200<a)throw Error("Expected metric index range 1-200, but was : "+
|
|
||||||
a);return{id:"metric"+a,name:"cm"+a,valueType:"integer",maxLength:void 0,defaultValue:void 0}};var Na=function(a){if(1>a)return"0";if(3>a)return"1-2";a=Math.floor(Math.log(a-1)/Math.log(2));return Math.pow(2,a)+1+"-"+Math.pow(2,a+1)},Oa=function(a,b){for(var c=0,d=a.length-1,e=0;c<=d;){var f=Math.floor((c+d)/2),e=a[f];if(b<=e){d=0==f?0:a[f-1];if(b>d)return(d+1).toString()+"-"+e.toString();d=f-1}else if(b>e){if(f>=a.length-1)return(a[a.length-1]+1).toString()+"+";c=f+1}}return"<= 0"};var z=function(){this.ab=[]},Pa=function(){return new z};g=z.prototype;g.when=function(a){this.ab.push(a);return this};g.zb=function(a){var b=arguments;this.when(function(a){return 0<=ja(b,a.Gb())});return this};g.Oc=function(a,b){var c=ra(arguments,1);this.when(function(b){b=b.T().get(a);return 0<=ja(c,b)});return this};g.xb=function(a,b){if(q(this.e))throw Error("Filter has already been set.");this.e=q(b)?r(a,b):a;return this};
|
|
||||||
g.Ca=function(){if(0==this.ab.length)throw Error("Must specify at least one predicate using #when or a helper method.");if(!q(this.e))throw Error("Must specify a delegate filter using #applyFilter.");return r(function(a){ma(this.ab,function(b){return b(a)})&&this.e(a)},this)};var A=function(){this.Ab=!1;this.Bb="";this.qb=!1;this.za=null};A.prototype.wc=function(a){this.Ab=!0;this.Bb=a||" - ";return this};A.prototype.Nc=function(){this.qb=!0;return this};A.prototype.Ec=function(){return Qa(this,Na)};A.prototype.Fc=function(a){return Qa(this,ha(Oa,a))};
|
|
||||||
var Qa=function(a,b){if(null!=a.za)throw Error("LabelerBuilder: Only one labeling strategy may be used.");a.za=r(function(a){var d=a.T().get(Ja),e=a.T().get(Ia);ea(d)&&(d=b(d),null!=e&&this.Ab&&(d=e+this.Bb+d),a.T().set(Ia,d))},a);return a};A.prototype.Ca=function(){if(null==this.za)throw Error("LabelerBuilder: a labeling strategy must be specified prior to calling build().");return Pa().zb("event").xb(r(function(a){this.za(a);this.qb&&a.T().remove(Ja)},this)).Ca()};var Ra=function(a,b){var c=Array.prototype.slice.call(arguments),d=c.shift();if("undefined"==typeof d)throw Error("[goog.string.format] Template required");return d.replace(/%([0\-\ \+]*)(\d+)?(\.(\d+))?([%sfdiu])/g,function(a,b,d,l,N,J,U,V){if("%"==J)return"%";var Db=c.shift();if("undefined"==typeof Db)throw Error("[goog.string.format] Not enough arguments");arguments[0]=Db;return B[J].apply(null,arguments)})},B={s:function(a,b,c){return isNaN(c)||""==c||a.length>=c?a:a=-1<b.indexOf("-",0)?a+Array(c-
|
|
||||||
a.length+1).join(" "):Array(c-a.length+1).join(" ")+a},f:function(a,b,c,d,e){d=a.toString();isNaN(e)||""==e||(d=a.toFixed(e));var f;f=0>a?"-":0<=b.indexOf("+")?"+":0<=b.indexOf(" ")?" ":"";0<=a&&(d=f+d);if(isNaN(c)||d.length>=c)return d;d=isNaN(e)?Math.abs(a).toString():Math.abs(a).toFixed(e);a=c-d.length-f.length;return d=0<=b.indexOf("-",0)?f+d+Array(a+1).join(" "):f+Array(a+1).join(0<=b.indexOf("0",0)?"0":" ")+d},d:function(a,b,c,d,e,f,k,l){return B.f(parseInt(a,10),b,c,d,0,f,k,l)}};B.i=B.d;
|
|
||||||
B.u=B.d;var Sa=function(a){if("function"==typeof a.t)return a.t();if(n(a))return a.split("");if(da(a)){for(var b=[],c=a.length,d=0;d<c;d++)b.push(a[d]);return b}return va(a)},Ta=function(a,b){if("function"==typeof a.forEach)a.forEach(b,void 0);else if(da(a)||n(a))ka(a,b,void 0);else{var c;if("function"==typeof a.F)c=a.F();else if("function"!=typeof a.t)if(da(a)||n(a)){c=[];for(var d=a.length,e=0;e<d;e++)c.push(e)}else c=wa(a);else c=void 0;for(var d=Sa(a),e=d.length,f=0;f<e;f++)b.call(void 0,d[f],c&&c[f],
|
|
||||||
a)}};var C=function(a){this.B=new x;for(var b=arguments,c=0;c<b.length;c+=2)this.set(b[c],b[c+1])};C.prototype.set=function(a,b){this.B.set(a.name,{key:a,value:b})};C.prototype.remove=function(a){this.B.remove(a.name)};C.prototype.get=function(a){a=this.B.get(a.name,null);return null===a?null:a.value};C.prototype.ia=function(a){this.B.ia(a.B)};var Ua=function(a,b){ka(a.B.t(),function(a){b(a.key,a.value)})};C.prototype.Jb=function(){var a={};Ua(this,function(b,c){a[b.id]=c});return a};
|
|
||||||
C.prototype.clone=function(){var a=new C;a.B=this.B.clone();return a};C.prototype.toString=function(){var a={};Ua(this,function(b,c){a[b.id]=c});return JSON.stringify(a)};var D=function(a){this.e=a};g=D.prototype;g.xc=function(a){var b=new D(r(this.P,this));b.I=Ga;b.N=a;return b};g.action=function(a){var b=new D(r(this.P,this));b.I=Ha;b.N=a;return b};g.label=function(a){var b=new D(r(this.P,this));b.I=Ia;b.N=a;return b};g.value=function(a){var b=new D(r(this.P,this));b.I=Ja;b.N=a;return b};g.yc=function(a,b){var c=new D(r(this.P,this));c.I=La(a);c.N=b;return c};g.Dc=function(a,b){var c=new D(r(this.P,this));c.I=Ma(a);c.N=b;return c};
|
|
||||||
g.send=function(a){var b=new C;this.P(b);return a.send("event",b)};g.P=function(a){null!=this.I&&null!=this.N&&!a.B.Q(this.I.name)&&a.set(this.I,this.N);q(this.e)&&this.e(a)};var Va=new D(ba);var E=function(){this.Y=this.Y;this.Da=this.Da};E.prototype.Y=!1;E.prototype.xa=function(){this.Y||(this.Y=!0,this.l())};E.prototype.l=function(){if(this.Da)for(;this.Da.length;)this.Da.shift()()};var F=function(a,b){this.type=a;this.currentTarget=this.target=b;this.defaultPrevented=this.U=!1;this.kb=!0};F.prototype.l=function(){};F.prototype.xa=function(){};F.prototype.preventDefault=function(){this.defaultPrevented=!0;this.kb=!1};var Wa=function(a){Wa[" "](a);return a};Wa[" "]=ba;var G;t:{var Xa=h.navigator;if(Xa){var Ya=Xa.userAgent;if(Ya){G=Ya;break t}}G=""}var H=function(a){return-1!=G.indexOf(a)};var Za=H("Opera")||H("OPR"),I=H("Trident")||H("MSIE"),K=H("Gecko")&&-1==G.toLowerCase().indexOf("webkit")&&!(H("Trident")||H("MSIE")),L=-1!=G.toLowerCase().indexOf("webkit"),$a=function(){var a=h.document;return a?a.documentMode:void 0},ab=function(){var a="",b;if(Za&&h.opera)return a=h.opera.version,p(a)?a():a;K?b=/rv\:([^\);]+)(\)|;)/:I?b=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:L&&(b=/WebKit\/(\S+)/);b&&(a=(a=b.exec(G))?a[1]:"");return I&&(b=$a(),b>parseFloat(a))?String(b):a}(),bb={},M=function(a){var b;
|
|
||||||
if(!(b=bb[a])){b=0;for(var c=String(ab).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),d=String(a).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),e=Math.max(c.length,d.length),f=0;0==b&&f<e;f++){var k=c[f]||"",l=d[f]||"",N=RegExp("(\\d*)(\\D*)","g"),J=RegExp("(\\d*)(\\D*)","g");do{var U=N.exec(k)||["","",""],V=J.exec(l)||["","",""];if(0==U[0].length&&0==V[0].length)break;b=ia(0==U[1].length?0:parseInt(U[1],10),0==V[1].length?0:parseInt(V[1],10))||ia(0==U[2].length,0==V[2].length)||ia(U[2],V[2])}while(0==
|
|
||||||
b)}b=bb[a]=0<=b}return b},cb=h.document,db=cb&&I?$a()||("CSS1Compat"==cb.compatMode?parseInt(ab,10):5):void 0;var eb=!I||I&&9<=db,fb=I&&!M("9"),gb=!L||M("528"),hb=K&&M("1.9b")||I&&M("8")||Za&&M("9.5")||L&&M("528"),ib=K&&!M("8")||I&&!M("9");var O=function(a,b){F.call(this,a?a.type:"");this.relatedTarget=this.currentTarget=this.target=null;this.charCode=this.keyCode=this.button=this.screenY=this.screenX=this.clientY=this.clientX=this.offsetY=this.offsetX=0;this.metaKey=this.shiftKey=this.altKey=this.ctrlKey=!1;this.Db=this.state=null;if(a){var c=this.type=a.type;this.target=a.target||a.srcElement;this.currentTarget=b;var d=a.relatedTarget;if(d){if(K){var e;t:{try{Wa(d.nodeName);e=!0;break t}catch(f){}e=!1}e||(d=null)}}else"mouseover"==
|
|
||||||
c?d=a.fromElement:"mouseout"==c&&(d=a.toElement);this.relatedTarget=d;this.offsetX=L||void 0!==a.offsetX?a.offsetX:a.layerX;this.offsetY=L||void 0!==a.offsetY?a.offsetY:a.layerY;this.clientX=void 0!==a.clientX?a.clientX:a.pageX;this.clientY=void 0!==a.clientY?a.clientY:a.pageY;this.screenX=a.screenX||0;this.screenY=a.screenY||0;this.button=a.button;this.keyCode=a.keyCode||0;this.charCode=a.charCode||("keypress"==c?a.keyCode:0);this.ctrlKey=a.ctrlKey;this.altKey=a.altKey;this.shiftKey=a.shiftKey;this.metaKey=
|
|
||||||
a.metaKey;this.state=a.state;this.Db=a;a.defaultPrevented&&this.preventDefault()}};u(O,F);O.prototype.preventDefault=function(){O.L.preventDefault.call(this);var a=this.Db;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,fb)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};O.prototype.l=function(){};var jb="closure_listenable_"+(1E6*Math.random()|0),kb=function(a){return!(!a||!a[jb])},lb=0;var mb=function(a,b,c,d,e){this.O=a;this.proxy=null;this.src=b;this.type=c;this.pa=!!d;this.sa=e;this.key=++lb;this.removed=this.qa=!1},nb=function(a){a.removed=!0;a.O=null;a.proxy=null;a.src=null;a.sa=null};var P=function(a){this.src=a;this.j={};this.Z=0};P.prototype.add=function(a,b,c,d,e){var f=a.toString();a=this.j[f];a||(a=this.j[f]=[],this.Z++);var k=ob(a,b,d,e);-1<k?(b=a[k],c||(b.qa=!1)):(b=new mb(b,this.src,f,!!d,e),b.qa=c,a.push(b));return b};P.prototype.remove=function(a,b,c,d){a=a.toString();if(!(a in this.j))return!1;var e=this.j[a];b=ob(e,b,c,d);return-1<b?(nb(e[b]),w.splice.call(e,b,1),0==e.length&&(delete this.j[a],this.Z--),!0):!1};
|
|
||||||
var pb=function(a,b){var c=b.type;if(!(c in a.j))return!1;var d=pa(a.j[c],b);d&&(nb(b),0==a.j[c].length&&(delete a.j[c],a.Z--));return d};P.prototype.removeAll=function(a){a=a&&a.toString();var b=0,c;for(c in this.j)if(!a||c==a){for(var d=this.j[c],e=0;e<d.length;e++)++b,nb(d[e]);delete this.j[c];this.Z--}return b};P.prototype.X=function(a,b,c,d){a=this.j[a.toString()];var e=-1;a&&(e=ob(a,b,c,d));return-1<e?a[e]:null};
|
|
||||||
var ob=function(a,b,c,d){for(var e=0;e<a.length;++e){var f=a[e];if(!f.removed&&f.O==b&&f.pa==!!c&&f.sa==d)return e}return-1};var qb="closure_lm_"+(1E6*Math.random()|0),rb={},sb=0,tb=function(a,b,c,d,e){if(m(b)){for(var f=0;f<b.length;f++)tb(a,b[f],c,d,e);return null}c=ub(c);return kb(a)?a.listen(b,c,d,e):vb(a,b,c,!1,d,e)},vb=function(a,b,c,d,e,f){if(!b)throw Error("Invalid event type");var k=!!e,l=wb(a);l||(a[qb]=l=new P(a));c=l.add(b,c,d,e,f);if(c.proxy)return c;d=xb();c.proxy=d;d.src=a;d.O=c;a.addEventListener?a.addEventListener(b.toString(),d,k):a.attachEvent(yb(b.toString()),d);sb++;return c},xb=function(){var a=zb,
|
|
||||||
b=eb?function(c){return a.call(b.src,b.O,c)}:function(c){c=a.call(b.src,b.O,c);if(!c)return c};return b},Ab=function(a,b,c,d,e){if(m(b)){for(var f=0;f<b.length;f++)Ab(a,b[f],c,d,e);return null}c=ub(c);return kb(a)?a.bb(b,c,d,e):vb(a,b,c,!0,d,e)},Bb=function(a,b,c,d,e){if(m(b))for(var f=0;f<b.length;f++)Bb(a,b[f],c,d,e);else c=ub(c),kb(a)?a.Va(b,c,d,e):a&&(a=wb(a))&&(b=a.X(b,c,!!d,e))&&Cb(b)},Cb=function(a){if(ea(a)||!a||a.removed)return!1;var b=a.src;if(kb(b))return pb(b.A,a);var c=a.type,d=a.proxy;
|
|
||||||
b.removeEventListener?b.removeEventListener(c,d,a.pa):b.detachEvent&&b.detachEvent(yb(c),d);sb--;(c=wb(b))?(pb(c,a),0==c.Z&&(c.src=null,b[qb]=null)):nb(a);return!0},yb=function(a){return a in rb?rb[a]:rb[a]="on"+a},Fb=function(a,b,c,d){var e=1;if(a=wb(a))if(b=a.j[b.toString()])for(b=b.concat(),a=0;a<b.length;a++){var f=b[a];f&&f.pa==c&&!f.removed&&(e&=!1!==Eb(f,d))}return Boolean(e)},Eb=function(a,b){var c=a.O,d=a.sa||a.src;a.qa&&Cb(a);return c.call(d,b)},zb=function(a,b){if(a.removed)return!0;if(!eb){var c;
|
|
||||||
if(!(c=b))t:{c=["window","event"];for(var d=h,e;e=c.shift();)if(null!=d[e])d=d[e];else{c=null;break t}c=d}e=c;c=new O(e,this);d=!0;if(!(0>e.keyCode||void 0!=e.returnValue)){t:{var f=!1;if(0==e.keyCode)try{e.keyCode=-1;break t}catch(k){f=!0}if(f||void 0==e.returnValue)e.returnValue=!0}e=[];for(f=c.currentTarget;f;f=f.parentNode)e.push(f);for(var f=a.type,l=e.length-1;!c.U&&0<=l;l--)c.currentTarget=e[l],d&=Fb(e[l],f,!0,c);for(l=0;!c.U&&l<e.length;l++)c.currentTarget=e[l],d&=Fb(e[l],f,!1,c)}return d}return Eb(a,
|
|
||||||
new O(b,this))},wb=function(a){a=a[qb];return a instanceof P?a:null},Gb="__closure_events_fn_"+(1E9*Math.random()>>>0),ub=function(a){if(p(a))return a;a[Gb]||(a[Gb]=function(b){return a.handleEvent(b)});return a[Gb]};var Q=function(){E.call(this);this.A=new P(this);this.kc=this;this.Qa=null};u(Q,E);Q.prototype[jb]=!0;g=Q.prototype;g.addEventListener=function(a,b,c,d){tb(this,a,b,c,d)};g.removeEventListener=function(a,b,c,d){Bb(this,a,b,c,d)};
|
|
||||||
g.dispatchEvent=function(a){var b,c=this.Qa;if(c){b=[];for(var d=1;c;c=c.Qa)b.push(c),++d}c=this.kc;d=a.type||a;if(n(a))a=new F(a,c);else if(a instanceof F)a.target=a.target||c;else{var e=a;a=new F(d,c);za(a,e)}var e=!0,f;if(b)for(var k=b.length-1;!a.U&&0<=k;k--)f=a.currentTarget=b[k],e=Hb(f,d,!0,a)&&e;a.U||(f=a.currentTarget=c,e=Hb(f,d,!0,a)&&e,a.U||(e=Hb(f,d,!1,a)&&e));if(b)for(k=0;!a.U&&k<b.length;k++)f=a.currentTarget=b[k],e=Hb(f,d,!1,a)&&e;return e};
|
|
||||||
g.l=function(){Q.L.l.call(this);this.A&&this.A.removeAll(void 0);this.Qa=null};g.listen=function(a,b,c,d){return this.A.add(String(a),b,!1,c,d)};g.bb=function(a,b,c,d){return this.A.add(String(a),b,!0,c,d)};g.Va=function(a,b,c,d){return this.A.remove(String(a),b,c,d)};var Hb=function(a,b,c,d){b=a.A.j[String(b)];if(!b)return!0;b=b.concat();for(var e=!0,f=0;f<b.length;++f){var k=b[f];if(k&&!k.removed&&k.pa==c){var l=k.O,N=k.sa||k.src;k.qa&&pb(a.A,k);e=!1!==l.call(N,d)&&e}}return e&&0!=d.kb};
|
|
||||||
Q.prototype.X=function(a,b,c,d){return this.A.X(String(a),b,c,d)};var Ib=function(a){h.setTimeout(function(){throw a;},0)},Jb,Kb=function(){var a=h.MessageChannel;"undefined"===typeof a&&"undefined"!==typeof window&&window.postMessage&&window.addEventListener&&(a=function(){var a=document.createElement("iframe");a.style.display="none";a.src="";document.documentElement.appendChild(a);var b=a.contentWindow,a=b.document;a.open();a.write("");a.close();var c="callImmediate"+Math.random(),d="file:"==b.location.protocol?"*":b.location.protocol+"//"+b.location.host,a=r(function(a){if(a.origin==
|
|
||||||
d||a.data==c)this.port1.onmessage()},this);b.addEventListener("message",a,!1);this.port1={};this.port2={postMessage:function(){b.postMessage(c,d)}}});if("undefined"!==typeof a&&!H("Trident")&&!H("MSIE")){var b=new a,c={},d=c;b.port1.onmessage=function(){c=c.next;var a=c.Fb;c.Fb=null;a()};return function(a){d.next={Fb:a};d=d.next;b.port2.postMessage(0)}}return"undefined"!==typeof document&&"onreadystatechange"in document.createElement("script")?function(a){var b=document.createElement("script");b.onreadystatechange=
|
|
||||||
function(){b.onreadystatechange=null;b.parentNode.removeChild(b);b=null;a();a=null};document.documentElement.appendChild(b)}:function(a){h.setTimeout(a,0)}};var Qb=function(a,b){Lb||Mb();Nb||(Lb(),Nb=!0);Ob.push(new Pb(a,b))},Lb,Mb=function(){if(h.Promise&&h.Promise.resolve){var a=h.Promise.resolve();Lb=function(){a.then(Rb)}}else Lb=function(){var a=Rb;!p(h.setImmediate)||h.Window&&h.Window.prototype.setImmediate==h.setImmediate?(Jb||(Jb=Kb()),Jb(a)):h.setImmediate(a)}},Nb=!1,Ob=[],Rb=function(){for(;Ob.length;){var a=Ob;Ob=[];for(var b=0;b<a.length;b++){var c=a[b];try{c.zc.call(c.scope)}catch(d){Ib(d)}}}Nb=!1},Pb=function(a,b){this.zc=a;this.scope=
|
|
||||||
b};var Sb=function(a){a.prototype.then=a.prototype.then;a.prototype.$goog_Thenable=!0},Tb=function(a){if(!a)return!1;try{return!!a.$goog_Thenable}catch(b){return!1}};var R=function(a,b){this.m=0;this.v=void 0;this.n=this.o=null;this.ua=this.La=!1;try{var c=this;a.call(b,function(a){Ub(c,2,a)},function(a){Ub(c,3,a)})}catch(d){Ub(this,3,d)}};R.prototype.then=function(a,b,c){return Vb(this,p(a)?a:null,p(b)?b:null,c)};Sb(R);R.prototype.cancel=function(a){0==this.m&&Qb(function(){var b=new Wb(a);Xb(this,b)},this)};
|
|
||||||
var Xb=function(a,b){if(0==a.m)if(a.o){var c=a.o;if(c.n){for(var d=0,e=-1,f=0,k;k=c.n[f];f++)if(k=k.wa)if(d++,k==a&&(e=f),0<=e&&1<d)break;0<=e&&(0==c.m&&1==d?Xb(c,b):(d=c.n.splice(e,1)[0],Yb(c),d.Ma(b)))}}else Ub(a,3,b)},$b=function(a,b){a.n&&a.n.length||2!=a.m&&3!=a.m||Zb(a);a.n||(a.n=[]);a.n.push(b)},Vb=function(a,b,c,d){var e={wa:null,jb:null,Ma:null};e.wa=new R(function(a,k){e.jb=b?function(c){try{var e=b.call(d,c);a(e)}catch(J){k(J)}}:a;e.Ma=c?function(b){try{var e=c.call(d,b);void 0===e&&b instanceof
|
|
||||||
Wb?k(b):a(e)}catch(J){k(J)}}:k});e.wa.o=a;$b(a,e);return e.wa};R.prototype.vb=function(a){this.m=0;Ub(this,2,a)};R.prototype.wb=function(a){this.m=0;Ub(this,3,a)};
|
|
||||||
var Ub=function(a,b,c){if(0==a.m){if(a==c)b=3,c=new TypeError("Promise cannot resolve to itself");else{if(Tb(c)){a.m=1;c.then(a.vb,a.wb,a);return}if(q(c))try{var d=c.then;if(p(d)){ac(a,c,d);return}}catch(e){b=3,c=e}}a.v=c;a.m=b;Zb(a);3!=b||c instanceof Wb||bc(a,c)}},ac=function(a,b,c){a.m=1;var d=!1,e=function(b){d||(d=!0,a.vb(b))},f=function(b){d||(d=!0,a.wb(b))};try{c.call(b,e,f)}catch(k){f(k)}},Zb=function(a){a.La||(a.La=!0,Qb(a.uc,a))};
|
|
||||||
R.prototype.uc=function(){for(;this.n&&this.n.length;){var a=this.n;this.n=[];for(var b=0;b<a.length;b++){var c=a[b],d=this.v;2==this.m?c.jb(d):(Yb(this),c.Ma(d))}}this.La=!1};var Yb=function(a){for(;a&&a.ua;a=a.o)a.ua=!1},bc=function(a,b){a.ua=!0;Qb(function(){a.ua&&cc.call(null,b)})},cc=Ib,Wb=function(a){v.call(this,a)};u(Wb,v);Wb.prototype.name="cancel";/*
|
|
||||||
Portions of this code are from MochiKit, received by
|
|
||||||
The Closure Authors under the MIT license. All other code is Copyright
|
|
||||||
2005-2009 The Closure Authors. All Rights Reserved.
|
|
||||||
*/
|
|
||||||
var S=function(a,b){this.ja=[];this.hb=a;this.gb=b||null;this.W=this.C=!1;this.v=void 0;this.Ka=this.Lb=this.Ja=!1;this.ka=0;this.o=null;this.Ia=0};S.prototype.cancel=function(a){if(this.C)this.v instanceof S&&this.v.cancel();else{if(this.o){var b=this.o;delete this.o;a?b.cancel(a):(b.Ia--,0>=b.Ia&&b.cancel())}this.hb?this.hb.call(this.gb,this):this.Ka=!0;this.C||this.w(new dc)}};S.prototype.ib=function(a,b){this.Ja=!1;ec(this,a,b)};
|
|
||||||
var ec=function(a,b,c){a.C=!0;a.v=c;a.W=!b;fc(a)},hc=function(a){if(a.C){if(!a.Ka)throw new gc;a.Ka=!1}};S.prototype.G=function(a){hc(this);ec(this,!0,a)};S.prototype.w=function(a){hc(this);ec(this,!1,a)};S.prototype.J=function(a,b){return ic(this,a,null,b)};var ic=function(a,b,c,d){a.ja.push([b,c,d]);a.C&&fc(a);return a};S.prototype.then=function(a,b,c){var d,e,f=new R(function(a,b){d=a;e=b});ic(this,d,function(a){a instanceof dc?f.cancel():e(a)});return f.then(a,b,c)};Sb(S);
|
|
||||||
var jc=function(a){var b=new S;ic(a,b.G,b.w,b);return b},kc=function(a){return la(a.ja,function(a){return p(a[1])})},fc=function(a){if(a.ka&&a.C&&kc(a)){var b=a.ka,c=lc[b];c&&(h.clearTimeout(c.ma),delete lc[b]);a.ka=0}a.o&&(a.o.Ia--,delete a.o);for(var b=a.v,d=c=!1;a.ja.length&&!a.Ja;){var e=a.ja.shift(),f=e[0],k=e[1],e=e[2];if(f=a.W?k:f)try{var l=f.call(e||a.gb,b);void 0!==l&&(a.W=a.W&&(l==b||l instanceof Error),a.v=b=l);Tb(b)&&(d=!0,a.Ja=!0)}catch(N){b=N,a.W=!0,kc(a)||(c=!0)}}a.v=b;d&&(l=r(a.ib,
|
|
||||||
a,!0),d=r(a.ib,a,!1),b instanceof S?(ic(b,l,d),b.Lb=!0):b.then(l,d));c&&(b=new mc(b),lc[b.ma]=b,a.ka=b.ma)},nc=function(a){var b=new S;b.G(a);return b},pc=function(){var a=oc,b=new S;b.w(a);return b},gc=function(){v.call(this)};u(gc,v);gc.prototype.message="Deferred has already fired";gc.prototype.name="AlreadyCalledError";var dc=function(){v.call(this)};u(dc,v);dc.prototype.message="Deferred was canceled";dc.prototype.name="CanceledError";
|
|
||||||
var mc=function(a){this.ma=h.setTimeout(r(this.pc,this),0);this.ga=a};mc.prototype.pc=function(){delete lc[this.ma];throw this.ga;};var lc={};var qc=function(a){this.$a=[];this.e=a};qc.prototype.S=function(a){if(!p(a))throw Error("Invalid filter. Must be a function.");this.$a.push(a)};qc.prototype.send=function(a,b){for(var c=new T(a,b),d=0;d<this.$a.length&&(this.$a[d](c),!c.Za);d++);return c.Za?nc():this.e.send(a,b)};var T=function(a,b){this.rc=a;this.qc=b;this.Za=!1};T.prototype.Gb=function(){return this.rc};T.prototype.T=function(){return this.qc};T.prototype.cancel=function(){this.Za=!0};var rc=function(a,b){this.width=a;this.height=b};rc.prototype.clone=function(){return new rc(this.width,this.height)};rc.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};!K&&!I||I&&I&&9<=db||K&&M("1.9.1");I&&M("9");var sc={id:"apiVersion",name:"v",valueType:"text",maxLength:void 0,defaultValue:void 0},tc={id:"appName",name:"an",valueType:"text",maxLength:100,defaultValue:void 0},uc={id:"appVersion",name:"av",valueType:"text",maxLength:100,defaultValue:void 0},vc={id:"clientId",name:"cid",valueType:"text",maxLength:void 0,defaultValue:void 0},wc={id:"language",name:"ul",valueType:"text",maxLength:20,defaultValue:void 0},xc={id:"libVersion",name:"_v",valueType:"text",maxLength:void 0,defaultValue:void 0},yc={id:"sampleRateOverride",
|
|
||||||
name:"usro",valueType:"integer",maxLength:void 0,defaultValue:void 0},zc={id:"screenColors",name:"sd",valueType:"text",maxLength:20,defaultValue:void 0},Ac={id:"screenResolution",name:"sr",valueType:"text",maxLength:20,defaultValue:void 0},Bc={id:"trackingId",name:"tid",valueType:"text",maxLength:void 0,defaultValue:void 0},Cc={id:"viewportSize",name:"vp",valueType:"text",maxLength:20,defaultValue:void 0},Dc={Rc:sc,Uc:tc,Vc:uc,cd:vc,vd:wc,wd:xc,Cd:yc,Dd:zc,Ed:Ac,Qd:Bc,Xd:Cc},Fc=function(a){if(!n(a))return a;
|
|
||||||
var b=Ec(a,Ka);if(q(b))return b;b=Ec(a,Dc);if(q(b))return b;b=/^dimension(\d+)$/.exec(a);if(null!==b)return La(parseInt(b[1],10));b=/^metric(\d+)$/.exec(a);if(null!==b)return Ma(parseInt(b[1],10));throw Error(a+" is not a valid parameter name.");},Ec=function(a,b){var c=xa(b,function(b){return b.id==a&&"metric"!=a&&"dimension"!=a});return q(c)?c:null};var W=function(a,b){this.Zb=b;this.q=b.Sa();this.sb=new C;this.Ya=!1};g=W.prototype;g.set=function(a,b){var c=Fc(a);this.sb.set(c,b)};g.S=function(a){this.Zb.S(a)};g.send=function(a,b){if(a instanceof D)return a.send(this);var c=this.sb.clone();b instanceof C?c.ia(b):q(b)&&ua(b,function(a,b){null!=a&&c.set(Fc(b),a)},this);this.Ya&&(this.Ya=!1,c.set(Ea,"start"));return this.q.send(a,c)};g.Gc=function(a){var b={description:a};this.set(Fa,a);return this.send("appview",b)};
|
|
||||||
g.Hc=function(a,b,c,d){return this.send("event",{eventCategory:a,eventAction:b,eventLabel:c,eventValue:d})};g.Jc=function(a,b,c){return this.send("social",{socialNetwork:a,socialAction:b,socialTarget:c})};g.Ic=function(a,b){return this.send("exception",{exDescription:a,exFatal:b})};g.Cb=function(a,b,c,d,e){return this.send("timing",{timingCategory:a,timingVar:b,timingLabel:d,timingValue:c,sampleRateOverride:e})};g.Ac=function(){this.Ya=!0};g.Mc=function(a,b,c,d){return new Gc(this,a,b,c,d)};
|
|
||||||
var Gc=function(a,b,c,d,e){this.yb=a;this.bc=b;this.ec=c;this.cc=d;this.V=e;this.dc=s()};Gc.prototype.send=function(){var a=this.yb.Cb(this.bc,this.ec,s()-this.dc,this.cc,this.V);this.yb=null;return a};var Hc=function(a,b,c,d,e){this.ic=a;this.fc=b;this.gc=c;this.k=d;this.hc=e};
|
|
||||||
Hc.prototype.Cc=function(a){var b=new W(0,this.hc.create());b.set(xc,this.ic);b.set(sc,1);b.set(tc,this.fc);b.set(uc,this.gc);b.set(Bc,a);a=window.navigator.language;b.set(wc,a);a=screen.colorDepth+"-bit";b.set(zc,a);a=[screen.width,screen.height].join("x");b.set(Ac,a);a=window.document;a="CSS1Compat"==a.compatMode?a.documentElement:a.body;a=new rc(a.clientWidth,a.clientHeight);a=[a.width,a.height].join("x");b.set(Cc,a);return b};Hc.prototype.Bc=function(){return jc(this.k.ha)};var Ic=function(a){this.sc=a};Ic.prototype.send=function(a,b){this.sc.push({Ub:a,Vb:b});return nc()};var Jc=function(a,b,c){this.k=a;this.ra=[];this.M={enabled:new Ic(this.ra),disabled:c};this.q=this.M.enabled;ic(jc(this.k.ha),ha(this.Pb,b),this.Ob,this)};Jc.prototype.Pb=function(a){this.M.enabled=a();Kc(this);ka(this.ra,function(a){this.send(a.Ub,a.Vb)},this);this.ra=null;Lc(this.k,r(this.Xb,this))};Jc.prototype.Ob=function(){this.q=this.M.enabled=this.M.disabled;this.ra=null};Jc.prototype.send=function(a,b){return this.q.send(a,b)};var Kc=function(a){a.q=a.k.va()?a.M.enabled:a.M.disabled};
|
|
||||||
Jc.prototype.Xb=function(a){switch(a){case "analytics.tracking-permitted":Kc(this)}};var Mc=function(a,b,c,d,e,f){S.call(this,e,f);this.Na=a;this.Oa=[];this.lb=!!b;this.Nb=!!c;this.Mb=!!d;for(b=this.mb=0;b<a.length;b++)ic(a[b],r(this.rb,this,b,!0),r(this.rb,this,b,!1));0!=a.length||this.lb||this.G(this.Oa)};u(Mc,S);Mc.prototype.rb=function(a,b,c){this.mb++;this.Oa[a]=[b,c];this.C||(this.lb&&b?this.G([a,c]):this.Nb&&!b?this.w(c):this.mb==this.Na.length&&this.G(this.Oa));this.Mb&&!b&&(c=null);return c};Mc.prototype.w=function(a){Mc.L.w.call(this,a);for(a=0;a<this.Na.length;a++)this.Na[a].cancel()};
|
|
||||||
var Nc=function(a){return(new Mc(a,!1,!0)).J(function(a){for(var c=[],d=0;d<a.length;d++)c[d]=a[d][1];return c})};var X=function(a){this.H=a;this.V=100;this.nb=[];this.Pa=this.la=null;this.ha=Oc(this);this.ha.J(function(){tb(this.H,"a",r(this.Rb,this))},this)},Oc=function(a){return Pc(a).J(function(){return this},a)},Pc=function(a){return Nc([Qc(a),Rc(a)])};X.prototype.Rb=function(){var a=this.la,b=this.va();Pc(this).J(function(){if(a!=this.la)throw Error("User ID changed unexpectedly!");b!=this.va()&&Sc(this)},this)};var Lc=function(a,b){a.nb.push(b)};
|
|
||||||
X.prototype.Lc=function(a){this.H.set("analytics.tracking-permitted",a).J(function(){this.Pa=a},this)};X.prototype.va=function(){var a;if(a=this.Pa)a=h._gaUserPrefs,a=!(a&&a.ioo&&a.ioo());return a};
|
|
||||||
var Qc=function(a){return a.H.get("analytics.tracking-permitted").J(function(a){this.Pa=void 0!==a?a:!0},a)},Rc=function(a){return a.H.get("analytics.user-id").J(function(a){if(!a){a="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".split("");for(var c=0,d=a.length;c<d;c++)switch(a[c]){case "x":a[c]=Math.floor(16*Math.random()).toString(16);break;case "y":a[c]=(Math.floor(4*Math.random())+8).toString(16)}a=a.join("");this.H.set("analytics.user-id",a)}this.la=a},a)};X.prototype.Kc=function(a){this.V=a};
|
|
||||||
var Sc=function(a){ka(a.nb,function(a){a("analytics.tracking-permitted")})};var Tc=function(a){Q.call(this);this.Wa=a;this.H=chrome.storage.local;chrome.storage.onChanged.addListener(r(this.nc,this))};u(Tc,Q);Tc.prototype.nc=function(a){Uc(this,a)&&this.dispatchEvent("a")};var Uc=function(a,b){return la(wa(b),function(a){return 0==a.lastIndexOf(this.Wa,0)},a)};Tc.prototype.get=function(a){var b=new S,c=this.Wa+"."+a;this.H.get(c,function(a){var e=chrome.runtime.lastError;e?b.w(e):b.G(a[c])});return b};
|
|
||||||
Tc.prototype.set=function(a,b){var c=new S,d={};d[this.Wa+"."+a]=b;this.H.set(d,function(){var a=chrome.runtime.lastError;a?c.w(a):c.G()});return c};var Y=function(){};Y.Yb=function(){return Y.Ib?Y.Ib:Y.Ib=new Y};Y.prototype.send=function(){return nc()};var Vc=function(a,b){this.Xa=[];var c=r(function(){this.Aa=new qc(b.Sa());ka(this.Xa,function(a){this.Aa.S(a)},this);this.Xa=null;return this.Aa},this);this.q=new Jc(a,c,Y.Yb())};Vc.prototype.Sa=function(){return this.q};Vc.prototype.S=function(a){this.Aa?this.Aa.S(a):this.Xa.push(a)};var Wc=function(a,b){this.k=a;this.mc=b};Wc.prototype.create=function(){return new Vc(this.k,this.mc)};var Xc=function(a,b){Q.call(this);this.ya=a||1;this.R=b||h;this.Ra=r(this.lc,this);this.Ta=s()};u(Xc,Q);g=Xc.prototype;g.enabled=!1;g.g=null;g.lc=function(){if(this.enabled){var a=s()-this.Ta;0<a&&a<.8*this.ya?this.g=this.R.setTimeout(this.Ra,this.ya-a):(this.g&&(this.R.clearTimeout(this.g),this.g=null),this.dispatchEvent("tick"),this.enabled&&(this.g=this.R.setTimeout(this.Ra,this.ya),this.Ta=s()))}};g.start=function(){this.enabled=!0;this.g||(this.g=this.R.setTimeout(this.Ra,this.ya),this.Ta=s())};
|
|
||||||
g.stop=function(){this.enabled=!1;this.g&&(this.R.clearTimeout(this.g),this.g=null)};g.l=function(){Xc.L.l.call(this);this.stop();delete this.R};var Yc=function(a,b,c){if(p(a))c&&(a=r(a,c));else if(a&&"function"==typeof a.handleEvent)a=r(a.handleEvent,a);else throw Error("Invalid listener argument");return 2147483647<b?-1:h.setTimeout(a,b||0)};var Z=function(a){E.call(this);this.Ua=a;this.b={}};u(Z,E);var Zc=[];Z.prototype.listen=function(a,b,c,d){m(b)||(b&&(Zc[0]=b.toString()),b=Zc);for(var e=0;e<b.length;e++){var f=tb(a,b[e],c||this.handleEvent,d||!1,this.Ua||this);if(!f)break;this.b[f.key]=f}return this};Z.prototype.bb=function(a,b,c,d){return $c(this,a,b,c,d)};var $c=function(a,b,c,d,e,f){if(m(c))for(var k=0;k<c.length;k++)$c(a,b,c[k],d,e,f);else{b=Ab(b,c,d||a.handleEvent,e,f||a.Ua||a);if(!b)return a;a.b[b.key]=b}return a};
|
|
||||||
Z.prototype.Va=function(a,b,c,d,e){if(m(b))for(var f=0;f<b.length;f++)this.Va(a,b[f],c,d,e);else c=c||this.handleEvent,e=e||this.Ua||this,c=ub(c),d=!!d,b=kb(a)?a.X(b,c,d,e):a?(a=wb(a))?a.X(b,c,d,e):null:null,b&&(Cb(b),delete this.b[b.key]);return this};Z.prototype.removeAll=function(){ua(this.b,Cb);this.b={}};Z.prototype.l=function(){Z.L.l.call(this);this.removeAll()};Z.prototype.handleEvent=function(){throw Error("EventHandler.handleEvent not implemented");};var ad=function(){Q.call(this);this.ta=new Z(this);gb&&(hb?this.ta.listen(ib?document.body:window,["online","offline"],this.tb):(this.ub=gb?navigator.onLine:!0,this.g=new Xc(250),this.ta.listen(this.g,"tick",this.ac),this.g.start()))};u(ad,Q);ad.prototype.ac=function(){var a=gb?navigator.onLine:!0;a!=this.ub&&(this.ub=a,this.tb())};ad.prototype.tb=function(){this.dispatchEvent((gb?navigator.onLine:1)?"online":"offline")};
|
|
||||||
ad.prototype.l=function(){ad.L.l.call(this);this.ta.xa();this.ta=null;this.g&&(this.g.xa(),this.g=null)};var bd=function(a,b){this.k=a;this.e=b};bd.prototype.send=function(a,b){b.set(vc,this.k.la);return this.e.send(a,b)};var cd=function(a){this.e=a};cd.prototype.send=function(a,b){dd(b);ed(b);return this.e.send(a,b)};var dd=function(a){Ua(a,function(b,c){void 0!==b.maxLength&&"text"==b.valueType&&0<b.maxLength&&c.length>b.maxLength&&a.set(b,c.substring(0,b.maxLength))})},ed=function(a){Ua(a,function(b,c){void 0!==b.defaultValue&&c==b.defaultValue&&a.remove(b)})};var oc={status:"device-offline",Ba:void 0},fd={status:"rate-limited",Ba:void 0},gd={status:"sampled-out",Ba:void 0},hd={status:"sent",Ba:void 0};var id=function(a,b){this.Wb=a;this.e=b};id.prototype.send=function(a,b){var c;c=this.Wb;var d=c.pb(),e=Math.floor((d-c.ob)*c.Sb);0<e&&(c.$=Math.min(c.$+e,c.Tb),c.ob=d);1>c.$?c=!1:(c.$-=1,c=!0);return c||"item"==a||"transaction"==a?this.e.send(a,b):nc(fd)};var jd=function(){this.$=60;this.Tb=500;this.Sb=5E-4;this.pb=function(){return(new Date).getTime()};this.ob=this.pb()};var kd=function(a,b){this.k=a;this.e=b};kd.prototype.send=function(a,b){var c=b.get(vc),c=parseInt(c.split("-")[1],16),d;"timing"!=a?d=this.k.V:((d=b.get(yc))&&b.remove(yc),d||(d=this.k.V));return c<655.36*d?this.e.send(a,b):nc(gd)};var ld=/^(?:([^:/?#.]+):)?(?:\/\/(?:([^/?#]*)@)?([^/#?]*?)(?::([0-9]+))?(?=[/#?]|$))?([^?#]+)?(?:\?([^#]*))?(?:#(.*))?$/,md=L,nd=function(a,b){if(md){md=!1;var c=h.location;if(c){var d=c.href;if(d&&(d=(d=nd(3,d))?decodeURI(d):d)&&d!=c.hostname)throw md=!0,Error();}}return b.match(ld)[a]||null};var od=function(){};od.prototype.Eb=null;var qd=function(a){var b;(b=a.Eb)||(b={},pd(a)&&(b[0]=!0,b[1]=!0),b=a.Eb=b);return b};var rd,sd=function(){};u(sd,od);var td=function(a){return(a=pd(a))?new ActiveXObject(a):new XMLHttpRequest},pd=function(a){if(!a.Hb&&"undefined"==typeof XMLHttpRequest&&"undefined"!=typeof ActiveXObject){for(var b=["MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"],c=0;c<b.length;c++){var d=b[c];try{return new ActiveXObject(d),a.Hb=d}catch(e){}}throw Error("Could not create ActiveXObject. ActiveX might be disabled, or MSXML might not be installed");}return a.Hb};rd=new sd;var $=function(a){Q.call(this);this.headers=new x;this.fa=a||null;this.D=!1;this.ca=this.a=null;this.ba=this.Fa="";this.K=this.Ea=this.aa=this.Ha=!1;this.ea=0;this.da=null;this.cb="";this.Ga=this.Kb=!1};u($,Q);var ud=/^https?$/i,vd=["POST","PUT"],wd=[],xd=function(a,b,c){var d=new $;wd.push(d);b&&d.listen("complete",b);d.bb("ready",d.tc);d.send(a,"POST",c,void 0)};$.prototype.tc=function(){this.xa();pa(wd,this)};
|
|
||||||
$.prototype.send=function(a,b,c,d){if(this.a)throw Error("[goog.net.XhrIo] Object is active with another request="+this.Fa+"; newUri="+a);b=b?b.toUpperCase():"GET";this.Fa=a;this.ba="";this.Ha=!1;this.D=!0;this.a=this.fa?td(this.fa):td(rd);this.ca=this.fa?qd(this.fa):qd(rd);this.a.onreadystatechange=r(this.eb,this);try{this.Ea=!0,this.a.open(b,String(a),!0),this.Ea=!1}catch(e){this.ga(5,e);return}a=c||"";var f=this.headers.clone();d&&Ta(d,function(a,b){f.set(b,a)});d=oa(f.F());c=h.FormData&&a instanceof
|
|
||||||
h.FormData;!(0<=ja(vd,b))||d||c||f.set("Content-Type","application/x-www-form-urlencoded;charset=utf-8");f.forEach(function(a,b){this.a.setRequestHeader(b,a)},this);this.cb&&(this.a.responseType=this.cb);"withCredentials"in this.a&&(this.a.withCredentials=this.Kb);try{yd(this),0<this.ea&&((this.Ga=zd(this.a))?(this.a.timeout=this.ea,this.a.ontimeout=r(this.fb,this)):this.da=Yc(this.fb,this.ea,this)),this.aa=!0,this.a.send(a),this.aa=!1}catch(k){this.ga(5,k)}};
|
|
||||||
var zd=function(a){return I&&M(9)&&ea(a.timeout)&&void 0!==a.ontimeout},na=function(a){return"content-type"==a.toLowerCase()};$.prototype.fb=function(){"undefined"!=typeof aa&&this.a&&(this.ba="Timed out after "+this.ea+"ms, aborting",this.dispatchEvent("timeout"),this.abort(8))};$.prototype.ga=function(a,b){this.D=!1;this.a&&(this.K=!0,this.a.abort(),this.K=!1);this.ba=b;Ad(this);Bd(this)};var Ad=function(a){a.Ha||(a.Ha=!0,a.dispatchEvent("complete"),a.dispatchEvent("error"))};
|
|
||||||
$.prototype.abort=function(){this.a&&this.D&&(this.D=!1,this.K=!0,this.a.abort(),this.K=!1,this.dispatchEvent("complete"),this.dispatchEvent("abort"),Bd(this))};$.prototype.l=function(){this.a&&(this.D&&(this.D=!1,this.K=!0,this.a.abort(),this.K=!1),Bd(this,!0));$.L.l.call(this)};$.prototype.eb=function(){this.Y||(this.Ea||this.aa||this.K?Cd(this):this.jc())};$.prototype.jc=function(){Cd(this)};
|
|
||||||
var Cd=function(a){if(a.D&&"undefined"!=typeof aa&&(!a.ca[1]||4!=Dd(a)||2!=Ed(a)))if(a.aa&&4==Dd(a))Yc(a.eb,0,a);else if(a.dispatchEvent("readystatechange"),4==Dd(a)){a.D=!1;try{var b=Ed(a),c,d;t:switch(b){case 200:case 201:case 202:case 204:case 206:case 304:case 1223:d=!0;break t;default:d=!1}if(!(c=d)){var e;if(e=0===b){var f=nd(1,String(a.Fa));if(!f&&self.location)var k=self.location.protocol,f=k.substr(0,k.length-1);e=!ud.test(f?f.toLowerCase():"")}c=e}if(c)a.dispatchEvent("complete"),a.dispatchEvent("success");
|
|
||||||
else{var l;try{l=2<Dd(a)?a.a.statusText:""}catch(N){l=""}a.ba=l+" ["+Ed(a)+"]";Ad(a)}}finally{Bd(a)}}},Bd=function(a,b){if(a.a){yd(a);var c=a.a,d=a.ca[0]?ba:null;a.a=null;a.ca=null;b||a.dispatchEvent("ready");try{c.onreadystatechange=d}catch(e){}}},yd=function(a){a.a&&a.Ga&&(a.a.ontimeout=null);ea(a.da)&&(h.clearTimeout(a.da),a.da=null)},Dd=function(a){return a.a?a.a.readyState:0},Ed=function(a){try{return 2<Dd(a)?a.a.status:-1}catch(b){return-1}};var Fd=function(a,b,c){this.r=a||null;this.oc=!!c},Hd=function(a){if(!a.c&&(a.c=new x,a.h=0,a.r))for(var b=a.r.split("&"),c=0;c<b.length;c++){var d=b[c].indexOf("="),e=null,f=null;0<=d?(e=b[c].substring(0,d),f=b[c].substring(d+1)):e=b[c];e=decodeURIComponent(e.replace(/\+/g," "));e=Gd(a,e);a.add(e,f?decodeURIComponent(f.replace(/\+/g," ")):"")}};g=Fd.prototype;g.c=null;g.h=null;g.add=function(a,b){Hd(this);this.r=null;a=Gd(this,a);var c=this.c.get(a);c||this.c.set(a,c=[]);c.push(b);this.h++;return this};
|
|
||||||
g.remove=function(a){Hd(this);a=Gd(this,a);return this.c.Q(a)?(this.r=null,this.h-=this.c.get(a).length,this.c.remove(a)):!1};g.Q=function(a){Hd(this);a=Gd(this,a);return this.c.Q(a)};g.F=function(){Hd(this);for(var a=this.c.t(),b=this.c.F(),c=[],d=0;d<b.length;d++)for(var e=a[d],f=0;f<e.length;f++)c.push(b[d]);return c};g.t=function(a){Hd(this);var b=[];if(n(a))this.Q(a)&&(b=qa(b,this.c.get(Gd(this,a))));else{a=this.c.t();for(var c=0;c<a.length;c++)b=qa(b,a[c])}return b};
|
|
||||||
g.set=function(a,b){Hd(this);this.r=null;a=Gd(this,a);this.Q(a)&&(this.h-=this.c.get(a).length);this.c.set(a,[b]);this.h++;return this};g.get=function(a,b){var c=a?this.t(a):[];return 0<c.length?String(c[0]):b};g.toString=function(){if(this.r)return this.r;if(!this.c)return"";for(var a=[],b=this.c.F(),c=0;c<b.length;c++)for(var d=b[c],e=encodeURIComponent(String(d)),d=this.t(d),f=0;f<d.length;f++){var k=e;""!==d[f]&&(k+="="+encodeURIComponent(String(d[f])));a.push(k)}return this.r=a.join("&")};
|
|
||||||
g.clone=function(){var a=new Fd;a.r=this.r;this.c&&(a.c=this.c.clone(),a.h=this.h);return a};var Gd=function(a,b){var c=String(b);a.oc&&(c=c.toLowerCase());return c};var Id=function(a,b){this.$b=a;this.na=b};Id.prototype.send=function(a,b){if(gb&&!navigator.onLine)return pc();var c=new S,d=Jd(a,b);d.length>this.na?c.w({status:"payload-too-big",Ba:Ra("Encoded hit length == %s, but should be <= %s.",d.length,this.na)}):xd(this.$b,function(){c.G(hd)},d);return c};var Jd=function(a,b){var c=new Fd;c.add(Da.name,a);Ua(b,function(a,b){c.add(a.name,b.toString())});return c.toString()};var Kd=function(a,b,c){this.k=a;this.Qb=b;this.na=c};Kd.prototype.Sa=function(){if(!this.q){var a=this.k;if(!jc(a.ha).C)throw Error("Cannot construct shared channel prior to settings being ready.");new ad;var b=new cd(new Id(this.Qb,this.na)),c=new jd;this.q=new bd(a,new kd(a,new id(c,b)))}return this.q};var Ld=new x,Md=function(){if(!Ba){var a=new Tc("google-analytics");Ba=new X(a)}return Ba};t("goog.async.Deferred",S);t("goog.async.Deferred.prototype.addCallback",S.prototype.J);t("goog.events.EventTarget",Q);t("goog.events.EventTarget.prototype.listen",Q.prototype.listen);t("analytics.getService",function(a){var b=Ld.get(a,null);if(null===b){var b=chrome.runtime.getManifest().version,c=Md();if(!Ca){var d=Md();Ca=new Wc(d,new Kd(d,"https://www.google-analytics.com/collect",8192))}b=new Hc("ca1.5.2",a,b,c,Ca);Ld.set(a,b)}return b});t("analytics.internal.GoogleAnalyticsService",Hc);
|
|
||||||
t("analytics.internal.GoogleAnalyticsService.prototype.getTracker",Hc.prototype.Cc);t("analytics.internal.GoogleAnalyticsService.prototype.getConfig",Hc.prototype.Bc);t("analytics.internal.ServiceSettings",X);t("analytics.internal.ServiceSettings.prototype.setTrackingPermitted",X.prototype.Lc);t("analytics.internal.ServiceSettings.prototype.isTrackingPermitted",X.prototype.va);t("analytics.internal.ServiceSettings.prototype.setSampleRate",X.prototype.Kc);t("analytics.internal.ServiceTracker",W);
|
|
||||||
t("analytics.internal.ServiceTracker.prototype.send",W.prototype.send);t("analytics.internal.ServiceTracker.prototype.sendAppView",W.prototype.Gc);t("analytics.internal.ServiceTracker.prototype.sendEvent",W.prototype.Hc);t("analytics.internal.ServiceTracker.prototype.sendSocial",W.prototype.Jc);t("analytics.internal.ServiceTracker.prototype.sendException",W.prototype.Ic);t("analytics.internal.ServiceTracker.prototype.sendTiming",W.prototype.Cb);
|
|
||||||
t("analytics.internal.ServiceTracker.prototype.startTiming",W.prototype.Mc);t("analytics.internal.ServiceTracker.Timing",Gc);t("analytics.internal.ServiceTracker.Timing.prototype.send",Gc.prototype.send);t("analytics.internal.ServiceTracker.prototype.forceSessionStart",W.prototype.Ac);t("analytics.internal.ServiceTracker.prototype.addFilter",W.prototype.S);t("analytics.internal.FilterChannel.Hit",T);t("analytics.internal.FilterChannel.Hit.prototype.getHitType",T.prototype.Gb);
|
|
||||||
t("analytics.internal.FilterChannel.Hit.prototype.getParameters",T.prototype.T);t("analytics.internal.FilterChannel.Hit.prototype.cancel",T.prototype.cancel);t("analytics.ParameterMap",C);t("analytics.ParameterMap.Entry",C.Entry);t("analytics.ParameterMap.prototype.set",C.prototype.set);t("analytics.ParameterMap.prototype.get",C.prototype.get);t("analytics.ParameterMap.prototype.remove",C.prototype.remove);t("analytics.ParameterMap.prototype.toObject",C.prototype.Jb);
|
|
||||||
t("analytics.HitTypes.APPVIEW","appview");t("analytics.HitTypes.EVENT","event");t("analytics.HitTypes.SOCIAL","social");t("analytics.HitTypes.TRANSACTION","transaction");t("analytics.HitTypes.ITEM","item");t("analytics.HitTypes.TIMING","timing");t("analytics.HitTypes.EXCEPTION","exception");ua(Ka,function(a){var b=a.id.replace(/[A-Z]/,"_$&").toUpperCase();t("analytics.Parameters."+b,a)});t("analytics.filters.EventLabelerBuilder",A);
|
|
||||||
t("analytics.filters.EventLabelerBuilder.prototype.appendToExistingLabel",A.prototype.wc);t("analytics.filters.EventLabelerBuilder.prototype.stripValue",A.prototype.Nc);t("analytics.filters.EventLabelerBuilder.prototype.powersOfTwo",A.prototype.Ec);t("analytics.filters.EventLabelerBuilder.prototype.rangeBounds",A.prototype.Fc);t("analytics.filters.EventLabelerBuilder.prototype.build",A.prototype.Ca);t("analytics.filters.FilterBuilder",z);t("analytics.filters.FilterBuilder.builder",Pa);
|
|
||||||
t("analytics.filters.FilterBuilder.prototype.when",z.prototype.when);t("analytics.filters.FilterBuilder.prototype.whenHitType",z.prototype.zb);t("analytics.filters.FilterBuilder.prototype.whenValue",z.prototype.Oc);t("analytics.filters.FilterBuilder.prototype.applyFilter",z.prototype.xb);t("analytics.filters.FilterBuilder.prototype.build",z.prototype.Ca);t("analytics.EventBuilder",D);t("analytics.EventBuilder.builder",function(){return Va});t("analytics.EventBuilder.prototype.category",D.prototype.xc);
|
|
||||||
t("analytics.EventBuilder.prototype.action",D.prototype.action);t("analytics.EventBuilder.prototype.label",D.prototype.label);t("analytics.EventBuilder.prototype.value",D.prototype.value);t("analytics.EventBuilder.prototype.dimension",D.prototype.yc);t("analytics.EventBuilder.prototype.metric",D.prototype.Dc);t("analytics.EventBuilder.prototype.send",D.prototype.send); })()
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
|
||||||
|
|
||||||
let Localiziation = function(locale) {
|
let Localiziation = function(locale) {
|
||||||
let self = { };
|
let self = { };
|
||||||
let messages = null;
|
let messages = null;
|
||||||
|
@ -9,19 +10,28 @@ let Localiziation = function(locale) {
|
||||||
let local = locale;
|
let local = locale;
|
||||||
|
|
||||||
self.loadMessages = function () {
|
self.loadMessages = function () {
|
||||||
var data;
|
const path = require('path');
|
||||||
|
let fileName = path.join(__dirname, "./locale/" + local + "/messages.json");
|
||||||
|
if (!fs.existsSync(fileName)) {
|
||||||
|
fileName = path.join(__dirname, "./../locale/" + local + "/messages.json");
|
||||||
|
}
|
||||||
|
console.log(fileName);
|
||||||
try {
|
try {
|
||||||
data = fs.readFileSync(path.join(__dirname, "./locale/" + local + "/messages.json"), 'utf8',);
|
var data = fs.readFileSync(fileName, 'utf8',);
|
||||||
messages = JSON.parse(data);
|
messages = JSON.parse(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("Error while reading languge file");
|
console.log("Error while reading language file: " + fileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.getMessage = function(messageID, substitutions = null) {
|
self.getMessage = function(messageID, substitutions = null) {
|
||||||
try {
|
try {
|
||||||
|
if (messages == null) {
|
||||||
|
self.loadMessages();
|
||||||
|
}
|
||||||
|
|
||||||
if (substitutions) {
|
if (substitutions) {
|
||||||
return messages[messageID].message.replace(/\{(\d+)\}/g, function (t, i) {
|
return messages[messageID].message.replace(/\{(\d+)\}/g, (t, i) => {
|
||||||
return substitutions[i] !== void 0 ? substitutions[i] : "{" + (i - substitutions.length) + "}";
|
return substitutions[i] !== void 0 ? substitutions[i] : "{" + (i - substitutions.length) + "}";
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
12
js/main.js
12
js/main.js
|
@ -11,6 +11,18 @@ const Store = require('electron-store');
|
||||||
const store = new Store();
|
const store = new Store();
|
||||||
var localization;
|
var localization;
|
||||||
|
|
||||||
|
process.on('uncaughtException', function (error) {
|
||||||
|
if (process.env.NODE_ENV !== 'development') {
|
||||||
|
GUI.log(localization.getMessage('unexpectedError', error));
|
||||||
|
if (GUI.connected_to || GUI.connecting_to) {
|
||||||
|
GUI.log(localization.getMessage('disconnecting'));
|
||||||
|
$('a.connect').trigger('click');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Set how the units render on the configurator only
|
// Set how the units render on the configurator only
|
||||||
const UnitType = {
|
const UnitType = {
|
||||||
none: "none",
|
none: "none",
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { findByIds } = require('usb');
|
const { ipcRenderer } = require('electron');
|
||||||
|
|
||||||
var usbDevices = [
|
var usbDevices = [
|
||||||
{ 'vendorId': 1155, 'productId': 57105},
|
{ 'vendorId': 1155, 'productId': 57105},
|
||||||
{ 'vendorId': 11836, 'productId': 57105}
|
{ 'vendorId': 11836, 'productId': 57105}
|
||||||
];
|
];
|
||||||
|
|
||||||
// TODO: Replace with events
|
|
||||||
|
|
||||||
var PortHandler = new function () {
|
var PortHandler = new function () {
|
||||||
this.initial_ports = false;
|
this.initial_ports = false;
|
||||||
|
@ -152,7 +151,7 @@ PortHandler.check = function () {
|
||||||
self.initial_ports = current_ports;
|
self.initial_ports = current_ports;
|
||||||
}
|
}
|
||||||
|
|
||||||
//self.check_usb_devices();
|
self.check_usb_devices();
|
||||||
|
|
||||||
GUI.updateManualPortVisibility();
|
GUI.updateManualPortVisibility();
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
|
@ -164,28 +163,32 @@ PortHandler.check = function () {
|
||||||
PortHandler.check_usb_devices = function (callback) {
|
PortHandler.check_usb_devices = function (callback) {
|
||||||
|
|
||||||
self.dfu_available = false;
|
self.dfu_available = false;
|
||||||
for (const device of usbDevices) {
|
|
||||||
if (findByIds(device.vendorId, device.productId)) {
|
navigator.usb.getDevices().then(devices => {
|
||||||
self.dfu_available = true;
|
devices.forEach(device => {
|
||||||
break;
|
usbDevices.forEach(usbDev => {
|
||||||
}
|
if (device.vendorId == usbDev.vendorId && device.productId == usbDev.productId) {
|
||||||
}
|
self.dfu_available = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
if (self.dfu_available) {
|
if (self.dfu_available) {
|
||||||
if (!$("div#port-picker #port [value='DFU']").length) {
|
if (!$("div#port-picker #port [value='DFU']").length) {
|
||||||
$('div#port-picker #port').append($('<option/>', {value: "DFU", text: "DFU", data: {isDFU: true}}));
|
$('div#port-picker #port').append($('<option/>', {value: "DFU", text: "DFU", data: {isDFU: true}}));
|
||||||
$('div#port-picker #port').val('DFU');
|
$('div#port-picker #port').val('DFU');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($("div#port-picker #port [value='DFU']").length) {
|
||||||
|
$("div#port-picker #port [value='DFU']").remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if ($("div#port-picker #port [value='DFU']").length) {
|
if (callback)
|
||||||
$("div#port-picker #port [value='DFU']").remove();
|
callback(self.dfu_available);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callback)
|
|
||||||
callback(self.dfu_available);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
PortHandler.update_port_select = function (ports) {
|
PortHandler.update_port_select = function (ports) {
|
||||||
$('div#port-picker #port').html(''); // drop previous one
|
$('div#port-picker #port').html(''); // drop previous one
|
||||||
|
|
|
@ -17,7 +17,7 @@ var STM32DFU_protocol = function () {
|
||||||
this.hex = null;
|
this.hex = null;
|
||||||
this.verify_hex = [];
|
this.verify_hex = [];
|
||||||
|
|
||||||
this.handle = null; // connection handle
|
this.usbDevice = null; // USB Device object
|
||||||
|
|
||||||
this.request = {
|
this.request = {
|
||||||
DETACH: 0x00, // OUT, Requests the device to leave DFU mode and enter the application.
|
DETACH: 0x00, // OUT, Requests the device to leave DFU mode and enter the application.
|
||||||
|
@ -67,7 +67,7 @@ var STM32DFU_protocol = function () {
|
||||||
this.transferSize = 2048; // Default USB DFU transfer size for F3,F4 and F7
|
this.transferSize = 2048; // Default USB DFU transfer size for F3,F4 and F7
|
||||||
};
|
};
|
||||||
|
|
||||||
STM32DFU_protocol.prototype.connect = function (device, hex, options, callback) {
|
STM32DFU_protocol.prototype.connect = function (usbDevices, hex, options, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
self.hex = hex;
|
self.hex = hex;
|
||||||
self.callback = callback;
|
self.callback = callback;
|
||||||
|
@ -90,67 +90,62 @@ STM32DFU_protocol.prototype.connect = function (device, hex, options, callback)
|
||||||
// reset progress bar to initial state
|
// reset progress bar to initial state
|
||||||
TABS.firmware_flasher.flashingMessage(null, TABS.firmware_flasher.FLASH_MESSAGE_TYPES.NEUTRAL).flashProgress(0);
|
TABS.firmware_flasher.flashingMessage(null, TABS.firmware_flasher.FLASH_MESSAGE_TYPES.NEUTRAL).flashProgress(0);
|
||||||
|
|
||||||
chrome.usb.getDevices(device, function (result) {
|
navigator.usb.getDevices().then(devices => {
|
||||||
if (result.length) {
|
var dev = null;
|
||||||
console.log('USB DFU detected with ID: ' + result[0].device);
|
devices.forEach(device => {
|
||||||
|
usbDevices.forEach(usbDev => {
|
||||||
|
if (device.vendorId == usbDev.vendorId && device.productId == usbDev.productId) {
|
||||||
|
self.usbDevice = device;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
self.openDevice(result[0]);
|
if (self.usbDevice ) {
|
||||||
|
console.log('USB DFU detected');
|
||||||
|
self.openDevice();
|
||||||
} else {
|
} else {
|
||||||
console.log('USB DFU not found');
|
console.log('USB DFU not found');
|
||||||
GUI.log(localization.getMessage('stm32UsbDfuNotFound'));
|
GUI.log(localization.getMessage('stm32UsbDfuNotFound'));
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
STM32DFU_protocol.prototype.openDevice = function (device) {
|
STM32DFU_protocol.prototype.openDevice = function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
chrome.usb.openDevice(device, function (handle) {
|
self.usbDevice.open().then( () => {
|
||||||
if (checkChromeRuntimeError()) {
|
GUI.log(localization.getMessage('usbDeviceOpened'));
|
||||||
console.log('Failed to open USB device!');
|
console.log('USB-Device opened');
|
||||||
GUI.log(localization.getMessage('usbDeviceOpenFail'));
|
|
||||||
if(GUI.operating_system === 'Linux') {
|
|
||||||
GUI.log(localization.getMessage('usbDeviceUdevNotice'));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.handle = handle;
|
|
||||||
|
|
||||||
GUI.log(localization.getMessage('usbDeviceOpened', handle.handle.toString()));
|
|
||||||
console.log('Device opened with Handle ID: ' + handle.handle);
|
|
||||||
self.claimInterface(0);
|
self.claimInterface(0);
|
||||||
|
}).catch(error => {
|
||||||
|
console.log('Failed to open USB device: ' + error);
|
||||||
|
GUI.log(localization.getMessage('usbDeviceOpenFail'));
|
||||||
|
if(GUI.operating_system === 'Linux') {
|
||||||
|
GUI.log(localization.getMessage('usbDeviceUdevNotice'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
STM32DFU_protocol.prototype.closeDevice = function () {
|
STM32DFU_protocol.prototype.closeDevice = function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
chrome.usb.closeDevice(this.handle, function closed() {
|
self.usbDevice.close().then(() => {
|
||||||
if (checkChromeRuntimeError()) {
|
|
||||||
console.log('Failed to close USB device!');
|
|
||||||
GUI.log(localization.getMessage('usbDeviceCloseFail'));
|
|
||||||
}
|
|
||||||
|
|
||||||
GUI.log(localization.getMessage('usbDeviceClosed'));
|
GUI.log(localization.getMessage('usbDeviceClosed'));
|
||||||
console.log('Device closed with Handle ID: ' + self.handle.handle);
|
console.log('USB-Device closed');
|
||||||
|
}).catch(error => {
|
||||||
self.handle = null;
|
console.log('Failed to close USB device!');
|
||||||
|
GUI.log(localization.getMessage('usbDeviceCloseFail'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.usbDevice = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
STM32DFU_protocol.prototype.claimInterface = function (interfaceNumber) {
|
STM32DFU_protocol.prototype.claimInterface = function (interfaceNumber) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
chrome.usb.claimInterface(this.handle, interfaceNumber, function claimed() {
|
self.usbDevice.claimInterface(interfaceNumber).then( () => {
|
||||||
// Don't perform the error check on MacOS at this time as there seems to be a bug
|
|
||||||
// where it always reports the Chrome error "Error claiming interface." even though
|
|
||||||
// the interface is in fact successfully claimed.
|
|
||||||
if (checkChromeRuntimeError() && (GUI.operating_system !== "MacOS")) {
|
|
||||||
console.log('Failed to claim USB device!');
|
|
||||||
self.cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Claimed interface: ' + interfaceNumber);
|
console.log('Claimed interface: ' + interfaceNumber);
|
||||||
|
|
||||||
if (self.options.exitDfu) {
|
if (self.options.exitDfu) {
|
||||||
|
@ -158,22 +153,25 @@ STM32DFU_protocol.prototype.claimInterface = function (interfaceNumber) {
|
||||||
} else {
|
} else {
|
||||||
self.upload_procedure(0);
|
self.upload_procedure(0);
|
||||||
}
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.log('Failed to claim USB device: ' + error);
|
||||||
|
self.cleanup();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
STM32DFU_protocol.prototype.releaseInterface = function (interfaceNumber) {
|
STM32DFU_protocol.prototype.releaseInterface = function (interfaceNumber) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
chrome.usb.releaseInterface(this.handle, interfaceNumber, function released() {
|
self.usbDevice.releaseInterface(interfaceNumber).then(() => {
|
||||||
console.log('Released interface: ' + interfaceNumber);
|
console.log('Released interface: ' + interfaceNumber);
|
||||||
|
|
||||||
self.closeDevice();
|
self.closeDevice();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
STM32DFU_protocol.prototype.resetDevice = function (callback) {
|
STM32DFU_protocol.prototype.resetDevice = function (callback) {
|
||||||
chrome.usb.resetDevice(this.handle, function (result) {
|
var self = this;
|
||||||
console.log('Reset Device: ' + result);
|
self.usbDevice.reset().then( () => {
|
||||||
|
console.log('Reset USB-Device');
|
||||||
|
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
});
|
});
|
||||||
|
@ -181,46 +179,50 @@ STM32DFU_protocol.prototype.resetDevice = function (callback) {
|
||||||
|
|
||||||
STM32DFU_protocol.prototype.getString = function (index, callback) {
|
STM32DFU_protocol.prototype.getString = function (index, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var setup = {
|
||||||
chrome.usb.controlTransfer(self.handle, {
|
recipient: 'device',
|
||||||
'direction': 'in',
|
requestType: 'standard',
|
||||||
'recipient': 'device',
|
request: 6,
|
||||||
'requestType': 'standard',
|
value: 0x300 | index,
|
||||||
'request': 6,
|
index: 0 // specifies language
|
||||||
'value': 0x300 | index,
|
};
|
||||||
'index': 0, // specifies language
|
self.usbDevice.controlTransferIn(setup, 255).then(result => {
|
||||||
'length': 255 // max length to retreive
|
if (result.status == 'ok') {;
|
||||||
}, function (result) {
|
var length = result.data.getUint8(0);
|
||||||
if (checkChromeRuntimeError()) {
|
var descriptor = "";
|
||||||
console.log('USB getString failed! ' + result.resultCode);
|
for (var i = 2; i < length; i += 2) {
|
||||||
callback("", result.resultCode);
|
var charCode = result.data.getUint16(i, true);
|
||||||
return;
|
descriptor += String.fromCharCode(charCode);
|
||||||
|
}
|
||||||
|
callback(descriptor, 0);
|
||||||
|
} else {
|
||||||
|
throw new Error(result.status);
|
||||||
}
|
}
|
||||||
var view = new DataView(result.data);
|
|
||||||
var length = view.getUint8(0);
|
}).catch(error => {
|
||||||
var descriptor = "";
|
console.log('USB getString failed: ' + error);
|
||||||
for (var i = 2; i < length; i += 2) {
|
callback("", 1);
|
||||||
var charCode = view.getUint16(i, true);
|
|
||||||
descriptor += String.fromCharCode(charCode);
|
|
||||||
}
|
|
||||||
callback(descriptor, result.resultCode);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
STM32DFU_protocol.prototype.getInterfaceDescriptors = function (interfaceNum, callback) {
|
STM32DFU_protocol.prototype.getInterfaceDescriptors = function (interfaceNum, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
chrome.usb.getConfiguration( this.handle, function (config) {
|
|
||||||
if (checkChromeRuntimeError()) {
|
|
||||||
console.log('USB getConfiguration failed!');
|
|
||||||
callback([], -200);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var interfaceID = 0;
|
var interfaceID = 0;
|
||||||
var descriptorStringArray = [];
|
var descriptorStringArray = [];
|
||||||
|
|
||||||
|
var interfacesCount = self.usbDevice.configuration.interfaces.length;
|
||||||
|
var descriptorCount = 0;
|
||||||
|
if (interfacesCount == 0) {
|
||||||
|
callback(0, 1); //Error
|
||||||
|
} else if (interfacesCount == 1) {
|
||||||
|
descriptorCount = self.usbDevice.configuration.interfaces[0].alternates.length
|
||||||
|
} else if (interfacesCount > 1) {
|
||||||
|
descriptorCount = interfacesCount;
|
||||||
|
}
|
||||||
|
|
||||||
var getDescriptorString = function () {
|
var getDescriptorString = function () {
|
||||||
if(interfaceID < config.interfaces.length) {
|
if (interfaceID < descriptorCount) {
|
||||||
self.getInterfaceDescriptor(interfaceID, function (descriptor, resultCode) {
|
self.getInterfaceDescriptor(interfaceID, function (descriptor, resultCode) {
|
||||||
if (resultCode) {
|
if (resultCode) {
|
||||||
callback([], resultCode);
|
callback([], resultCode);
|
||||||
|
@ -245,73 +247,70 @@ STM32DFU_protocol.prototype.getInterfaceDescriptors = function (interfaceNum, ca
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
getDescriptorString();
|
getDescriptorString();
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
STM32DFU_protocol.prototype.getInterfaceDescriptor = function (_interface, callback) {
|
STM32DFU_protocol.prototype.getInterfaceDescriptor = function (_interface, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
chrome.usb.controlTransfer(this.handle, {
|
var setup = {
|
||||||
'direction': 'in',
|
recipient: 'device',
|
||||||
'recipient': 'device',
|
requestType: 'standard',
|
||||||
'requestType': 'standard',
|
request: 6,
|
||||||
'request': 6,
|
value: 0x200,
|
||||||
'value': 0x200,
|
index: 0
|
||||||
'index': 0,
|
}
|
||||||
'length': 18 + _interface * 9
|
self.usbDevice.controlTransferIn(setup, 18 + _interface * 9).then(result => {
|
||||||
}, function (result) {
|
if (result.status == 'ok') {
|
||||||
if (checkChromeRuntimeError()) {
|
var buf = new Uint8Array(result.data.buffer, 9 + _interface * 9);
|
||||||
console.log('USB getInterfaceDescriptor failed! ' + result.resultCode);
|
var descriptor = {
|
||||||
callback({}, result.resultCode);
|
'bLength': buf[0],
|
||||||
return;
|
'bDescriptorType': buf[1],
|
||||||
}
|
'bInterfaceNumber': buf[2],
|
||||||
|
'bAlternateSetting': buf[3],
|
||||||
var buf = new Uint8Array(result.data, 9 + _interface * 9);
|
'bNumEndpoints': buf[4],
|
||||||
var descriptor = {
|
'bInterfaceClass': buf[5],
|
||||||
'bLength': buf[0],
|
'bInterfaceSubclass': buf[6],
|
||||||
'bDescriptorType': buf[1],
|
'bInterfaceProtocol': buf[7],
|
||||||
'bInterfaceNumber': buf[2],
|
'iInterface': buf[8]
|
||||||
'bAlternateSetting': buf[3],
|
};
|
||||||
'bNumEndpoints': buf[4],
|
callback(descriptor, 0);
|
||||||
'bInterfaceClass': buf[5],
|
} else {
|
||||||
'bInterfaceSubclass': buf[6],
|
throw new Error(result.status)
|
||||||
'bInterfaceProtocol': buf[7],
|
}
|
||||||
'iInterface': buf[8]
|
}).catch(error => {
|
||||||
};
|
console.log('USB getInterfaceDescriptor failed: ' + error);
|
||||||
|
callback({}, 1);
|
||||||
callback(descriptor, result.resultCode);
|
return;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
STM32DFU_protocol.prototype.getFunctionalDescriptor = function (_interface, callback) {
|
STM32DFU_protocol.prototype.getFunctionalDescriptor = function (_interface, callback) {
|
||||||
var self = this;
|
var self = this;
|
||||||
chrome.usb.controlTransfer(this.handle, {
|
var setup = {
|
||||||
'direction': 'in',
|
recipient: 'interface',
|
||||||
'recipient': 'interface',
|
requestType: 'standard',
|
||||||
'requestType': 'standard',
|
request: 6,
|
||||||
'request': 6,
|
value: 0x2100,
|
||||||
'value': 0x2100,
|
index: 0
|
||||||
'index': 0,
|
};
|
||||||
'length': 255
|
self.usbDevice.controlTransferIn(setup, 255).then(result => {
|
||||||
}, function (result) {
|
if (result.status == 'ok') {
|
||||||
if (checkChromeRuntimeError()) {
|
var buf = new Uint8Array(result.data.buffer);
|
||||||
console.log('USB getFunctionalDescriptor failed! ' + result.resultCode);
|
var descriptor = {
|
||||||
callback({}, result.resultCode);
|
'bLength': buf[0],
|
||||||
return;
|
'bDescriptorType': buf[1],
|
||||||
}
|
'bmAttributes': buf[2],
|
||||||
|
'wDetachTimeOut': (buf[4] << 8)|buf[3],
|
||||||
var buf = new Uint8Array(result.data);
|
'wTransferSize': (buf[6] << 8)|buf[5],
|
||||||
|
'bcdDFUVersion': buf[7]
|
||||||
var descriptor = {
|
};
|
||||||
'bLength': buf[0],
|
callback(descriptor, 0);
|
||||||
'bDescriptorType': buf[1],
|
} else {
|
||||||
'bmAttributes': buf[2],
|
throw new Error(result.status);
|
||||||
'wDetachTimeOut': (buf[4] << 8)|buf[3],
|
}
|
||||||
'wTransferSize': (buf[6] << 8)|buf[5],
|
}).catch(error => {
|
||||||
'bcdDFUVersion': buf[7]
|
console.log('USB getFunctionalDescriptor failed! ' + error);
|
||||||
};
|
callback({}, 1);
|
||||||
|
|
||||||
callback(descriptor, result.resultCode);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -424,60 +423,49 @@ STM32DFU_protocol.prototype.getChipInfo = function (_interface, callback) {
|
||||||
STM32DFU_protocol.prototype.controlTransfer = function (direction, request, value, _interface, length, data, callback, _timeout) {
|
STM32DFU_protocol.prototype.controlTransfer = function (direction, request, value, _interface, length, data, callback, _timeout) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// timeout support was added in chrome v43
|
|
||||||
var timeout;
|
|
||||||
if (typeof _timeout === "undefined") {
|
|
||||||
timeout = 0; // default is 0 (according to chrome.usb API)
|
|
||||||
} else {
|
|
||||||
timeout = _timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (direction == 'in') {
|
if (direction == 'in') {
|
||||||
// data is ignored
|
// data is ignored
|
||||||
chrome.usb.controlTransfer(this.handle, {
|
var setup = {
|
||||||
'direction': 'in',
|
recipient: 'interface',
|
||||||
'recipient': 'interface',
|
requestType: 'class',
|
||||||
'requestType': 'class',
|
request: request,
|
||||||
'request': request,
|
value: value,
|
||||||
'value': value,
|
index: _interface
|
||||||
'index': _interface,
|
}
|
||||||
'length': length,
|
self.usbDevice.controlTransferIn(setup, length).then( result => {
|
||||||
'timeout': timeout
|
if (result.status == 'ok') {
|
||||||
}, function (result) {
|
var buf = new Uint8Array(result.data.buffer);
|
||||||
if (checkChromeRuntimeError()) {
|
callback(buf, result.resultCode);
|
||||||
console.log('USB controlTransfer IN failed for request ' + request + '!');
|
} else {
|
||||||
|
throw new Error(result.status);
|
||||||
}
|
}
|
||||||
if (result.resultCode) console.log('USB transfer result code: ' + result.resultCode);
|
}).catch(() => {
|
||||||
|
console.log('USB controlTransfer IN failed for request ' + request + '!');
|
||||||
var buf = new Uint8Array(result.data);
|
|
||||||
callback(buf, result.resultCode);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// length is ignored
|
// length is ignored
|
||||||
|
var dataIn;
|
||||||
if (data) {
|
if (data) {
|
||||||
var arrayBuf = new ArrayBuffer(data.length);
|
dataIn = new Uint8Array(data);;
|
||||||
var arrayBufView = new Uint8Array(arrayBuf);
|
|
||||||
arrayBufView.set(data);
|
|
||||||
} else {
|
} else {
|
||||||
var arrayBuf = new ArrayBuffer(0);
|
dataIn = new Uint8Array(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.usb.controlTransfer(this.handle, {
|
var setup = {
|
||||||
'direction': 'out',
|
recipient: 'interface',
|
||||||
'recipient': 'interface',
|
requestType: 'class',
|
||||||
'requestType': 'class',
|
request: request,
|
||||||
'request': request,
|
value: value,
|
||||||
'value': value,
|
index: _interface
|
||||||
'index': _interface,
|
}
|
||||||
'data': arrayBuf,
|
self.usbDevice.controlTransferOut(setup, dataIn).then(result => {
|
||||||
'timeout': timeout
|
if (result.status == 'ok') {
|
||||||
}, function (result) {
|
callback(result);
|
||||||
if (checkChromeRuntimeError()) {
|
} else {
|
||||||
console.log('USB controlTransfer OUT failed for request ' + request + '!');
|
throw new Error(result.status);
|
||||||
}
|
}
|
||||||
if (result.resultCode) console.log('USB transfer result code: ' + result.resultCode);
|
}).catch(() => {
|
||||||
|
console.log('USB controlTransfer OUT failed for request ' + request + '!');
|
||||||
callback(result);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*global chrome, chrome.i18n*/
|
/*global chrome*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
|
@ -29,7 +29,7 @@ $(document).ready(function () {
|
||||||
|
|
||||||
modal = new jBox('Modal', {
|
modal = new jBox('Modal', {
|
||||||
width: 400,
|
width: 400,
|
||||||
height: 100,
|
height: 120,
|
||||||
animation: false,
|
animation: false,
|
||||||
closeOnClick: false,
|
closeOnClick: false,
|
||||||
closeOnEsc: false,
|
closeOnEsc: false,
|
||||||
|
@ -302,7 +302,7 @@ function onOpen(openInfo) {
|
||||||
|
|
||||||
GUI.log(localization.getMessage('serialPortOpened', [openInfo.connectionId]));
|
GUI.log(localization.getMessage('serialPortOpened', [openInfo.connectionId]));
|
||||||
|
|
||||||
// save selected port with chrome.storage if the port differs
|
// save selected port if the port differs
|
||||||
var last_used_port = store.get('last_used_port', false);
|
var last_used_port = store.get('last_used_port', false);
|
||||||
if (last_used_port) {
|
if (last_used_port) {
|
||||||
if (last_used_port != GUI.connected_to) {
|
if (last_used_port != GUI.connected_to) {
|
||||||
|
|
|
@ -23,6 +23,12 @@
|
||||||
"options_render": {
|
"options_render": {
|
||||||
"message": "Configurator rendering options"
|
"message": "Configurator rendering options"
|
||||||
},
|
},
|
||||||
|
"unexpectedError" : {
|
||||||
|
"message": "Unexpected error: {0}"
|
||||||
|
},
|
||||||
|
"disconnecting": {
|
||||||
|
"message": "Disconnecting..."
|
||||||
|
},
|
||||||
"connect": {
|
"connect": {
|
||||||
"message": "Connect"
|
"message": "Connect"
|
||||||
},
|
},
|
||||||
|
@ -303,7 +309,7 @@
|
||||||
"message": "UDP connection timed out."
|
"message": "UDP connection timed out."
|
||||||
},
|
},
|
||||||
"usbDeviceOpened": {
|
"usbDeviceOpened": {
|
||||||
"message": "USB device <span style=\"color: #37a8db\">successfully</span> opened with ID: {0}"
|
"message": "USB device <span style=\"color: #37a8db\">successfully</span> opened"
|
||||||
},
|
},
|
||||||
"usbDeviceOpenFail": {
|
"usbDeviceOpenFail": {
|
||||||
"message": "<span style=\"color: red\">Failed</span> to open USB device!"
|
"message": "<span style=\"color: red\">Failed</span> to open USB device!"
|
||||||
|
|
106
main.js
106
main.js
|
@ -1,16 +1,57 @@
|
||||||
const { app, BrowserWindow } = require('electron');
|
const { app, BrowserWindow, ipcMain } = require('electron');
|
||||||
const windowStateKeeper = require('electron-window-state');
|
const windowStateKeeper = require('electron-window-state');
|
||||||
|
const path = require('path');
|
||||||
const Store = require('electron-store');
|
const Store = require('electron-store');
|
||||||
Store.initRenderer();
|
Store.initRenderer();
|
||||||
|
|
||||||
require('@electron/remote/main').initialize();
|
require('@electron/remote/main').initialize();
|
||||||
|
|
||||||
|
const usbBootloaderIds = [
|
||||||
|
{ vendorId: 1155, productId: 57105},
|
||||||
|
{ vendorId: 11836, productId: 57105}
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||||
if (require('electron-squirrel-startup')) {
|
if (require('electron-squirrel-startup')) {
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mainWindow = null;
|
let mainWindow = null;
|
||||||
|
let bluetoothDeviceChooser = null;
|
||||||
|
let btDeviceList = null;
|
||||||
|
let selectBluetoothCallback = null;
|
||||||
|
|
||||||
|
// In Eletrcon the bluetooth device chooser didn't exist, so we have to buid our own
|
||||||
|
function createDeviceChooser() {
|
||||||
|
bluetoothDeviceChooser = new BrowserWindow({
|
||||||
|
parent: mainWindow,
|
||||||
|
width: 400,
|
||||||
|
height: 400,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, 'js/libraries/bluetooth-device-chooser/preload.js')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
bluetoothDeviceChooser.removeMenu();
|
||||||
|
bluetoothDeviceChooser.loadFile(path.join(__dirname, 'js/libraries/bluetooth-device-chooser/index.html'));
|
||||||
|
|
||||||
|
bluetoothDeviceChooser.on('closed', () => {
|
||||||
|
btDeviceList = null;
|
||||||
|
if (selectBluetoothCallback) {
|
||||||
|
selectBluetoothCallback('');
|
||||||
|
selectBluetoothCallback = null;
|
||||||
|
}
|
||||||
|
bluetoothDeviceChooser = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('deviceSelected', (_event, deviceID) => {
|
||||||
|
if (selectBluetoothCallback) {
|
||||||
|
selectBluetoothCallback(deviceID);
|
||||||
|
selectBluetoothCallback = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
app.on('ready', () => {
|
app.on('ready', () => {
|
||||||
|
|
||||||
|
@ -31,6 +72,69 @@ app.on('ready', () => {
|
||||||
webSecurity: false
|
webSecurity: false
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
|
||||||
|
event.preventDefault();
|
||||||
|
selectBluetoothCallback = callback;
|
||||||
|
|
||||||
|
const compare = (a, b) => {
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
a.every((element, index) => {
|
||||||
|
if (element.deviceId !== b[index].deviceId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!btDeviceList || !compare(btDeviceList, deviceList)) {
|
||||||
|
btDeviceList = [...deviceList];
|
||||||
|
|
||||||
|
if (!bluetoothDeviceChooser) {
|
||||||
|
createDeviceChooser();
|
||||||
|
}
|
||||||
|
bluetoothDeviceChooser.webContents.send('ble-scan', btDeviceList);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
mainWindow.webContents.session.on('select-usb-device', (event, details, callback) => {
|
||||||
|
console.log(details.deviceList)
|
||||||
|
let premittedDevice = null;
|
||||||
|
if (details.deviceList) {
|
||||||
|
details.deviceList.every((device, idx) => {
|
||||||
|
if (device.productId == usbBootloaderIds[idx].productId && device.vendorId == usbBootloaderIds[idx].vendorId) {
|
||||||
|
premittedDevice = device.deviceId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (premittedDevice) {
|
||||||
|
callback(premittedDevice);
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.session.setDevicePermissionHandler((details) => {
|
||||||
|
if (details.deviceType === 'usb' && details.origin === 'file://') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||||
|
return {
|
||||||
|
action: 'allow',
|
||||||
|
overrideBrowserWindowOptions: {
|
||||||
|
autoHideMenuBar: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
|
app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
|
||||||
|
|
||||||
require("@electron/remote/main").enable(mainWindow.webContents);
|
require("@electron/remote/main").enable(mainWindow.webContents);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
@import 'tabs/receiver_msp.css';
|
@import 'tabs/receiver_msp.css';
|
||||||
@import './opensans_webfontkit/fonts.css';
|
@import './opensans_webfontkit/fonts.css';
|
||||||
@import '/js/libraries/jquery.nouislider.min.css';
|
@import '../../js/libraries/jquery.nouislider.min.css';
|
||||||
@import '/js/libraries/jquery.nouislider.pips.min.css';
|
@import '../../js/libraries/jquery.nouislider.pips.min.css';
|
|
@ -153,7 +153,7 @@ TABS.calibration.initialize = function (callback) {
|
||||||
|
|
||||||
modalProcessing = new jBox('Modal', {
|
modalProcessing = new jBox('Modal', {
|
||||||
width: 400,
|
width: 400,
|
||||||
height: 100,
|
height: 120,
|
||||||
animation: false,
|
animation: false,
|
||||||
closeOnClick: false,
|
closeOnClick: false,
|
||||||
closeOnEsc: false,
|
closeOnEsc: false,
|
||||||
|
@ -237,7 +237,7 @@ TABS.calibration.initialize = function (callback) {
|
||||||
|
|
||||||
let modalProcessing = new jBox('Modal', {
|
let modalProcessing = new jBox('Modal', {
|
||||||
width: 400,
|
width: 400,
|
||||||
height: 100,
|
height: 120,
|
||||||
animation: false,
|
animation: false,
|
||||||
closeOnClick: false,
|
closeOnClick: false,
|
||||||
closeOnEsc: false,
|
closeOnEsc: false,
|
||||||
|
@ -279,7 +279,7 @@ TABS.calibration.initialize = function (callback) {
|
||||||
|
|
||||||
modalProcessing = new jBox('Modal', {
|
modalProcessing = new jBox('Modal', {
|
||||||
width: 400,
|
width: 400,
|
||||||
height: 100,
|
height: 120,
|
||||||
animation: false,
|
animation: false,
|
||||||
closeOnClick: false,
|
closeOnClick: false,
|
||||||
closeOnEsc: false,
|
closeOnEsc: false,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
/*global chrome,GUI,TABS,nwdialog,$*/
|
/*global chrome,GUI,TABS,$*/
|
||||||
TABS.cli = {
|
TABS.cli = {
|
||||||
lineDelayMs: 50,
|
lineDelayMs: 50,
|
||||||
profileSwitchDelayMs: 100,
|
profileSwitchDelayMs: 100,
|
||||||
|
@ -159,7 +159,7 @@ TABS.cli.initialize = function (callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
fs.writeFile(result.filePath , self.outputHistory, (err) => {
|
fs.writeFile(result.filePath, self.outputHistory, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
GUI.log(localization.getMessage('ErrorWritingFile'));
|
GUI.log(localization.getMessage('ErrorWritingFile'));
|
||||||
return console.error(err);
|
return console.error(err);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*global $,nwdialog*/
|
/*global $*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { marked } = require('marked');
|
const { marked } = require('marked');
|
||||||
|
@ -223,8 +223,19 @@ TABS.firmware_flasher.initialize = function (callback) {
|
||||||
|
|
||||||
$('a.load_file').on('click', function () {
|
$('a.load_file').on('click', function () {
|
||||||
|
|
||||||
nwdialog.setContext(document);
|
var options = {
|
||||||
nwdialog.openFileDialog('.hex', function(filename) {
|
filters: [ { name: "HEX file", extensions: ['hex'] } ]
|
||||||
|
};
|
||||||
|
dialog.showOpenDialog(options).then(result => {
|
||||||
|
if (result.canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let filename;
|
||||||
|
if (result.filePaths.length == 1) {
|
||||||
|
filename = result.filePaths[0];
|
||||||
|
}
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
$('div.git_info').slideUp();
|
$('div.git_info').slideUp();
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/*global nwdialog*/
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
TABS.logging = {};
|
TABS.logging = {};
|
||||||
|
@ -226,13 +225,17 @@ TABS.logging.initialize = function (callback) {
|
||||||
const filename = 'inav_data_log_' + date.getFullYear() + '-' + zeroPad(date.getMonth() + 1, 2) + '-'
|
const filename = 'inav_data_log_' + date.getFullYear() + '-' + zeroPad(date.getMonth() + 1, 2) + '-'
|
||||||
+ zeroPad(date.getDate(), 2) + '_' + zeroPad(date.getHours(), 2) + zeroPad(date.getMinutes(), 2)
|
+ zeroPad(date.getDate(), 2) + '_' + zeroPad(date.getHours(), 2) + zeroPad(date.getMinutes(), 2)
|
||||||
+ zeroPad(date.getSeconds(), 2);
|
+ zeroPad(date.getSeconds(), 2);
|
||||||
const accepts = [{
|
|
||||||
description: 'TXT files', extensions: ['txt'],
|
|
||||||
}];
|
|
||||||
|
|
||||||
nwdialog.setContext(document);
|
var options = {
|
||||||
nwdialog.saveFileDialog(filename, accepts, '', function(file) {
|
defaultPath: filename,
|
||||||
loggingFileName = file;
|
filters: [ { name: "TXT file", extensions: ['txt'] } ]
|
||||||
|
};
|
||||||
|
dialog.showSaveDialog(options).then(result => {
|
||||||
|
if (result.canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loggingFileName = result.filePath;
|
||||||
readyToWrite = true;
|
readyToWrite = true;
|
||||||
store.set('logging_file_name', loggingFileName);
|
store.set('logging_file_name', loggingFileName);
|
||||||
store.set('logging_file_ready', readyToWrite);
|
store.set('logging_file_ready', readyToWrite);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="tab-magnetometer toolbar_fixed_bottom">
|
<div class="tab-magnetometer toolbar_fixed_bottom">
|
||||||
<div class="content_wrapper">
|
<div class="content_wrapper">
|
||||||
<div class="tab_title" data-i18n="tabMagnetometer">Magnetometer</div>
|
<div class="tab_title" data-i18n="tabMAGNETOMETER">Magnetometer</div>
|
||||||
<div class="note spacebottom">
|
<div class="note spacebottom">
|
||||||
<div class="note_spacer">
|
<div class="note_spacer">
|
||||||
<p i18n="magnetometerHelp"></p>
|
<p i18n="magnetometerHelp"></p>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
/*global chrome,GUI,BOARD_ALIGNMENT,TABS,nwdialog,helper,$*/
|
/*global chrome,GUI,BOARD_ALIGNMENT,TABS,helper,$*/
|
||||||
|
|
||||||
TABS.magnetometer = {};
|
TABS.magnetometer = {};
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,10 @@ TABS.mission_control.initialize = function (callback) {
|
||||||
var textGeom;
|
var textGeom;
|
||||||
let isOffline = false;
|
let isOffline = false;
|
||||||
let rthUpdateInterval = 0;
|
let rthUpdateInterval = 0;
|
||||||
|
let $safehomesTable;
|
||||||
|
let $safehomesTableBody;
|
||||||
|
let $waypointOptionsTable;
|
||||||
|
let $waypointOptionsTableBody;
|
||||||
|
|
||||||
if (GUI.active_tab != 'mission_control') {
|
if (GUI.active_tab != 'mission_control') {
|
||||||
GUI.active_tab = 'mission_control';
|
GUI.active_tab = 'mission_control';
|
||||||
|
@ -948,13 +952,22 @@ TABS.mission_control.initialize = function (callback) {
|
||||||
if (singleMissionActive()) {
|
if (singleMissionActive()) {
|
||||||
return true;
|
return true;
|
||||||
} else if (confirm(localization.getMessage('confirm_overwrite_multimission_file_load_option'))) {
|
} else if (confirm(localization.getMessage('confirm_overwrite_multimission_file_load_option'))) {
|
||||||
nwdialog.setContext(document);
|
var options = {
|
||||||
nwdialog.openFileDialog(function(result) {
|
filters: [ { name: "Mission file", extensions: ['mission'] } ]
|
||||||
loadMissionFile(result);
|
};
|
||||||
multimissionCount = 0;
|
dialog.showOpenDialog(options).then(result => {
|
||||||
multimission.flush();
|
if (result.canceled) {
|
||||||
renderMultimissionTable();
|
console.log('No file selected');
|
||||||
})
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.filePaths.length == 1) {
|
||||||
|
loadMissionFile(result.filePaths[0]);
|
||||||
|
multimissionCount = 0;
|
||||||
|
multimission.flush();
|
||||||
|
renderMultimissionTable();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2139,7 +2152,7 @@ TABS.mission_control.initialize = function (callback) {
|
||||||
const returnAltitude = checkAltElevSanity(false, selectedMarker.getAlt(), elevationAtWP, selectedMarker.getP3());
|
const returnAltitude = checkAltElevSanity(false, selectedMarker.getAlt(), elevationAtWP, selectedMarker.getP3());
|
||||||
selectedMarker.setAlt(returnAltitude);
|
selectedMarker.setAlt(returnAltitude);
|
||||||
$('#pointAlt').val(selectedMarker.getAlt());
|
$('#pointAlt').val(selectedMarker.getAlt());
|
||||||
altitudeMeters = app.ConvertCentimetersToMeters(selectedMarker.getAlt());
|
let altitudeMeters = app.ConvertCentimetersToMeters(selectedMarker.getAlt());
|
||||||
$('#altitudeInMeters').text(` ${altitudeMeters}m`);
|
$('#altitudeInMeters').text(` ${altitudeMeters}m`);
|
||||||
|
|
||||||
mission.updateWaypoint(selectedMarker);
|
mission.updateWaypoint(selectedMarker);
|
||||||
|
@ -2416,17 +2429,30 @@ TABS.mission_control.initialize = function (callback) {
|
||||||
if (!fileLoadMultiMissionCheck()) return;
|
if (!fileLoadMultiMissionCheck()) return;
|
||||||
|
|
||||||
if (markers.length && !confirm(localization.getMessage('confirm_delete_all_points'))) return;
|
if (markers.length && !confirm(localization.getMessage('confirm_delete_all_points'))) return;
|
||||||
nwdialog.setContext(document);
|
var options = {
|
||||||
nwdialog.openFileDialog('.mission', function(result) {
|
filters: [ { name: "Mission file", extensions: ['mission'] } ]
|
||||||
loadMissionFile(result);
|
};
|
||||||
|
dialog.showOpenDialog(options).then(result => {
|
||||||
|
if (result.canceled) {
|
||||||
|
console.log('No file selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result.filePaths.length == 1) {
|
||||||
|
loadMissionFile(result.filePaths[0]);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#saveFileMissionButton').on('click', function () {
|
$('#saveFileMissionButton').on('click', function () {
|
||||||
nwdialog.setContext(document);
|
var options = {
|
||||||
nwdialog.saveFileDialog('', '.mission', function(result) {
|
filters: [ { name: "Mission file", extensions: ['mission'] } ]
|
||||||
saveMissionFile(result);
|
};
|
||||||
})
|
dialog.showSaveDialog(options).then(result => {
|
||||||
|
if (result.canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saveMissionFile(result.filePath);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#loadMissionButton').on('click', function () {
|
$('#loadMissionButton').on('click', function () {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/*global $,MSP,MSPCodes,TABS,GUI,CONFIGURATOR,helper,mspHelper,nwdialog,SDCARD,chrome*/
|
/*global $,MSP,MSPCodes,TABS,GUI,CONFIGURATOR,helper,mspHelper,SDCARD,chrome*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var
|
var
|
||||||
|
@ -394,9 +394,15 @@ TABS.onboard_logging.initialize = function (callback) {
|
||||||
+ zeroPad(date.getDate(), 2) + '_' + zeroPad(date.getHours(), 2) + zeroPad(date.getMinutes(), 2)
|
+ zeroPad(date.getDate(), 2) + '_' + zeroPad(date.getHours(), 2) + zeroPad(date.getMinutes(), 2)
|
||||||
+ zeroPad(date.getSeconds(), 2) + '.TXT';
|
+ zeroPad(date.getSeconds(), 2) + '.TXT';
|
||||||
|
|
||||||
nwdialog.setContext(document);
|
var options = {
|
||||||
nwdialog.saveFileDialog(filename, function(file) {
|
defaultPath: filename,
|
||||||
onComplete(file);
|
filters: [ { name: "TXT file", extensions: ['txt'] } ]
|
||||||
|
};
|
||||||
|
dialog.showSaveDialog(options).then(result => {
|
||||||
|
if (result.canceled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onComplete(result.filePath);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
54
tabs/osd.js
54
tabs/osd.js
|
@ -1,4 +1,4 @@
|
||||||
/*global $,nwdialog*/
|
/*global $*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const inflection = require( 'inflection' );
|
const inflection = require( 'inflection' );
|
||||||
|
@ -3315,58 +3315,6 @@ TABS.osd.initialize = function (callback) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', 'span.progressLabel a.save_font', function () {
|
|
||||||
//noinspection JSUnresolvedVariable
|
|
||||||
chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: 'baseflight', accepts: [
|
|
||||||
{extensions: ['mcm']}
|
|
||||||
]}, function (fileEntry) {
|
|
||||||
//noinspection JSUnresolvedVariable
|
|
||||||
if (chrome.runtime.lastError) {
|
|
||||||
//noinspection JSUnresolvedVariable
|
|
||||||
console.error(chrome.runtime.lastError.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//noinspection JSUnresolvedVariable
|
|
||||||
chrome.fileSystem.getDisplayPath(fileEntry, function (path) {
|
|
||||||
console.log('Saving firmware to: ' + path);
|
|
||||||
|
|
||||||
// check if file is writable
|
|
||||||
//noinspection JSUnresolvedVariable
|
|
||||||
chrome.fileSystem.isWritableEntry(fileEntry, function (isWritable) {
|
|
||||||
if (isWritable) {
|
|
||||||
var blob = new Blob([intel_hex], {type: 'text/plain'});
|
|
||||||
|
|
||||||
fileEntry.createWriter(function (writer) {
|
|
||||||
var truncated = false;
|
|
||||||
|
|
||||||
writer.onerror = function (e) {
|
|
||||||
console.error(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
writer.onwriteend = function () {
|
|
||||||
if (!truncated) {
|
|
||||||
// onwriteend will be fired again when truncation is finished
|
|
||||||
truncated = true;
|
|
||||||
writer.truncate(blob.size);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
writer.write(blob);
|
|
||||||
}, function (e) {
|
|
||||||
console.error(e);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('You don\'t have write permissions for this file, sorry.');
|
|
||||||
GUI.log(localization.getMessage('writePermissionsForFile'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.update_preview').on('change', function () {
|
$('.update_preview').on('change', function () {
|
||||||
if (OSD.data) {
|
if (OSD.data) {
|
||||||
// Force an OSD redraw by saving any element
|
// Force an OSD redraw by saving any element
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer_box">
|
<div class="spacer_box">
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<select id="receiver_type" data-setting="receiver_type"/>
|
<select id="receiver_type" data-setting="receiver_type"></select>
|
||||||
<label for="receiver_type">
|
<label for="receiver_type">
|
||||||
<span data-i18n="receiverType"></span>
|
<span data-i18n="receiverType"></span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -45,18 +45,18 @@
|
||||||
<div id="serialrx_provider-wrapper">
|
<div id="serialrx_provider-wrapper">
|
||||||
<div class="info-box ok-box" data-i18n="configurationSerialRXHelp"></div>
|
<div class="info-box ok-box" data-i18n="configurationSerialRXHelp"></div>
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<select id="serialrx_provider" data-setting="serialrx_provider"/>
|
<select id="serialrx_provider" data-setting="serialrx_provider"></select>
|
||||||
<label for="serialrx_provider">
|
<label for="serialrx_provider">
|
||||||
<span data-i18n="configurationSerialRX"></span>
|
<span data-i18n="configurationSerialRX"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<select id="serialrx_inverted" data-setting="serialrx_inverted"/>
|
<select id="serialrx_inverted" data-setting="serialrx_inverted"></select>
|
||||||
<label for="serialrx_inverted">
|
<label for="serialrx_inverted">
|
||||||
<span data-i18n="serialrx_inverted"></span>
|
<span data-i18n="serialrx_inverted"></span>
|
||||||
</label>
|
</label>
|
||||||
</div><div class="select">
|
</div><div class="select">
|
||||||
<select id="serialrx_halfduplex" data-setting="serialrx_halfduplex"/>
|
<select id="serialrx_halfduplex" data-setting="serialrx_halfduplex"></select>
|
||||||
<label for="serialrx_halfduplex">
|
<label for="serialrx_halfduplex">
|
||||||
<span data-i18n="serialrx_halfduplex"></span>
|
<span data-i18n="serialrx_halfduplex"></span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer_box">
|
<div class="spacer_box">
|
||||||
<div class="select">
|
<div class="select">
|
||||||
<select id="rc_filter_auto" data-setting="rc_filter_auto" />
|
<select id="rc_filter_auto" data-setting="rc_filter_auto"></select>
|
||||||
<span data-i18n="rc_filter_auto"></span>
|
<span data-i18n="rc_filter_auto"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -338,29 +338,16 @@ TABS.receiver.initialize = function (callback) {
|
||||||
});
|
});
|
||||||
|
|
||||||
$("a.sticks").click(function () {
|
$("a.sticks").click(function () {
|
||||||
var
|
var mspWin = window.open("tabs/receiver_msp.html", "receiver_msp", "width=420,height=720,menubar=no,contextIsolation=no,nodeIntegration=yes");
|
||||||
windowWidth = 420,
|
|
||||||
windowHeight = Math.min(window.innerHeight, 720);
|
mspWin.window.setRawRx = function (channels) {
|
||||||
|
if (CONFIGURATOR.connectionValid && GUI.active_tab != 'cli') {
|
||||||
window.open("/html/receiver_msp.html", {
|
mspHelper.setRawRx(channels);
|
||||||
id: "receiver_msp",
|
return true;
|
||||||
innerBounds: {
|
} else {
|
||||||
minWidth: windowWidth, minHeight: windowHeight,
|
return false;
|
||||||
width: windowWidth, height: windowHeight,
|
|
||||||
maxWidth: windowWidth, maxHeight: windowHeight
|
|
||||||
},
|
|
||||||
alwaysOnTop: true
|
|
||||||
}, function (createdWindow) {
|
|
||||||
// Give the window a callback it can use to send the channels (otherwise it can't see those objects)
|
|
||||||
createdWindow.contentWindow.setRawRx = function (channels) {
|
|
||||||
if (CONFIGURATOR.connectionValid && GUI.active_tab != 'cli') {
|
|
||||||
mspHelper.setRawRx(channels);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only show the MSP control sticks if the MSP Rx feature is enabled
|
// Only show the MSP control sticks if the MSP Rx feature is enabled
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script type="text/javascript" src="/js/libraries/jquery.nouislider.all.min.js"></script>
|
|
||||||
<script type="text/javascript" src="/js/tabs/receiver_msp.js"></script>
|
<script type="text/javascript" src="../js/localization.js"></script>
|
||||||
<link type="text/css" rel="stylesheet" href="./../css/receiver-msp.css" media="all" />
|
<script type="text/javascript" src="../tabs/receiver_msp.js"></script>
|
||||||
<script type="text/javascript" src="/js/localization.js"></script>
|
<script type="text/javascript" src="../js/libraries/jquery.nouislider.all.min.js"></script>
|
||||||
|
<link type="text/css" rel="stylesheet" href="../src/css/receiver-msp.css" media="all" />
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="control-gimbals">
|
<div class="control-gimbals">
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
window.$ = window.jQuery = require('jquery');
|
window.$ = window.jQuery = require('jquery');
|
||||||
|
|
||||||
var localization = new Localiziation("en");;
|
var localization = new Localiziation("en");
|
||||||
|
|
||||||
var
|
var
|
||||||
CHANNEL_MIN_VALUE = 1000,
|
CHANNEL_MIN_VALUE = 1000,
|
||||||
|
@ -11,10 +11,10 @@ var
|
||||||
|
|
||||||
// What's the index of each channel in the MSP channel list?
|
// What's the index of each channel in the MSP channel list?
|
||||||
channelMSPIndexes = {
|
channelMSPIndexes = {
|
||||||
roll: 0,
|
Roll: 0,
|
||||||
pitch: 1,
|
Pitch: 1,
|
||||||
yaw: 3,
|
Yaw: 3,
|
||||||
throttle: 2,
|
Throttle: 2,
|
||||||
ch5: 4,
|
ch5: 4,
|
||||||
ch6: 5,
|
ch6: 5,
|
||||||
ch7: 6,
|
ch7: 6,
|
||||||
|
@ -27,10 +27,10 @@ var
|
||||||
|
|
||||||
// Set reasonable initial stick positions (Mode 2)
|
// Set reasonable initial stick positions (Mode 2)
|
||||||
stickValues = {
|
stickValues = {
|
||||||
throttle: CHANNEL_MIN_VALUE,
|
Throttle: CHANNEL_MIN_VALUE,
|
||||||
pitch: CHANNEL_MID_VALUE,
|
Pitch: CHANNEL_MID_VALUE,
|
||||||
roll: CHANNEL_MID_VALUE,
|
Roll: CHANNEL_MID_VALUE,
|
||||||
yaw: CHANNEL_MID_VALUE,
|
Yaw: CHANNEL_MID_VALUE,
|
||||||
ch5: CHANNEL_MIN_VALUE,
|
ch5: CHANNEL_MIN_VALUE,
|
||||||
ch6: CHANNEL_MIN_VALUE,
|
ch6: CHANNEL_MIN_VALUE,
|
||||||
ch7: CHANNEL_MIN_VALUE,
|
ch7: CHANNEL_MIN_VALUE,
|
||||||
|
@ -43,8 +43,8 @@ var
|
||||||
|
|
||||||
// First the vertical axis, then the horizontal:
|
// First the vertical axis, then the horizontal:
|
||||||
gimbals = [
|
gimbals = [
|
||||||
["throttle", "yaw"],
|
["Throttle", "Yaw"],
|
||||||
["pitch", "roll"]
|
["Pitch", "Roll"]
|
||||||
],
|
],
|
||||||
|
|
||||||
gimbalElems,
|
gimbalElems,
|
||||||
|
@ -133,8 +133,9 @@ function localizeAxisNames() {
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
$("a.button-enable").click(function() {
|
$("a.button-enable").click(function() {
|
||||||
var
|
|
||||||
shrinkHeight = $(".warning").height();
|
|
||||||
|
var shrinkHeight = $(".warning").height();
|
||||||
|
|
||||||
$(".warning").slideUp("short", function() {
|
$(".warning").slideUp("short", function() {
|
||||||
window.current().innerBounds.minHeight -= shrinkHeight;
|
window.current().innerBounds.minHeight -= shrinkHeight;
|
||||||
|
@ -142,6 +143,7 @@ $(document).ready(function() {
|
||||||
window.current().innerBounds.maxHeight -= shrinkHeight;
|
window.current().innerBounds.maxHeight -= shrinkHeight;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
enableTX = true;
|
enableTX = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue