diff --git a/.gitignore b/.gitignore index 9b51971..55557e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ *.bak node_modules/ -/public/a/ -/public/s/ -/public/SpiderADB/ -/public/WuppiMini/ +/public/ diff --git a/Build.sh b/Build.sh index 78b579a..4fa2bb8 100755 --- a/Build.sh +++ b/Build.sh @@ -1,12 +1,24 @@ #!/bin/sh -for App in SpiderADB WuppiMini +SourceApps="SpiderADB WuppiMini" +HubSdkApps="$(SourceApps) Ecoji MatrixStickerHelper" + +rm -vrf ./public || true +cp -vr ./static ./public +cp -vr ./shared ./public/shared + +for App in $(SourceApps) do mkdir -p ./public/${App} - cd ./src/${App} + cd ./source/${App} sh ./Requirements.sh cp -r $(sh ./Build.sh) ../../public/${App}/ cd ../.. done -cp -r ./shared ./public/shared + cd ./public node ../WriteRedirectPages.js + +for App in $(HubSdkApps) +do + echo # TODO write manifest.json files +done diff --git a/shared/OctoHub-Global.js b/shared/OctoHub-Global.js index e69de29..bda5d55 100644 --- a/shared/OctoHub-Global.js +++ b/shared/OctoHub-Global.js @@ -0,0 +1,29 @@ +window.addEventListener('load', (function(){ + +if (!['', 'hub.octt.eu.org'].includes(location.host)) { + var noticeElem = document.createElement('p'); + noticeElem.style = ` + position: fixed; + z-index: 1000; + top: 0; + left: 0; + margin: 0; + width: 100%; + color: black; + background-color: thistle; + font-size: initial; + font-size: smaller; + text-align: center; + `; + noticeElem.innerHTML = `You are viewing this page on the secondary/backup domain. Open it on hub.octt.eu.org. + `; + document.body.appendChild(noticeElem); +} + +})); diff --git a/src/SpiderADB/.gitignore b/source/SpiderADB/.gitignore similarity index 100% rename from src/SpiderADB/.gitignore rename to source/SpiderADB/.gitignore diff --git a/src/SpiderADB/Build.sh b/source/SpiderADB/Build.sh similarity index 100% rename from src/SpiderADB/Build.sh rename to source/SpiderADB/Build.sh diff --git a/src/SpiderADB/Requirements.sh b/source/SpiderADB/Requirements.sh similarity index 100% rename from src/SpiderADB/Requirements.sh rename to source/SpiderADB/Requirements.sh diff --git a/src/SpiderADB/SpiderADB.js b/source/SpiderADB/SpiderADB.js similarity index 52% rename from src/SpiderADB/SpiderADB.js rename to source/SpiderADB/SpiderADB.js index 7696391..49f38ba 100644 --- a/src/SpiderADB/SpiderADB.js +++ b/source/SpiderADB/SpiderADB.js @@ -1,48 +1,54 @@ import * as Adb from '@yume-chan/adb'; import * as AdbDaemonWebUsb from '@yume-chan/adb-daemon-webusb'; import AdbWebCredentialStore from '@yume-chan/adb-credential-web'; -import { DecodeUtf8Stream } from '@yume-chan/stream-extra'; +import { DecodeUtf8Stream, WrapReadableStream, WrapConsumableStream } from '@yume-chan/stream-extra'; +import { PackageManager } from '@yume-chan/android-bin'; // TODO: // * warning on fail to claim USB interface (it may be because of other tabs, or a local adb server) // * warn or gracefully handle debug permission not granted // * package manager with install/uninstall/dump, debloat tool with default list and import/export // * fastboot shell and tools? possible? +// * logs for Packages section? (async function(){ const deviceSelect = $('select$deviceSelect$'); -const deviceConnect = $('button$deviceConnect$'); const terminalOutput = $('textarea$terminalOutput$'); -function resizeTerminal () { - terminalOutput.style.height = `${window.innerHeight - ((48 + 8) * 4)}px`; -} -resizeTerminal(); - -window.addEventListener('resize', (function(){ - resizeTerminal(); -})); - -$('input$terminalInput$').addEventListener('keydown', (async function(event){ - if (event.keyCode == 13) { // Enter - const cmd = $('input$terminalInput$').value; - terminalOutput.textContent += (cmd + '\n'); - const process = await Device.adb.subprocess.spawn(cmd); - await process.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo( - new WritableStream({ write(chunk) { - terminalOutput.textContent += chunk; - terminalOutput.scrollTop = terminalOutput.scrollHeight; - } }), - ); - terminalOutput.textContent += '\n> '; - $('input$terminalInput$').value = null; - }; -})); +$('button$apkInstall$').onclick = (async function(){ + // TODO show info popup before actually installing, also allow installing via drag&drop on packages section + const fileInput = $('button$apkInstall$').querySelector('input'); + fileInput.onchange = (function(event){ + const count = event.target.files.length; + if (!count > 0) { + return; + } + alert(`Installing ${count} package(s)...`); + const pm = new PackageManager(Device.adb); + Array.from(event.target.files).forEach(async function(file, index){ + try { + await pm.installStream(file.size, (new WrapReadableStream(file.stream())).pipeThrough(new WrapConsumableStream())); + alert(`Successfully installed package ${index + 1} of ${count}.`); + refreshPackagesList(); + } catch (err) { + alert(err); + } + }); + }); + fileInput.click(); +}); let Device = {}; const CredentialStore = new AdbWebCredentialStore(); +function resizeTerminal () { + const divider = (Device.adb ? 2 : 3); + terminalOutput.style.height = `${window.innerHeight - ((48 + 8) * divider)}px`; +} +window.addEventListener('resize', resizeTerminal); +resizeTerminal(); + const UsbManager = AdbDaemonWebUsb.AdbDaemonWebUsbDeviceManager.BROWSER; if (!UsbManager) { $('div$browserWarning$').innerHTML = `
@@ -79,8 +85,10 @@ async function connectAuthorizeDevice () { Device.transport = await Adb.AdbDaemonTransport.authenticate({ connection: Device.connection, credentialStore: CredentialStore }); Device.adb = new Adb.Adb(Device.transport); } catch (err) { - $('[name="deviceStatus"]').innerHTML = 'An error occurred while trying to establish a device connection. Please ensure that no other processes or browser tabs on this system are currently using the device, then retry.'; + $('p$deviceStatus$').textContent = 'An error occurred while trying to establish a device connection. Please ensure that no other processes or browser tabs on this system are currently using the device, then retry.'; } + } else { + $('p$deviceStatus$').textContent = null; } } @@ -103,15 +111,18 @@ async function refreshDeviceSelect () { deviceSelect.innerHTML = null; const devices = await UsbManager.getDevices(); if (devices.length) { + $('p$deviceStatus$').textContent = null; deviceSelect.innerHTML = ''; devices.forEach(function(device, index){ - var deviceOption = document.createElement('option'); + const deviceOption = document.createElement('option'); deviceOption.textContent = `${device.raw.productName} [${device.raw.serialNumber}]`; deviceSelect.appendChild(deviceOption); }); deviceSelect.onchange = onSwitchDevice; deviceSelect.disabled = false; } else { + // TODO probably put this warning elsewhere? because it seems like the browser can see even Androids with ADB disabled, obviously they won't be able to connect + $('p$deviceStatus$').innerHTML = 'Connect a debuggable Android device via USB to continue. (Read "How to Enable USB Debugging on Android" for help).'; deviceSelect.innerHTML = ''; } } @@ -123,7 +134,8 @@ async function onSwitchDevice () { } async function refreshDeviceInfo () { - if (deviceSelect.selectedIndex > 0) { + let onDevice = (deviceSelect.selectedIndex > 0); + if (onDevice) { const device = await getDevice(); $('$deviceOem$').innerHTML = `Brand: ${device.raw.manufacturerName}`; $('$deviceModel$').innerHTML = `Model: ${device.raw.productName}`; @@ -133,29 +145,38 @@ async function refreshDeviceInfo () { if (Device.adb) { $('$deviceStatus$').innerHTML = null; // $('$devicePropDump$').innerHTML = null; + $('$deviceCpuAbis$').innerHTML = `CPU ABIs: ${await Device.adb.getProp('ro.system.product.cpu.abilist')}`; $('$androidVersion$').innerHTML = `Android version: ${await Device.adb.getProp('ro.build.version.release')}`; - $('$androidApi$').innerHTML = `API version: ${await Device.adb.getProp('ro.build.version.sdk')}`; + $('$androidApi$').innerHTML = `SDK API version: ${await Device.adb.getProp('ro.build.version.sdk')}`; + //$('$androidNickname$').innerHTML = `Device name: ${await Device.adb.getProp('persist.sys.device_name')}`; + $('$androidBuildDate$').innerHTML = `Build date: ${await Device.adb.getProp('ro.vendor.build.date')}`; + $('$androidBuildFingerprint$').innerHTML = `Build fingerprint: ${await Device.adb.getProp('ro.vendor.build.fingerprint')}`; $('$androidInfo$').hidden = false; $('$connectReminder$').hidden = true; terminalOutput.disabled = false; - terminalOutput.textContent += '> '; - $('input$terminalInput$').disabled = false; + terminalOutput.textContent += (terminalOutput.textContent ? '\n> ' : '> '); + $('button$clearTerminal$').disabled = false; /* for (const line of (await Device.adb.getProp()).split('\n')) { const elem = document.createElement('li'); elem.textContent = line; $('$devicePropDump$').appendChild(elem); } */ } else { - $('$deviceInfo$').hidden = true; + onDevice = false; } - $('$deviceInfo$').hidden = false; } else { - $('$deviceStatus$').innerHTML = null; + //$('$deviceStatus$').innerHTML = null; $('$connectReminder$').hidden = false; terminalOutput.disabled = true; - $('input$terminalInput$').disabled = true; - $('$deviceInfo$').hidden = true; } + toggleDeviceElems(onDevice); +} + +function toggleDeviceElems (enabled) { + $('$deviceInfo$').hidden = !enabled; + $('button$apkInstall$').disabled = !enabled; + $('input$terminalInput$').disabled = !enabled; + resizeTerminal(); } async function refreshDeviceSection () { @@ -164,7 +185,7 @@ async function refreshDeviceSection () { } refreshDeviceSection(); -deviceConnect.onclick = (async function(){ +$('button$deviceConnect$').onclick = (async function(){ const device = await UsbManager.requestDevice(); if (!device) { return; @@ -174,6 +195,60 @@ deviceConnect.onclick = (async function(){ deviceSelect.selectedIndex = (deviceSelect.children.length - 1); deviceSelect.onchange(); }); -deviceConnect.disabled = false; +$('button$deviceConnect$').disabled = false; + +$('input$terminalInput$').addEventListener('keydown', (async function(event){ + if (event.keyCode == 13) { // Enter + const cmd = $('input$terminalInput$').value; + if (!terminalOutput.textContent) { + terminalOutput.textContent += '> '; + } + terminalOutput.textContent += (cmd + '\n'); + const process = await Device.adb.subprocess.spawn(cmd); + const processWriteToTerminal = () => new WritableStream({ write(chunk) { + terminalOutput.textContent += chunk; + terminalOutput.scrollTop = terminalOutput.scrollHeight; + $('button$clearTerminal$').disabled = false; + } }); + await process.stdout.pipeThrough(new DecodeUtf8Stream()).pipeTo(processWriteToTerminal()); + await process.stderr.pipeThrough(new DecodeUtf8Stream()).pipeTo(processWriteToTerminal()); + terminalOutput.textContent += '\n> '; + terminalOutput.scrollTop = terminalOutput.scrollHeight; + $('input$terminalInput$').value = null; + }; +})); + +$('button$clearTerminal$').onclick = (function(){ + terminalOutput.textContent = ''; + $('button$clearTerminal$').disabled = true; +}); + +$('button$wrapTerminal$').onclick = (function(){ + terminalOutput.style.textWrap = (terminalOutput.style.textWrap ? '' : 'nowrap'); + terminalOutput.scrollTop = terminalOutput.scrollHeight; +}); + +async function refreshPackagesList () { + $('ul$packageList$').innerHTML = null; + const pm = new PackageManager(Device.adb); + const list = await pm.listPackages(); + let result = await list.next(); + while (!result.done) { + var packageElem = document.createElement('li'); + packageElem.innerHTML = `${result.value.packageName}`; + /* packageElem.querySelector('input').onclick = (function(){ + // TODO: hide or show action buttons that do actions on selected elements if there is none or at least one + }); */ + $('ul$packageList$').appendChild(packageElem); + result = await list.next(); + } +} + +window.addEventListener('hashchange', (async function(){ + const sectionHash = location.hash.slice(2).split('/')[0]; + if (Device.adb && sectionHash === 'packages' /* && !$('ul$packageList$').innerHTML */) { + refreshPackagesList(); + } +})); })(); diff --git a/src/SpiderADB/holo-web/holo-base-elements.css b/source/SpiderADB/holo-web/holo-base-elements.css similarity index 100% rename from src/SpiderADB/holo-web/holo-base-elements.css rename to source/SpiderADB/holo-web/holo-base-elements.css diff --git a/src/SpiderADB/holo-web/holo-base-widgets.css b/source/SpiderADB/holo-web/holo-base-widgets.css similarity index 100% rename from src/SpiderADB/holo-web/holo-base-widgets.css rename to source/SpiderADB/holo-web/holo-base-widgets.css diff --git a/src/SpiderADB/holo-web/holo-extra-octt.css b/source/SpiderADB/holo-web/holo-extra-octt.css similarity index 95% rename from src/SpiderADB/holo-web/holo-extra-octt.css rename to source/SpiderADB/holo-web/holo-extra-octt.css index 5285c73..c850159 100644 --- a/src/SpiderADB/holo-web/holo-extra-octt.css +++ b/source/SpiderADB/holo-web/holo-extra-octt.css @@ -27,7 +27,7 @@ a[data-action-section] { /* width: 60%; */ height: 100vh; z-index: 1; - background: rgba(0, 0, 0, 0.5); + background: rgba(0, 0, 0, 0.6); } .holo-sidebar[data-open="open"], .holo-sideBar[data-open="open"] { @@ -37,7 +37,7 @@ a[data-action-section] { .holo-sidebar .holo-list, .holo-sideBar .holo-list { width: 60%; height: 100vh; - background: black; + background: rgba(0, 0, 0, 0.9); } .actionBar .holo-title.holo-menu, .holo-actionBar .holo-title.holo-menu { diff --git a/src/SpiderADB/holo-web/holo-extra-octt.js b/source/SpiderADB/holo-web/holo-extra-octt.js similarity index 73% rename from src/SpiderADB/holo-web/holo-extra-octt.js rename to source/SpiderADB/holo-web/holo-extra-octt.js index 078251e..5edd016 100644 --- a/src/SpiderADB/holo-web/holo-extra-octt.js +++ b/source/SpiderADB/holo-web/holo-extra-octt.js @@ -1,13 +1,17 @@ window.addEventListener('load', (function() { +var initialSectionName = $('[data-section][data-open]').dataset.section; + $('::[data-action-sidebar]').forEach(function(actionSidebarElem){ var sidebarElem = $('[data-sidebar="' + actionSidebarElem.dataset.actionSidebar + '"]'); - actionSidebarElem.onclick = sidebarElem.onclick = (function(){ + function toggleSidebar () { sidebarElem.dataset.open = (sidebarElem.dataset.open !== 'open' ? 'open' : false); - }); - sidebarElem.querySelector('.holo-list').onclick = (function(event){ + } + actionSidebarElem.addEventListener('click', toggleSidebar); + sidebarElem.addEventListener('click', toggleSidebar); + sidebarElem.querySelector('.holo-list').addEventListener('click', (function(event){ event.stopPropagation(); - }); + })); arrayFrom(sidebarElem.querySelectorAll('.holo-list li button, .holo-list li [role="button"]')).forEach(function(buttonElem){ buttonElem.addEventListener('click', (function(){ sidebarElem.dataset.open = false; @@ -40,9 +44,11 @@ function refreshDisplaySections (sectionTargetName) { } refreshDisplaySections(); -var sectionHash = location.hash.slice(2).split('/')[0]; -if (sectionHash) { - $(`[data-action-section="${sectionHash}"]`).click(); +function hashChange () { + var sectionHash = location.hash.slice(2).split('/')[0]; + $(`[data-action-section="${sectionHash || initialSectionName}"]`).click(); } +window.addEventListener('hashchange', hashChange); +hashChange(); })); diff --git a/src/SpiderADB/holo-web/holo-ics-dark-elements.css b/source/SpiderADB/holo-web/holo-ics-dark-elements.css similarity index 100% rename from src/SpiderADB/holo-web/holo-ics-dark-elements.css rename to source/SpiderADB/holo-web/holo-ics-dark-elements.css diff --git a/src/SpiderADB/holo-web/holo-ics-dark-widgets.css b/source/SpiderADB/holo-web/holo-ics-dark-widgets.css similarity index 100% rename from src/SpiderADB/holo-web/holo-ics-dark-widgets.css rename to source/SpiderADB/holo-web/holo-ics-dark-widgets.css diff --git a/src/SpiderADB/holo-web/holo-menu.png b/source/SpiderADB/holo-web/holo-menu.png similarity index 100% rename from src/SpiderADB/holo-web/holo-menu.png rename to source/SpiderADB/holo-web/holo-menu.png diff --git a/src/SpiderADB/holo-web/holo-touch.js b/source/SpiderADB/holo-web/holo-touch.js similarity index 100% rename from src/SpiderADB/holo-web/holo-touch.js rename to source/SpiderADB/holo-web/holo-touch.js diff --git a/src/SpiderADB/index.html b/source/SpiderADB/index.html similarity index 52% rename from src/SpiderADB/index.html rename to source/SpiderADB/index.html index a2f02de..8242663 100644 --- a/src/SpiderADB/index.html +++ b/source/SpiderADB/index.html @@ -14,8 +14,16 @@
@@ -26,6 +34,16 @@ + + +SpiderADB is an user-friendly webapp for connecting to devices via the Android Debug Bridge, straight from a browser. More infos coming soon.
+SpiderADB is an user-friendly webapp for connecting to devices via the Android Debug Bridge, straight from a browser.
+ +Here are some additional tips and tricks you might find useful to make the most out of this app:
++ This app is open-source and made with mostly-vanilla web technologies. You can find the full source code on my Git repo: https://gitlab.com/octospacc/octospacc.gitlab.io/-/tree/master/source/SpiderADB/. +
Also, this app wouldn't have been possible without these third-party components, of which the license is specified in brackets: