const $ = jQuery = require('jquery') const {ipcRenderer} = require('electron') const {Adb} = require("@devicefarmer/adbkit"); const toast = require('./helper/toasts.js') const AdbDevice = require('./models/AdbDevice.js') const LocalDevice = require('./models/LocalDevice.js') // client only used to detect if an ADB server is available let client = Adb.createClient() // Buttons const btnLoadConfig = $('#btnLoadConfig') const btnImportLogs = $('#btnImportLogs') const btnDeleteLogs = $('#btnDeleteLogs') const btnLoadAssetBundles = $('#btnLoadAssetBundles') const btnDeleteAssetBundles = $('#btnDeleteAssetBundles') const btnLoadVisuals = $('#btnLoadVisuals') const btnDeleteVisuals = $('#btnDeleteVisuals') const btnLoadSounds = $('#btnLoadSounds') const btnDeleteSounds = $('#btnDeleteSounds') const btnSearchDevices = $('#btnSearchDevices') // modal elements const modal = new bootstrap.Modal('#deleteModal') const modalTitle = $('#deleteModalTitle') const modalBody = $('#deleteModalBody') const modalBtn = $('#deleteModalBtn') const modalSpinner = $('#deleteModalSpinner') // list of devices, as objects and as a DOM element let devices = undefined; const lstDevices = $('#lstDevices') // ------------------ // UI event listeners // ------------------ // open file selector for copying a scenario btnLoadConfig.on('click', () => { ipcRenderer.send('openDialog', { type: 'file', filters: [{name: 'JSON', extensions: ['json']}], callback: 'configSelected' }) }) // open folder selector for copying logs btnImportLogs.on('click', () => { ipcRenderer.send('openDialog', { type: 'folder', callback: 'directorySelected' }) }) // open a confirmation dialog when deleting log files btnDeleteLogs.on('click', () => { modalTitle.text('Vous êtes en train de supprimer tous les logs. Êtes-vous sûr?') confirmDelete(getSelectedDevice().getPath(['ExperimentLogs'])) }) // call the main process to open a file dialog btnLoadAssetBundles.on('click', () => { ipcRenderer.send('openDialog', { type: 'file', multiselect: true, callback: 'filesSelected', subFolder: 'AssetBundles' }) }) // open a confirmation dialog when deleting AssenBundles btnDeleteAssetBundles.on('click', () => { modalTitle.text('Vous êtes en train de supprimer tous les AssetBundles. Êtes-vous sûr?') confirmDelete(getSelectedDevice().getPath(['AssetBundles'])) }) // call the main process to open a file dialog btnLoadSounds.on('click', () => { ipcRenderer.send('openDialog', { type: 'file', multiselect: true, callback: 'filesSelected', subFolder: 'Sounds', filters: [{name: 'Audio', extensions: ['wav', 'mp3']}] }) }) // open a confirmation dialog when deleting sound files btnDeleteSounds.on('click', () => { modalTitle.text('Vous êtes en train de supprimer tous les sons. Êtes-vous sûr?') confirmDelete(getSelectedDevice().getPath(['Sounds'])) }) // call the main process to open a file dialog btnLoadVisuals.on('click', () => { ipcRenderer.send('openDialog', { type: 'file', multiselect: true, callback: 'filesSelected', subFolder: 'Visuals', filters: [{name: 'Visuals', extensions: ['jpg', 'png', 'mp4']}] }) }) // open a confirmation dialog when deleting visual interference files btnDeleteVisuals.on('click', () => { modalTitle.text('Vous êtes en train de supprimer tous les visuels. Êtes-vous sûr?') confirmDelete(getSelectedDevice().getPath(['Visuals'])) }) // update the access path when the selected device changes lstDevices.on('change', () => { updatePath() }) // update the list of connected devices btnSearchDevices.on('click', async () => { await listDevices() }) // ------------------------------------- // listeners for calls from main process // ------------------------------------- // called after a file has been selected. decides depending on the selected device type what function must be // called ipcRenderer.on('configSelected', async (event, data) => { if (await getSelectedDevice().uploadConfig(data.filePaths[0])) { toast.create('Chargement du scénario complet.', 'text-bg-success') } else { toast.create('Erreur au chargement du scénario', 'text-bg-danger') } }) // called after a file has been selected. decides depending on the selected device type what function must be // called ipcRenderer.on('filesSelected', async (event, data) => { if (await getSelectedDevice().prepareAndUpload(data.filePaths, data.subFolder)) { toast.create('Chargement des fichiers complet.', 'text-bg-success') } else { toast.create('Erreur, chargement des fichiers incomplet.', 'text-bg-danger') } }) // called after a directory has been selected. decides depending on the selected device type what function must be // called ipcRenderer.on('directorySelected', async (event, data) => { if (await getSelectedDevice().downloadLogs(data.filePaths[0])) { toast.create('Téléchargement des logs complet.', 'text-bg-success') } else { toast.create('Erreur au téléchargement des logs.', 'text-bg-danger') } }) // --------- // functions // --------- /** * Open a modal dialog that asks for confirmation. Shows all files that will be deleted. * @param rootFolder the folder whose content will be deleted */ async function confirmDelete(rootFolder) { let device = getSelectedDevice() let fileList // try to get a list of file. might fail if the selected device is no longer connected try { fileList = await device.getFilesRecursively('', rootFolder, true) } catch (e) { console.error(e.message) toast.create(`Erreur de connexion avec l'appareil ${device.name}.`, 'text-bg-danger') return } // if folder contains no file, inform the user and then do nothing if (fileList.length === 0) { toast.create('Aucun fichier à supprimer sur ' + device, 'text-bg-primary') return } // create list of files that will be deleted let text = 'Les fichiers suivants vont être supprimés sur ' + device + ': ' // prepare the modal modalBody.html(text) modalSpinner.hide() modalBtn.prop('disabled', false) // remove prior listener and add new one modalBtn.unbind() modalBtn.on('click', async () => { // show loading with spinner modalSpinner.show() modalBtn.prop('disabled', true) // try to delete the data try { if (await device.remove(fileList, rootFolder)) { toast.create('Suppression complète.', 'text-bg-success') } else { toast.create('Erreur, la suppression des fichiers est incomplet.', 'text-bg-danger') } } catch (e) { console.error(e.mesage) toast.create(`Erreur de connexion avec l'appareil ${device.name}.`, 'text-bg-danger') } finally { modal.hide() } }) modal.show() } /** * List all ADB devices that are currently connected to this computer, including the computer itself, and put them into * the lstDevices select. */ async function listDevices() { // try to get a list of connected devices try { let ld = await LocalDevice.getDevices() let ad = await AdbDevice.getDevices() devices = ld.concat(ad) } catch (e) { console.error(e.message) toast.create('Erreur à la recherche des appareils.', 'text-bg-danger') return } // empty the list lstDevices.empty() for (let i = 0; i < devices.length; i++) { lstDevices.append(new Option(devices[i], i)) } // store devices in variable for later use toast.create(`Recherche d'appareil complet. ${devices.length} appareil(s) à dipsosition.`, 'text-bg-success') updatePath() } /** * Set the path under which the currently selected device is available. */ function updatePath() { $('#path span').text(getSelectedDevice().getPath()) } /** * Get the selected device */ function getSelectedDevice() { return devices[lstDevices.val()] } $(async () => { // check if an ADB server is available. if not: show a warning as toast try { let version = await client.version() console.log('adb version: ' + version) } catch (e) { toast.create( 'Les outils ADB ne sont pas detectés: des appareils Oculus Quest ne peuvent pas être utilisés.', 'text-bg-warning', 10000) console.warn('adb not installed') client = undefined } // update the list of connected devices await listDevices() updatePath() })