Update SpiderADB; Update apps metadata
This commit is contained in:
parent
d11ee0f162
commit
04c4012171
32
Build.sh
32
Build.sh
|
@ -1,6 +1,15 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
SourceApps="SpiderADB WuppiMini"
|
SourceApps="SpiderADB WuppiMini"
|
||||||
HubSdkApps="${SourceApps} Ecoji MatrixStickerHelper"
|
HubSdkApps="${SourceApps} MatrixStickerHelper"
|
||||||
|
|
||||||
|
quoteVar(){ echo '"'"$1"'"' ;}
|
||||||
|
|
||||||
|
getMetaAttr(){
|
||||||
|
file="$1"
|
||||||
|
name="$2"
|
||||||
|
key="$([ -n "$3" ] && echo "$3" || echo "property")"
|
||||||
|
grep '<meta '"$key"'="'"$name"'"' "$file" | grep '>' | cut -d '"' -f4
|
||||||
|
}
|
||||||
|
|
||||||
rm -vrf ./public || true
|
rm -vrf ./public || true
|
||||||
cp -vr ./static ./public
|
cp -vr ./static ./public
|
||||||
|
@ -8,10 +17,10 @@ cp -vr ./shared ./public/shared
|
||||||
|
|
||||||
for App in ${SourceApps}
|
for App in ${SourceApps}
|
||||||
do
|
do
|
||||||
mkdir -p ./public/${App}
|
mkdir -p "./public/${App}"
|
||||||
cd ./source/${App}
|
cd "./source/${App}"
|
||||||
sh ./Requirements.sh
|
sh ./Requirements.sh
|
||||||
cp -r $(sh ./Build.sh) ../../public/${App}/
|
cp -vr $(sh ./Build.sh) "../../public/${App}/"
|
||||||
cd ../..
|
cd ../..
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@ -20,5 +29,18 @@ node ../WriteRedirectPages.js
|
||||||
|
|
||||||
for App in ${HubSdkApps}
|
for App in ${HubSdkApps}
|
||||||
do
|
do
|
||||||
echo # TODO write manifest.json files
|
file="./${App}/index.html"
|
||||||
|
name="$(getMetaAttr "${file}" og:title)"
|
||||||
|
description="$(getMetaAttr "${file}" og:description property)"
|
||||||
|
url="$(getMetaAttr "${file}" Url OctoSpaccHubSdk)" #"$(getMetaAttr "${file}" og:url property)"
|
||||||
|
cat << [OctoSpaccHubSdk-WebManifest-EOF] > "./${App}/WebManifest.json"
|
||||||
|
{
|
||||||
|
$(getMetaAttr "${file}" WebManifestExtra OctoSpaccHubSdk | sed s/\'/\"/g)
|
||||||
|
$([ -n "${description}" ] && echo "$(quoteVar description): $(quoteVar "${description}"),")
|
||||||
|
"start_url": "${url}",
|
||||||
|
"scope": "${url}",
|
||||||
|
"name": "${name}"
|
||||||
|
}
|
||||||
|
[OctoSpaccHubSdk-WebManifest-EOF]
|
||||||
|
sed -i 's|</head>|<title>'"${name}"'</title><link rel="manifest" href="./WebManifest.json"/></head>|' "${file}"
|
||||||
done
|
done
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
npm update
|
npm update
|
||||||
npm install
|
npm install
|
||||||
yes | npx esbuild
|
yes | npx esbuild --help > /dev/null # Install esbuild
|
||||||
|
|
|
@ -1,47 +1,35 @@
|
||||||
import * as Adb from '@yume-chan/adb';
|
import { Adb, AdbDaemonTransport } from '@yume-chan/adb';
|
||||||
import * as AdbDaemonWebUsb from '@yume-chan/adb-daemon-webusb';
|
import { AdbDaemonWebUsbDeviceManager, AdbDaemonWebUsbDeviceWatcher } from '@yume-chan/adb-daemon-webusb';
|
||||||
import AdbWebCredentialStore from '@yume-chan/adb-credential-web';
|
import AdbWebCredentialStore from '@yume-chan/adb-credential-web';
|
||||||
import { DecodeUtf8Stream, WrapReadableStream, WrapConsumableStream } from '@yume-chan/stream-extra';
|
import { DecodeUtf8Stream, WrapReadableStream, WrapConsumableStream } from '@yume-chan/stream-extra';
|
||||||
import { PackageManager } from '@yume-chan/android-bin';
|
import { PackageManager } from '@yume-chan/android-bin';
|
||||||
|
|
||||||
// TODO:
|
// 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
|
// * package manager with install/uninstall/dump, debloat tool with default list and import/export
|
||||||
// * fastboot shell and tools? possible?
|
// * fastboot shell and tools? possible?
|
||||||
// * logs for Packages section?
|
// * logs for Packages section?
|
||||||
|
|
||||||
(async function(){
|
(async function(){
|
||||||
|
|
||||||
|
$('p$javascriptWarning$').remove();
|
||||||
|
$('.holo-section[data-section="about"]').style = null;
|
||||||
|
$('p$connectReminder$').hidden = false;
|
||||||
|
|
||||||
const deviceSelect = $('select$deviceSelect$');
|
const deviceSelect = $('select$deviceSelect$');
|
||||||
const terminalOutput = $('textarea$terminalOutput$');
|
const terminalOutput = $('textarea$terminalOutput$');
|
||||||
|
|
||||||
$('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 = {};
|
let Device = {};
|
||||||
const CredentialStore = new AdbWebCredentialStore();
|
const CredentialStore = new AdbWebCredentialStore();
|
||||||
|
|
||||||
|
function popupBox (text) {
|
||||||
|
$('p$popupBox$').hidden = null;
|
||||||
|
$('p$popupBox$').innerHTML = `${text} <button>X</button>`;
|
||||||
|
$('p$popupBox$').querySelector('button').onclick = (function(){
|
||||||
|
$('p$popupBox$').hidden = true;
|
||||||
|
$('p$popupBox$').innerHTML = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function resizeTerminal () {
|
function resizeTerminal () {
|
||||||
const divider = (Device.adb ? 2 : 3);
|
const divider = (Device.adb ? 2 : 3);
|
||||||
terminalOutput.style.height = `${window.innerHeight - ((48 + 8) * divider)}px`;
|
terminalOutput.style.height = `${window.innerHeight - ((48 + 8) * divider)}px`;
|
||||||
|
@ -49,7 +37,7 @@ function resizeTerminal () {
|
||||||
window.addEventListener('resize', resizeTerminal);
|
window.addEventListener('resize', resizeTerminal);
|
||||||
resizeTerminal();
|
resizeTerminal();
|
||||||
|
|
||||||
const UsbManager = AdbDaemonWebUsb.AdbDaemonWebUsbDeviceManager.BROWSER;
|
const UsbManager = AdbDaemonWebUsbDeviceManager.BROWSER;
|
||||||
if (!UsbManager) {
|
if (!UsbManager) {
|
||||||
$('div$browserWarning$').innerHTML = `<p>
|
$('div$browserWarning$').innerHTML = `<p>
|
||||||
<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/USB#browser_compatibility">WebUSB is not supported</a> in this browser, so the app cannot work.
|
<a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/USB#browser_compatibility">WebUSB is not supported</a> in this browser, so the app cannot work.
|
||||||
|
@ -70,7 +58,7 @@ if (!UsbManager) {
|
||||||
return; // kill the app
|
return; // kill the app
|
||||||
}
|
}
|
||||||
|
|
||||||
new AdbDaemonWebUsb.AdbDaemonWebUsbDeviceWatcher((async function(connectedDevice){
|
new AdbDaemonWebUsbDeviceWatcher((async function(connectedDevice){
|
||||||
if (!connectedDevice) {
|
if (!connectedDevice) {
|
||||||
await disconnectDevice();
|
await disconnectDevice();
|
||||||
}
|
}
|
||||||
|
@ -82,8 +70,8 @@ async function connectAuthorizeDevice () {
|
||||||
Device.device = await getDevice();
|
Device.device = await getDevice();
|
||||||
try {
|
try {
|
||||||
Device.connection = await Device.device.connect();
|
Device.connection = await Device.device.connect();
|
||||||
Device.transport = await Adb.AdbDaemonTransport.authenticate({ connection: Device.connection, credentialStore: CredentialStore });
|
Device.transport = await AdbDaemonTransport.authenticate({ connection: Device.connection, credentialStore: CredentialStore });
|
||||||
Device.adb = new Adb.Adb(Device.transport);
|
Device.adb = new Adb(Device.transport);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$('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.';
|
$('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.';
|
||||||
}
|
}
|
||||||
|
@ -144,7 +132,6 @@ async function refreshDeviceInfo () {
|
||||||
//$('$deviceInfo$').hidden = false;
|
//$('$deviceInfo$').hidden = false;
|
||||||
if (Device.adb) {
|
if (Device.adb) {
|
||||||
$('$deviceStatus$').innerHTML = null;
|
$('$deviceStatus$').innerHTML = null;
|
||||||
// $('$devicePropDump$').innerHTML = null;
|
|
||||||
$('$deviceCpuAbis$').innerHTML = `<b>CPU ABIs</b>: ${await Device.adb.getProp('ro.system.product.cpu.abilist')}`;
|
$('$deviceCpuAbis$').innerHTML = `<b>CPU ABIs</b>: ${await Device.adb.getProp('ro.system.product.cpu.abilist')}`;
|
||||||
$('$androidVersion$').innerHTML = `<b>Android version</b>: ${await Device.adb.getProp('ro.build.version.release')}`;
|
$('$androidVersion$').innerHTML = `<b>Android version</b>: ${await Device.adb.getProp('ro.build.version.release')}`;
|
||||||
$('$androidApi$').innerHTML = `<b>SDK API version</b>: ${await Device.adb.getProp('ro.build.version.sdk')}`;
|
$('$androidApi$').innerHTML = `<b>SDK API version</b>: ${await Device.adb.getProp('ro.build.version.sdk')}`;
|
||||||
|
@ -156,11 +143,6 @@ async function refreshDeviceInfo () {
|
||||||
terminalOutput.disabled = false;
|
terminalOutput.disabled = false;
|
||||||
terminalOutput.textContent += (terminalOutput.textContent ? '\n> ' : '> ');
|
terminalOutput.textContent += (terminalOutput.textContent ? '\n> ' : '> ');
|
||||||
$('button$clearTerminal$').disabled = false;
|
$('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 {
|
} else {
|
||||||
onDevice = false;
|
onDevice = false;
|
||||||
}
|
}
|
||||||
|
@ -175,8 +157,13 @@ async function refreshDeviceInfo () {
|
||||||
function toggleDeviceElems (enabled) {
|
function toggleDeviceElems (enabled) {
|
||||||
$('$deviceInfo$').hidden = !enabled;
|
$('$deviceInfo$').hidden = !enabled;
|
||||||
$('button$apkInstall$').disabled = !enabled;
|
$('button$apkInstall$').disabled = !enabled;
|
||||||
|
$('button$packagesUninstall$').disabled = true;
|
||||||
|
$('button$packagesInvertSelect$').disabled = !enabled;
|
||||||
$('input$terminalInput$').disabled = !enabled;
|
$('input$terminalInput$').disabled = !enabled;
|
||||||
resizeTerminal();
|
resizeTerminal();
|
||||||
|
if (!enabled) {
|
||||||
|
$('ul$packageList$').innerHTML = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshDeviceSection () {
|
async function refreshDeviceSection () {
|
||||||
|
@ -228,25 +215,86 @@ $('button$wrapTerminal$').onclick = (function(){
|
||||||
terminalOutput.scrollTop = terminalOutput.scrollHeight;
|
terminalOutput.scrollTop = terminalOutput.scrollHeight;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('button$apkInstall$').onclick = (async function(){
|
||||||
|
// TODO allow installing via drag&drop on packages section
|
||||||
|
const fileInput = this.querySelector('input');
|
||||||
|
fileInput.onchange = (async function(event){
|
||||||
|
const count = event.target.files.length;
|
||||||
|
if (!count > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
popupBox(`Installing ${count} package(s)...`);
|
||||||
|
const pm = new PackageManager(Device.adb);
|
||||||
|
for (const index in Object.keys(event.target.files)) {
|
||||||
|
const file = event.target.files[index];
|
||||||
|
try {
|
||||||
|
await pm.installStream(file.size, (new WrapReadableStream(file.stream())).pipeThrough(new WrapConsumableStream()));
|
||||||
|
popupBox(`Successfully installed package ${Number(index) + 1} of ${count}.`);
|
||||||
|
refreshPackagesList();
|
||||||
|
} catch (err) {
|
||||||
|
popupBox('Operation has failed:' + err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fileInput.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('button$packagesUninstall$').onclick = (async function(event){
|
||||||
|
const checkedPackagesElems = $('ul$packageList$').querySelectorAll('input[type="checkbox"]:checked');
|
||||||
|
const count = checkedPackagesElems.length;
|
||||||
|
if (!confirm(`Confirm uninstalling ${count} packages? (Currently only works on user packages, will fail on system ones.)`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('button$packagesUninstall$').disabled = true;
|
||||||
|
const pm = new PackageManager(Device.adb);
|
||||||
|
for (const index in Object.keys(checkedPackagesElems)) {
|
||||||
|
try {
|
||||||
|
const packageElem = checkedPackagesElems[index].parentElement;
|
||||||
|
const packageName = packageElem.textContent.trim();
|
||||||
|
await pm.uninstall(packageName);
|
||||||
|
popupBox(`Successfully uninstalled package ${Number(index) + 1} of ${count}.`);
|
||||||
|
refreshPackagesList();
|
||||||
|
} catch(err) {
|
||||||
|
popupBox('Operation has failed:' + err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$('button$packagesUninstall$').disabled = false;
|
||||||
|
refreshPackagesList();
|
||||||
|
})
|
||||||
|
|
||||||
|
$('button$packagesInvertSelect$').onclick = (function(){
|
||||||
|
Array.from($('ul$packageList$').querySelectorAll('input[type="checkbox"]')).forEach(function(packageElem){
|
||||||
|
packageElem.checked = !packageElem.checked;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
async function refreshPackagesList () {
|
async function refreshPackagesList () {
|
||||||
$('ul$packageList$').innerHTML = null;
|
$('ul$packageList$').innerHTML = null;
|
||||||
|
$('button$packagesUninstall$').disabled = true;
|
||||||
const pm = new PackageManager(Device.adb);
|
const pm = new PackageManager(Device.adb);
|
||||||
const list = await pm.listPackages();
|
const list = await pm.listPackages();
|
||||||
|
const checkedList = {};
|
||||||
|
let index = 0;
|
||||||
let result = await list.next();
|
let result = await list.next();
|
||||||
while (!result.done) {
|
while (!result.done) {
|
||||||
var packageElem = document.createElement('li');
|
var packageElem = document.createElement('li');
|
||||||
packageElem.innerHTML = `${result.value.packageName}<!-- <input type="checkbox" class="floatRight"/>-->`;
|
packageElem.index = index;
|
||||||
/* packageElem.querySelector('input').onclick = (function(){
|
packageElem.innerHTML = `${result.value.packageName} <input type="checkbox" class="floatRight"/>`;
|
||||||
// TODO: hide or show action buttons that do actions on selected elements if there is none or at least one
|
packageElem.querySelector('input').onchange = (function(){
|
||||||
}); */
|
checkedList[this.parentElement.index] = this.checked;
|
||||||
|
$('button$packagesUninstall$').disabled = !Object.values(checkedList).includes(true);
|
||||||
|
});
|
||||||
$('ul$packageList$').appendChild(packageElem);
|
$('ul$packageList$').appendChild(packageElem);
|
||||||
|
index++;
|
||||||
result = await list.next();
|
result = await list.next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('hashchange', (async function(){
|
window.addEventListener('hashchange', (async function(){
|
||||||
const sectionHash = location.hash.slice(2).split('/')[0];
|
const sectionHash = location.hash.slice(2).split('/')[0];
|
||||||
if (Device.adb && sectionHash === 'packages' /* && !$('ul$packageList$').innerHTML */) {
|
if (Device.adb && sectionHash === 'packages') {
|
||||||
refreshPackagesList();
|
refreshPackagesList();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -27,7 +27,7 @@ a[data-action-section] {
|
||||||
/* width: 60%; */
|
/* width: 60%; */
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background: rgba(0, 0, 0, 0.6);
|
background: rgba(0, 0, 0, 0.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.holo-sidebar[data-open="open"], .holo-sideBar[data-open="open"] {
|
.holo-sidebar[data-open="open"], .holo-sideBar[data-open="open"] {
|
||||||
|
@ -37,10 +37,10 @@ a[data-action-section] {
|
||||||
.holo-sidebar .holo-list, .holo-sideBar .holo-list {
|
.holo-sidebar .holo-list, .holo-sideBar .holo-list {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: rgba(0, 0, 0, 0.9);
|
background: rgba(0, 0, 0, 0.80);
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionBar .holo-title.holo-menu, .holo-actionBar .holo-title.holo-menu {
|
.actionbar .holo-title.holo-menu, .holo-actionbar .holo-title.holo-menu, .actionBar .holo-title.holo-menu, .holo-actionBar .holo-title.holo-menu {
|
||||||
background-position: 0 16px;
|
background-position: 0 16px;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-image: url(./holo-menu.png);
|
background-image: url(./holo-menu.png);
|
||||||
|
@ -52,12 +52,42 @@ a[data-action-section] {
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.holo-sidebar > .holo-list, .holo-sideBar > .holo-list {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holo-actionbar .holo-list, .holo-actionBar .holo-list {
|
||||||
|
position: absolute;
|
||||||
|
top: -2px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holo-actionbar .holo-list.collapsible, .holo-actionBar .holo-list.collapsible {
|
||||||
|
top: 47px;
|
||||||
|
background: rgba(16, 16, 16, 0.95);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holo-actionbar .holo-list > div, .holo-actionBar .holo-list > div,
|
||||||
|
.holo-actionbar .holo-list:not(.collapsible) li, .holo-actionBar .holo-list:not(.collapsible) li {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holo-actionbar .holo-list li > button, .holo-actionBar ul.holo-list li > button {
|
||||||
|
font-size: revert;
|
||||||
|
}
|
||||||
|
|
||||||
|
.holo-actionbar .holo-list:not(.collapsible) *[data-collapsed="true"], .holo-actionBar .holo-list:not(.collapsible) *[data-collapsed="true"],
|
||||||
|
.holo-actionbar .holo-list.collapsible *[data-collapsed="false"], .holo-actionBar .holo-list.collapsible *[data-collapsed="false"] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* https://github.com/ZMYaro/holo-web/issues/1#issuecomment-12778881 */
|
/* https://github.com/ZMYaro/holo-web/issues/1#issuecomment-12778881 */
|
||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: white;
|
color: white;
|
||||||
border-width: 0 0 1px;
|
border-width: 0 0 1px;
|
||||||
border-color: #7F7F7F;
|
border-color: /* #4AB3E4; */ #7F7F7F;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
padding: 0px 4px;
|
padding: 0px 4px;
|
||||||
|
@ -72,7 +102,7 @@ textarea {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: white;
|
color: white;
|
||||||
border-width: 0 0 1px;
|
border-width: 0 0 1px;
|
||||||
border-color: #7F7F7F;
|
border-color: /* #4AB3E4; */ #7F7F7F;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
padding: 4px 4px;
|
padding: 4px 4px;
|
||||||
box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box;
|
box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box;
|
||||||
|
|
|
@ -1,7 +1,39 @@
|
||||||
window.addEventListener('load', (function() {
|
window.addEventListener('load', (function() {
|
||||||
|
|
||||||
|
//function getElemPrototype (elem) { return elem.outerHTML.split('>') }
|
||||||
|
|
||||||
|
/* https://stackoverflow.com/a/47932848 */
|
||||||
|
//function camelToDash(str){ return str.replace(/([A-Z])/g, function($1){return "-"+$1.toLowerCase();}) }
|
||||||
|
|
||||||
|
/* function elemToQuery (elem) {
|
||||||
|
var query = elem.tagName;
|
||||||
|
if (elem.id) {
|
||||||
|
query += `[id="${elem.id}"]`;
|
||||||
|
}
|
||||||
|
if (elem.className) {
|
||||||
|
query += `[class="${elem.className}"]`;
|
||||||
|
}
|
||||||
|
for (var key in elem.dataset) {
|
||||||
|
query += `[data-${camelToDash(key)}="${elem.dataset[key]}"]`;
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
} */
|
||||||
|
|
||||||
var initialSectionName = $('[data-section][data-open]').dataset.section;
|
var initialSectionName = $('[data-section][data-open]').dataset.section;
|
||||||
|
|
||||||
|
$(':: .holo-actionbar > .holo-list, .holo-actionBar > .holo-list').forEach(function(actionListElem){
|
||||||
|
var actionListElemNew = actionListElem.cloneNode(true);
|
||||||
|
actionListElemNew.classList.add('collapsible');
|
||||||
|
actionListElemNew.querySelector('[data-collapser]').remove();
|
||||||
|
arrayFrom(actionListElemNew.querySelectorAll('.holo-list li')).forEach(function(itemElem){
|
||||||
|
itemElem.remove();
|
||||||
|
});
|
||||||
|
actionListElem.insertAdjacentElement('afterend', actionListElemNew);
|
||||||
|
actionListElem.querySelector('button[data-collapser], [data-collapser] > button').onclick = (function(){
|
||||||
|
actionListElemNew.style.display = (actionListElemNew.style.display ? null : 'revert');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$('::[data-action-sidebar]').forEach(function(actionSidebarElem){
|
$('::[data-action-sidebar]').forEach(function(actionSidebarElem){
|
||||||
var sidebarElem = $('[data-sidebar="' + actionSidebarElem.dataset.actionSidebar + '"]');
|
var sidebarElem = $('[data-sidebar="' + actionSidebarElem.dataset.actionSidebar + '"]');
|
||||||
function toggleSidebar () {
|
function toggleSidebar () {
|
||||||
|
@ -41,6 +73,7 @@ function refreshDisplaySections (sectionTargetName) {
|
||||||
displaySectionsElem.style.display = 'none';
|
displaySectionsElem.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
reorderActionBar();
|
||||||
}
|
}
|
||||||
refreshDisplaySections();
|
refreshDisplaySections();
|
||||||
|
|
||||||
|
@ -51,4 +84,52 @@ function hashChange () {
|
||||||
window.addEventListener('hashchange', hashChange);
|
window.addEventListener('hashchange', hashChange);
|
||||||
hashChange();
|
hashChange();
|
||||||
|
|
||||||
|
function reorderActionBar () {
|
||||||
|
$(':: .holo-actionbar, .holo-actionBar').forEach(function(actionBarElem){
|
||||||
|
var childrenWidth = 0;
|
||||||
|
var collapsedChildren = 0;
|
||||||
|
childrenWidth += actionBarElem.querySelector('button.holo-title').clientWidth;
|
||||||
|
arrayFrom(actionBarElem.querySelectorAll('.holo-list.collapsible li')).forEach(function(itemElem){
|
||||||
|
var destParentElem = actionBarElem.querySelector('.holo-list:not(.collapsible)');
|
||||||
|
//if (!destParentElem.className.includes('holo-list')) {
|
||||||
|
//if (getElemPrototype(itemElem.parentElement) !== getElemPrototype(destParentElem)) {
|
||||||
|
// destParentElem = actionBarElem.querySelector(`.holo-list:not(.collapsible) > ${elemToQuery(itemElem.parentElement)}`);
|
||||||
|
//}
|
||||||
|
destParentElem.insertBefore(itemElem, destParentElem.lastElementChild);
|
||||||
|
});
|
||||||
|
arrayFrom(actionBarElem.querySelectorAll('.holo-list:not(.collapsible) li')).forEach(function(itemElem){
|
||||||
|
itemElem.dataset.collapsed = false;
|
||||||
|
childrenWidth += itemElem.clientWidth;
|
||||||
|
});
|
||||||
|
//arrayFrom(actionBarElem.querySelectorAll('.holo-list.collapsible li')).forEach(function(itemElem){
|
||||||
|
// itemElem.dataset.collapsed = false;
|
||||||
|
//});
|
||||||
|
if (childrenWidth <= actionBarElem.clientWidth) {
|
||||||
|
actionBarElem.querySelector('[data-collapser]').style.display = 'none';
|
||||||
|
}
|
||||||
|
while (childrenWidth > actionBarElem.clientWidth) {
|
||||||
|
var itemElem = arrayFrom(actionBarElem.querySelectorAll('.holo-list:not(.collapsible) li:not([data-collapsed="true"]):not([data-collapser])')).slice(-1)[0];
|
||||||
|
if (!itemElem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
collapsedChildren++;
|
||||||
|
childrenWidth -= itemElem.clientWidth;
|
||||||
|
itemElem.dataset.collapsed = true;
|
||||||
|
var destParentElem = actionBarElem.querySelector('.holo-list.collapsible');
|
||||||
|
//if (!destParentElem.className.includes('holo-list')) {
|
||||||
|
// destParentElem = actionBarElem.querySelector(`.holo-list.collapsible > ${elemToQuery(itemElem.parentElement)}`);
|
||||||
|
//}
|
||||||
|
destParentElem.insertBefore(itemElem, destParentElem.firstElementChild);
|
||||||
|
actionBarElem.querySelector('[data-collapser]').style.display = null;
|
||||||
|
}
|
||||||
|
//if (collapsedChildren > 0) {
|
||||||
|
// arrayFrom(actionBarElem.querySelectorAll('.holo-list.collapsible li:not([data-collapsed="true"]):not([data-collapser])')).slice(-collapsedChildren).forEach(function(itemElem){
|
||||||
|
// itemElem.dataset.collapsed = true;
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', reorderActionBar);
|
||||||
|
reorderActionBar();
|
||||||
|
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<title>🕷️ SpiderADB</title>
|
<meta property="og:title" content="🕷️ SpiderADB"/>
|
||||||
|
<meta OctoSpaccHubSdk="Url" content="https://hub.octt.eu.org/SpiderADB/"/>
|
||||||
|
<meta OctoSpaccHubSdk="WebManifestExtra" content="'display':'standalone',"/>
|
||||||
<link rel="stylesheet" href="./holo-web/holo-base-elements.css"/>
|
<link rel="stylesheet" href="./holo-web/holo-base-elements.css"/>
|
||||||
<link rel="stylesheet" href="./holo-web/holo-base-widgets.css"/>
|
<link rel="stylesheet" href="./holo-web/holo-base-widgets.css"/>
|
||||||
<link rel="stylesheet" href="./holo-web/holo-ics-dark-elements.css"/>
|
<link rel="stylesheet" href="./holo-web/holo-ics-dark-elements.css"/>
|
||||||
|
@ -15,10 +17,11 @@
|
||||||
<script src="../../shared/OctoHub-Global.js"></script>
|
<script src="../../shared/OctoHub-Global.js"></script>
|
||||||
<style>
|
<style>
|
||||||
.floatRight { float: right; }
|
.floatRight { float: right; }
|
||||||
body { overflow-x: hidden; padding-bottom: 0; }
|
body { overflow-x: hidden; padding-bottom: 0; overflow-wrap: break-word; }
|
||||||
ul.holo-list li input[type="checkbox"].floatRight { margin-top: 0; }
|
ul.holo-list li input[type="checkbox"].floatRight { margin-top: 0; }
|
||||||
section.holo-sideBar > ul.holo-list { overflow-y: auto; }
|
p[name="popupBox"] { box-sizing: border-box; position: fixed; top: 25%; left: 0; width: 100%; min-height: 20%; padding: 1em; background: rgba(0, 0, 0, 0.95); }
|
||||||
[name="terminalInput"], [name="terminalOutput"] {
|
p[name="popupBox"] > button { float: right; }
|
||||||
|
input[name="terminalInput"], textarea[name="terminalOutput"] {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
margin-right: 0 !important;
|
margin-right: 0 !important;
|
||||||
|
@ -31,19 +34,44 @@
|
||||||
<button class="holo-title holo-menu" data-action-sidebar="sidebar">
|
<button class="holo-title holo-menu" data-action-sidebar="sidebar">
|
||||||
🕷️ SpiderADB <small>(WIP)</small>
|
🕷️ SpiderADB <small>(WIP)</small>
|
||||||
</button>
|
</button>
|
||||||
<!-- <button class="floatRight" data-display-sections="terminal">
|
<ul class="holo-list">
|
||||||
Tabs
|
<!-- <div data-display-sections="terminal">
|
||||||
</button> -->
|
<li><button name="clearTerminal" disabled="true">
|
||||||
<button class="floatRight" data-display-sections="terminal" name="clearTerminal" disabled="true">
|
|
||||||
Clear
|
Clear
|
||||||
</button>
|
</button></li>
|
||||||
<button class="floatRight" data-display-sections="terminal" name="wrapTerminal">
|
<li><button name="wrapTerminal">
|
||||||
Wrap
|
Wrap
|
||||||
</button>
|
</button></li>
|
||||||
<button class="floatRight" data-display-sections="packages" name="apkInstall">
|
</div>
|
||||||
|
<div data-display-sections="packages">
|
||||||
|
<li><button name="packagesInvertSelect" disabled="true">
|
||||||
|
Invert selection
|
||||||
|
</button></li>
|
||||||
|
<li><button name="apkInstall" disabled="true">
|
||||||
<input type="file" hidden="true" multiple="true" accept=".apk, application/vnd.android.package-archive"/>
|
<input type="file" hidden="true" multiple="true" accept=".apk, application/vnd.android.package-archive"/>
|
||||||
Install APK(s)
|
Install APK(s)
|
||||||
</button>
|
</button></li>
|
||||||
|
</div> -->
|
||||||
|
<li data-display-sections="terminal"><button name="wrapTerminal">
|
||||||
|
Wrap
|
||||||
|
</button></li>
|
||||||
|
<li data-display-sections="terminal"><button name="clearTerminal" disabled="true">
|
||||||
|
Clear
|
||||||
|
</button></li>
|
||||||
|
<li data-display-sections="packages"><button name="packagesInvertSelect" disabled="true">
|
||||||
|
Invert selection
|
||||||
|
</button></li>
|
||||||
|
<li data-display-sections="packages"><button name="packagesUninstall" disabled="true">
|
||||||
|
Uninstall selected
|
||||||
|
</button></li>
|
||||||
|
<li data-display-sections="packages"><button name="apkInstall" disabled="true">
|
||||||
|
<input type="file" hidden="true" multiple="true" accept=".apk, application/vnd.android.package-archive"/>
|
||||||
|
Install APK(s)
|
||||||
|
</button></li>
|
||||||
|
<li data-collapser><button>
|
||||||
|
<b>···</b>
|
||||||
|
</button></li>
|
||||||
|
</ul>
|
||||||
</header>
|
</header>
|
||||||
<section class="holo-sideBar" data-sidebar="sidebar">
|
<section class="holo-sideBar" data-sidebar="sidebar">
|
||||||
<ul class="holo-list">
|
<ul class="holo-list">
|
||||||
|
@ -68,24 +96,47 @@
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
<section class="holo-body">
|
<section class="holo-body">
|
||||||
<p name="connectReminder" data-display-sections="terminal packages">
|
<p name="javascriptWarning" style="font-size: xx-large; font-weight: bold;">You need to enable JavaScript to run this app!</p>
|
||||||
|
<p name="connectReminder" data-display-sections="terminal packages" hidden="true">
|
||||||
You must <a data-action-section="devices">connect and authorize a device</a> first.
|
You must <a data-action-section="devices">connect and authorize a device</a> first.
|
||||||
</p>
|
</p>
|
||||||
<section class="holo-section" data-section="about">
|
<p name="popupBox" hidden="true"></p>
|
||||||
<p>SpiderADB is an user-friendly webapp for connecting to devices via the Android Debug Bridge, straight from a browser.</p>
|
<section class="holo-section" data-section="about" style="display: block !important;">
|
||||||
<!-- TODO features -->
|
<p>
|
||||||
|
<b>SpiderADB</b> is an user-friendly webapp for connecting to devices via the Android Debug Bridge, straight from a browser.
|
||||||
|
The aim of this is to be kind of a swiss army knife that can be used from any platform, with minimal hassle,
|
||||||
|
to do a number of advanced administration and debugging task on Android devices. These are the current features:
|
||||||
|
</p><ul>
|
||||||
|
<li><b>Devices</b>: Allows connecting new devices, shows a list of the paired ones, and lists basic useful information about various parts of the system.</li>
|
||||||
|
<li><b>Terminal</b>: Provides a basic terminal shell for inputting commands and reading their output. (Currently doesn't support any teletype features, so only basic commands can be run properly.)</li>
|
||||||
|
<li><b>Packages</b>: Displays a list of the currently installed packages, allowing for multiple to be uninstalled, and also allows uploading APK files for installation.</li>
|
||||||
|
</ul>
|
||||||
<p>Here are some additional tips and tricks you might find useful to make the most out of this app:</p><ul>
|
<p>Here are some additional tips and tricks you might find useful to make the most out of this app:</p><ul>
|
||||||
<li><a target="_blank" href="https://dev.to/larsonzhong/most-complete-adb-commands-4pcg">Most complete ADB command manual</a></li>
|
<li><a target="_blank" href="https://dev.to/larsonzhong/most-complete-adb-commands-4pcg">Most complete ADB command manual</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3>Open Source and Credits</h3>
|
<h3>Open-Source and Licensing</h3><p>
|
||||||
<p>
|
This app is open-source and made with mostly-vanilla web technologies.
|
||||||
This app is open-source and made with mostly-vanilla web technologies. You can find the full source code on my Git repo: <a href="https://gitlab.com/octospacc/octospacc.gitlab.io/-/tree/master/source/SpiderADB/">https://gitlab.com/octospacc/octospacc.gitlab.io/-/tree/master/source/SpiderADB/</a>.
|
You can find the full source code on my Git repo:
|
||||||
<p>Also, this app wouldn't have been possible without these third-party components, of which the license is specified in brackets:</p><ul>
|
<a href="https://gitlab.com/octospacc/octospacc.gitlab.io/-/tree/master/source/SpiderADB/">https://gitlab.com/octospacc/octospacc.gitlab.io/-/tree/master/source/SpiderADB/</a>.
|
||||||
|
</p><p>Copyright (C) 2024, OctoSpacc
|
||||||
|
<br/>This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
<br/>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||||
|
<br/>You should have received a copy of the GNU Affero General Public License along with this program. If not, see <a target="_blank" href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a>.
|
||||||
|
</p>
|
||||||
|
<h3>Third-Parties and Credits</h3><p>
|
||||||
|
This app wouldn't have been possible without these third-party components, of which the license is specified in brackets:
|
||||||
|
</p><ul>
|
||||||
<li><a target="_blank" href="https://github.com/yume-chan/ya-webadb">Tango</a> [MIT]: ADB port for the web</li>
|
<li><a target="_blank" href="https://github.com/yume-chan/ya-webadb">Tango</a> [MIT]: ADB port for the web</li>
|
||||||
<li><a target="_blank" href="https://github.com/tango-adb/old-demo">Tango Demo (Old)</a> [MIT]: the previous official Tango demo webapp, helpful for writing my app since the Tango documentation is pretty lacking</li>
|
<li><a target="_blank" href="https://github.com/tango-adb/old-demo">Tango Demo (Old)</a> [MIT]: the previous official Tango demo webapp, helpful for writing my app since the Tango documentation is pretty lacking</li>
|
||||||
<li><a target="_blank" href="https://github.com/zmyaro/holo-web">Holo Web</a> [MIT]: stylesheets for recreating the Android Holo theme on the web</li>
|
<li><a target="_blank" href="https://github.com/zmyaro/holo-web">Holo Web</a> [MIT]: stylesheets for recreating the Android Holo theme on the web</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3>Changelog</h3>
|
<h3>Changelog</h3>
|
||||||
|
<h4>2024-04-20</h4><ul>
|
||||||
|
<li>Add feature descriptions to About section, and licensing info.</li>
|
||||||
|
<li>Update UI engine, adding auto-collapsible option menu.</li>
|
||||||
|
<li>Add selection boxes in Packages menu, and allow uninstalling (user) packages.</li>
|
||||||
|
<li>Fix design for no-JS user agents: add warning and make the About page the visible content.</li>
|
||||||
|
</ul>
|
||||||
<h4>2024-04-18</h4><ul>
|
<h4>2024-04-18</h4><ul>
|
||||||
<li>Improve Terminal logic, which now also shows stdout, scaling, and add a Clear feature.</li>
|
<li>Improve Terminal logic, which now also shows stdout, scaling, and add a Clear feature.</li>
|
||||||
<li>Introduce Packages menu, listing all installed packages' names, and allow installing (multiple) APKs files.</li>
|
<li>Introduce Packages menu, listing all installed packages' names, and allow installing (multiple) APKs files.</li>
|
||||||
|
@ -121,10 +172,6 @@
|
||||||
<li name="deviceCpuAbis"></li>
|
<li name="deviceCpuAbis"></li>
|
||||||
</div>
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
<!-- <details>
|
|
||||||
<summary><code>getprop</code> Info dump</summary>
|
|
||||||
<ul name="devicePropDump"></ul>
|
|
||||||
</details> -->
|
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<section class="holo-section" data-section="terminal">
|
<section class="holo-section" data-section="terminal">
|
||||||
|
@ -132,11 +179,8 @@
|
||||||
<input name="terminalInput" type="text" disabled="true" placeholder="> Input any command..."/>
|
<input name="terminalInput" type="text" disabled="true" placeholder="> Input any command..."/>
|
||||||
</section>
|
</section>
|
||||||
<section class="holo-section" data-section="packages">
|
<section class="holo-section" data-section="packages">
|
||||||
<!-- TODO buttons -->
|
<ul class="holo-list" name="packageList"></ul>
|
||||||
<ul class="holo-list" name="packageList"></ul><!--<li>Test <input type="checkbox" class="floatRight"/></li><!--<label><li>Test <input type="checkbox"/></li></label>-->
|
<!-- <section class="holo-subsection" name="packageInfo"></section> -->
|
||||||
<section class="holo-subsection" name="packageInfo">
|
|
||||||
|
|
||||||
</section>
|
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<script src="./bundle.js"></script>
|
<script src="./bundle.js"></script>
|
||||||
|
|
|
@ -35,7 +35,9 @@ const appPager = (content, title) => `${title ? `<h2>${title}</h2>` : ''}${conte
|
||||||
const newHtmlPage = (content, title) => `<!DOCTYPE html><html><head>
|
const newHtmlPage = (content, title) => `<!DOCTYPE html><html><head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<title>${title ? `${title} — ` : ''}${appName}</title>
|
<meta property="og:title" content="${title ? `${title} — ` : ''}${appName}"/>
|
||||||
|
<meta OctoSpaccHubSdk="Url" content="https://hub.octt.eu.org/WuppiMini/"/>
|
||||||
|
<meta OctoSpaccHubSdk="WebManifestExtra" content="'display':'standalone',"/>
|
||||||
<script src="../../shared/OctoHub-Global.js"></script>
|
<script src="../../shared/OctoHub-Global.js"></script>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<title>🃏️ [Matrix] Sticker Helper</title>
|
<meta property="og:title" content="🃏️ [Matrix] Sticker Helper"/>
|
||||||
|
<meta OctoSpaccHubSdk="Url" content="https://hub.octt.eu.org/MatrixStickerHelper/"/>
|
||||||
|
<meta OctoSpaccHubSdk="WebManifestExtra" content="'display':'standalone',"/>
|
||||||
|
|
||||||
<script src="../../shared/OctoHub-Global.js"></script>
|
<script src="../../shared/OctoHub-Global.js"></script>
|
||||||
<script src="../../../SpaccDotWeb/SpaccDotWeb.Alt.js" module="SpaccDotWeb"></script><!-- offline -->
|
<script src="../../../SpaccDotWeb/SpaccDotWeb.Alt.js" module="SpaccDotWeb"></script><!-- offline -->
|
||||||
|
|
Loading…
Reference in New Issue