/* * 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 += "

Name: " + id + "

"; div.innerHTML += "

Type: " + name + "

"; div.innerHTML += "

MAC Addr: " + mac + "

"; 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 = '
'; innerDiv += name; if (mac) { innerDiv += " | " innerDiv += mac; } innerDiv += '
'; 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, }; }