blob: c9aaac4cc5eef8be77cc36ce6b94b1404fcf5f3c [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Creates a "toggle control", which is a stylized checkbox with an icon. The
// onToggleCb callback is called every time the control changes state with the
// new toggle position (true for ON) and is expected to return a promise of the
// new toggle position which can resolve to the opposite position of the one
// received if there was error.
function createToggleControl(elm, iconName, onToggleCb, initialState = false) {
let icon = document.createElement('span');
icon.classList.add('toggle-control-icon');
icon.classList.add('material-icons-outlined');
if (iconName) {
icon.appendChild(document.createTextNode(iconName));
}
elm.appendChild(icon);
let toggle = document.createElement('label');
toggle.classList.add('toggle-control-switch');
let input = document.createElement('input');
input.type = 'checkbox';
input.checked = !!initialState;
input.onchange = e => {
let nextPr = onToggleCb(e.target.checked);
if (nextPr && 'then' in nextPr) {
nextPr.then(checked => {
e.target.checked = !!checked;
});
}
};
toggle.appendChild(input);
let slider = document.createElement('span');
slider.classList.add('toggle-control-slider');
toggle.appendChild(slider);
elm.classList.add('toggle-control');
elm.appendChild(toggle);
return {
// Sets the state of the toggle control. This only affects the
// visible state of the control in the UI, it doesn't affect the
// state of the underlying resources. It's most useful to make
// changes of said resources visible to the user.
Set: checked => input.checked = !!checked,
};
}
function createButtonListener(button_id_class, func,
deviceConnection, listener) {
let buttons = [];
let ele = document.getElementById(button_id_class);
if (ele != null) {
buttons.push(ele);
} else {
buttons = document.getElementsByClassName(button_id_class);
}
for (var button of buttons) {
if (func != null) {
button.onclick = func;
}
button.addEventListener('mousedown', listener);
}
}
function createInputListener(input_id, func, listener) {
input = document.getElementById(input_id);
if (func != null) {
input.oninput = func;
}
input.addEventListener('input', listener);
}
function validateMacAddress(val) {
var regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
return (regex.test(val));
}
function validateMacWrapper() {
let type = document.getElementById('bluetooth-wizard-type').value;
let button = document.getElementById("bluetooth-wizard-device");
let macField = document.getElementById('bluetooth-wizard-mac');
if (this.id == 'bluetooth-wizard-type') {
if (type == "remote_loopback") {
button.disabled = false;
macField.setCustomValidity('');
macField.disabled = true;
macField.required = false;
macField.placeholder = 'N/A';
macField.value = '';
return;
}
}
macField.disabled = false;
macField.required = true;
macField.placeholder = 'Device MAC';
if (validateMacAddress($(macField).val())) {
button.disabled = false;
macField.setCustomValidity('');
} else {
button.disabled = true;
macField.setCustomValidity('MAC address invalid');
}
}
$('[validate-mac]').bind('input', validateMacWrapper);
$('[validate-mac]').bind('select', validateMacWrapper);
function parseDevice(device) {
let id, name, mac;
var regex = /([0-9]+):([^@ ]*)(@(([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})))?/;
if (regex.test(device)) {
let regexMatches = device.match(regex);
id = regexMatches[1];
name = regexMatches[2];
mac = regexMatches[4];
}
if (mac === undefined) {
mac = "";
}
return [id, name, mac];
}
function btUpdateAdded(devices) {
let deviceArr = devices.split('\r\n');
let [id, name, mac] = parseDevice(deviceArr[0]);
if (name) {
let div = document.getElementById('bluetooth-wizard-confirm').getElementsByClassName('bluetooth-text')[1];
div.innerHTML = "";
div.innerHTML += "<p>Name: <b>" + id + "</b></p>";
div.innerHTML += "<p>Type: <b>" + name + "</b></p>";
div.innerHTML += "<p>MAC Addr: <b>" + mac + "</b></p>";
return true;
}
return false;
}
function parsePhy(phy) {
let id = phy.substring(0, phy.indexOf(":"));
phy = phy.substring(phy.indexOf(":") + 1);
let name = phy.substring(0, phy.indexOf(":"));
let devices = phy.substring(phy.indexOf(":") + 1);
return [id, name, devices];
}
function btParsePhys(phys) {
if (phys.indexOf("Phys:") < 0) {
return null;
}
let phyDict = {};
phys = phys.split('Phys:')[1];
let phyArr = phys.split('\r\n');
for (var phy of phyArr.slice(1)) {
phy = phy.trim();
if (phy.length == 0 || phy.indexOf("deleted") >= 0) {
continue;
}
let [id, name, devices] = parsePhy(phy);
phyDict[name] = id;
}
return phyDict;
}
function btUpdateDeviceList(devices) {
let deviceArr = devices.split('\r\n');
if (deviceArr[0].indexOf("Devices:") >= 0) {
let div = document.getElementById('bluetooth-list').getElementsByClassName('bluetooth-text')[0];
div.innerHTML = "";
let count = 0;
for (var device of deviceArr.slice(1)) {
if (device.indexOf("Phys:") >= 0) {
break;
}
count++;
if (device.indexOf("deleted") >= 0) {
continue;
}
let [id, name, mac] = parseDevice(device);
let innerDiv = '<div><button title="Delete" data-device-id="'
innerDiv += id;
innerDiv += '" class="bluetooth-list-trash material-icons">delete</button>';
innerDiv += name;
if (mac) {
innerDiv += " | "
innerDiv += mac;
}
innerDiv += '</div>';
div.innerHTML += innerDiv;
}
return count;
}
return -1;
}
function createControlPanelButton(
command, title, icon_name, listener,
parent_id = 'control-panel-default-buttons') {
let button = document.createElement('button');
document.getElementById(parent_id).appendChild(button);
button.title = title;
button.dataset.command = command;
button.disabled = true;
// Capture mousedown/up/out commands instead of click to enable
// hold detection. mouseout is used to catch if the user moves the
// mouse outside the button while holding down.
button.addEventListener('mousedown', listener);
button.addEventListener('mouseup', listener);
button.addEventListener('mouseout', listener);
// Set the button image using Material Design icons.
// See http://google.github.io/material-design-icons
// and https://material.io/resources/icons
button.classList.add('material-icons');
button.innerHTML = icon_name;
return button;
}
function positionModal(button_id, modal_id) {
const modalButton = document.getElementById(button_id);
const modalDiv = document.getElementById(modal_id);
// Position the modal to the right of the show modal button.
modalDiv.style.top = modalButton.offsetTop;
modalDiv.style.left = modalButton.offsetWidth + 30;
}
function createModalButton(button_id, modal_id, close_id, hide_id) {
const modalButton = document.getElementById(button_id);
const modalDiv = document.getElementById(modal_id);
const modalHeader = modalDiv.querySelector('.modal-header');
const modalClose = document.getElementById(close_id);
const modalDivHide = document.getElementById(hide_id);
positionModal(button_id, modal_id);
function showHideModal(show) {
if (show) {
modalButton.classList.add('modal-button-opened')
modalDiv.style.display = 'block';
} else {
modalButton.classList.remove('modal-button-opened')
modalDiv.style.display = 'none';
}
if (modalDivHide != null) {
modalDivHide.style.display = 'none';
}
}
// Allow the show modal button to toggle the modal,
modalButton.addEventListener(
'click', evt => showHideModal(modalDiv.style.display != 'block'));
// but the close button always closes.
modalClose.addEventListener('click', evt => showHideModal(false));
// Allow the modal to be dragged by the header.
let modalOffsets = {
midDrag: false,
mouseDownOffsetX: null,
mouseDownOffsetY: null,
};
modalHeader.addEventListener('mousedown', evt => {
modalOffsets.midDrag = true;
// Store the offset of the mouse location from the
// modal's current location.
modalOffsets.mouseDownOffsetX = parseInt(modalDiv.style.left) - evt.clientX;
modalOffsets.mouseDownOffsetY = parseInt(modalDiv.style.top) - evt.clientY;
});
modalHeader.addEventListener('mousemove', evt => {
let offsets = modalOffsets;
if (offsets.midDrag) {
// Move the modal to the mouse location plus the
// offset calculated on the initial mouse-down.
modalDiv.style.left = evt.clientX + offsets.mouseDownOffsetX;
modalDiv.style.top = evt.clientY + offsets.mouseDownOffsetY;
}
});
document.addEventListener('mouseup', evt => {
modalOffsets.midDrag = false;
});
}
function cmdConsole(consoleViewName, consoleInputName) {
let consoleView = document.getElementById(consoleViewName);
let addString =
function(str) {
consoleView.value += str;
consoleView.scrollTop = consoleView.scrollHeight;
}
let addLine =
function(line) {
addString(line + '\r\n');
}
let commandCallbacks = [];
let addCommandListener =
function(f) {
commandCallbacks.push(f);
}
let onCommand =
function(cmd) {
cmd = cmd.trim();
if (cmd.length == 0) return;
commandCallbacks.forEach(f => {
f(cmd);
})
}
addCommandListener(cmd => addLine('>> ' + cmd));
let consoleInput = document.getElementById(consoleInputName);
consoleInput.addEventListener('keydown', e => {
if ((e.key && e.key == 'Enter') || e.keyCode == 13) {
let command = e.target.value;
e.target.value = '';
onCommand(command);
}
});
return {
consoleView: consoleView,
consoleInput: consoleInput,
addLine: addLine,
addString: addString,
addCommandListener: addCommandListener,
};
}