Update SpiderADB; Update apps metadata

This commit is contained in:
octospacc 2024-04-21 00:58:00 +02:00
parent d11ee0f162
commit 04c4012171
8 changed files with 318 additions and 89 deletions

View File

@ -1,6 +1,15 @@
#!/bin/sh
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
cp -vr ./static ./public
@ -8,10 +17,10 @@ cp -vr ./shared ./public/shared
for App in ${SourceApps}
do
mkdir -p ./public/${App}
cd ./source/${App}
mkdir -p "./public/${App}"
cd "./source/${App}"
sh ./Requirements.sh
cp -r $(sh ./Build.sh) ../../public/${App}/
cp -vr $(sh ./Build.sh) "../../public/${App}/"
cd ../..
done
@ -20,5 +29,18 @@ node ../WriteRedirectPages.js
for App in ${HubSdkApps}
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

View File

@ -1,4 +1,4 @@
#!/bin/sh
npm update
npm install
yes | npx esbuild
yes | npx esbuild --help > /dev/null # Install esbuild

View File

@ -1,47 +1,35 @@
import * as Adb from '@yume-chan/adb';
import * as AdbDaemonWebUsb from '@yume-chan/adb-daemon-webusb';
import { Adb, AdbDaemonTransport } from '@yume-chan/adb';
import { AdbDaemonWebUsbDeviceManager, AdbDaemonWebUsbDeviceWatcher } from '@yume-chan/adb-daemon-webusb';
import AdbWebCredentialStore from '@yume-chan/adb-credential-web';
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(){
$('p$javascriptWarning$').remove();
$('.holo-section[data-section="about"]').style = null;
$('p$connectReminder$').hidden = false;
const deviceSelect = $('select$deviceSelect$');
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 = {};
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 () {
const divider = (Device.adb ? 2 : 3);
terminalOutput.style.height = `${window.innerHeight - ((48 + 8) * divider)}px`;
@ -49,7 +37,7 @@ function resizeTerminal () {
window.addEventListener('resize', resizeTerminal);
resizeTerminal();
const UsbManager = AdbDaemonWebUsb.AdbDaemonWebUsbDeviceManager.BROWSER;
const UsbManager = AdbDaemonWebUsbDeviceManager.BROWSER;
if (!UsbManager) {
$('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.
@ -70,7 +58,7 @@ if (!UsbManager) {
return; // kill the app
}
new AdbDaemonWebUsb.AdbDaemonWebUsbDeviceWatcher((async function(connectedDevice){
new AdbDaemonWebUsbDeviceWatcher((async function(connectedDevice){
if (!connectedDevice) {
await disconnectDevice();
}
@ -82,8 +70,8 @@ async function connectAuthorizeDevice () {
Device.device = await getDevice();
try {
Device.connection = await Device.device.connect();
Device.transport = await Adb.AdbDaemonTransport.authenticate({ connection: Device.connection, credentialStore: CredentialStore });
Device.adb = new Adb.Adb(Device.transport);
Device.transport = await AdbDaemonTransport.authenticate({ connection: Device.connection, credentialStore: CredentialStore });
Device.adb = new Adb(Device.transport);
} 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.';
}
@ -144,7 +132,6 @@ async function refreshDeviceInfo () {
//$('$deviceInfo$').hidden = false;
if (Device.adb) {
$('$deviceStatus$').innerHTML = null;
// $('$devicePropDump$').innerHTML = null;
$('$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')}`;
$('$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.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 {
onDevice = false;
}
@ -175,8 +157,13 @@ async function refreshDeviceInfo () {
function toggleDeviceElems (enabled) {
$('$deviceInfo$').hidden = !enabled;
$('button$apkInstall$').disabled = !enabled;
$('button$packagesUninstall$').disabled = true;
$('button$packagesInvertSelect$').disabled = !enabled;
$('input$terminalInput$').disabled = !enabled;
resizeTerminal();
if (!enabled) {
$('ul$packageList$').innerHTML = null;
}
}
async function refreshDeviceSection () {
@ -228,25 +215,86 @@ $('button$wrapTerminal$').onclick = (function(){
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 () {
$('ul$packageList$').innerHTML = null;
$('button$packagesUninstall$').disabled = true;
const pm = new PackageManager(Device.adb);
const list = await pm.listPackages();
const checkedList = {};
let index = 0;
let result = await list.next();
while (!result.done) {
var packageElem = document.createElement('li');
packageElem.innerHTML = `${result.value.packageName}<!-- <input type="checkbox" class="floatRight"/>-->`;
/* 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
}); */
packageElem.index = index;
packageElem.innerHTML = `${result.value.packageName} <input type="checkbox" class="floatRight"/>`;
packageElem.querySelector('input').onchange = (function(){
checkedList[this.parentElement.index] = this.checked;
$('button$packagesUninstall$').disabled = !Object.values(checkedList).includes(true);
});
$('ul$packageList$').appendChild(packageElem);
index++;
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 */) {
if (Device.adb && sectionHash === 'packages') {
refreshPackagesList();
}
}));

View File

@ -27,7 +27,7 @@ a[data-action-section] {
/* width: 60%; */
height: 100vh;
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"] {
@ -37,10 +37,10 @@ a[data-action-section] {
.holo-sidebar .holo-list, .holo-sideBar .holo-list {
width: 60%;
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-repeat: no-repeat;
background-image: url(./holo-menu.png);
@ -52,12 +52,42 @@ a[data-action-section] {
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 */
input[type="text"] {
background: transparent;
color: white;
border-width: 0 0 1px;
border-color: #7F7F7F;
border-color: /* #4AB3E4; */ #7F7F7F;
border-style: solid;
line-height: 36px;
padding: 0px 4px;
@ -72,7 +102,7 @@ textarea {
background: transparent;
color: white;
border-width: 0 0 1px;
border-color: #7F7F7F;
border-color: /* #4AB3E4; */ #7F7F7F;
border-style: solid;
padding: 4px 4px;
box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box;

View File

@ -1,7 +1,39 @@
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;
$(':: .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){
var sidebarElem = $('[data-sidebar="' + actionSidebarElem.dataset.actionSidebar + '"]');
function toggleSidebar () {
@ -41,6 +73,7 @@ function refreshDisplaySections (sectionTargetName) {
displaySectionsElem.style.display = 'none';
}
});
reorderActionBar();
}
refreshDisplaySections();
@ -51,4 +84,52 @@ function hashChange () {
window.addEventListener('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();
}));

View File

@ -3,7 +3,9 @@
<head>
<meta charset="UTF-8"/>
<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-widgets.css"/>
<link rel="stylesheet" href="./holo-web/holo-ics-dark-elements.css"/>
@ -15,10 +17,11 @@
<script src="../../shared/OctoHub-Global.js"></script>
<style>
.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; }
section.holo-sideBar > ul.holo-list { overflow-y: auto; }
[name="terminalInput"], [name="terminalOutput"] {
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); }
p[name="popupBox"] > button { float: right; }
input[name="terminalInput"], textarea[name="terminalOutput"] {
width: 100% !important;
margin-left: 0 !important;
margin-right: 0 !important;
@ -31,19 +34,44 @@
<button class="holo-title holo-menu" data-action-sidebar="sidebar">
🕷️ SpiderADB <small>(WIP)</small>
</button>
<!-- <button class="floatRight" data-display-sections="terminal">
Tabs
</button> -->
<button class="floatRight" data-display-sections="terminal" name="clearTerminal" disabled="true">
Clear
</button>
<button class="floatRight" data-display-sections="terminal" name="wrapTerminal">
Wrap
</button>
<button class="floatRight" data-display-sections="packages" name="apkInstall">
<input type="file" hidden="true" multiple="true" accept=".apk, application/vnd.android.package-archive"/>
Install APK(s)
</button>
<ul class="holo-list">
<!-- <div data-display-sections="terminal">
<li><button name="clearTerminal" disabled="true">
Clear
</button></li>
<li><button name="wrapTerminal">
Wrap
</button></li>
</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"/>
Install APK(s)
</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>
<section class="holo-sideBar" data-sidebar="sidebar">
<ul class="holo-list">
@ -68,24 +96,47 @@
</ul>
</section>
<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.
</p>
<section class="holo-section" data-section="about">
<p>SpiderADB is an user-friendly webapp for connecting to devices via the Android Debug Bridge, straight from a browser.</p>
<!-- TODO features -->
<p name="popupBox" hidden="true"></p>
<section class="holo-section" data-section="about" style="display: block !important;">
<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>
<li><a target="_blank" href="https://dev.to/larsonzhong/most-complete-adb-commands-4pcg">Most complete ADB command manual</a></li>
</ul>
<h3>Open Source and Credits</h3>
<p>
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>.
<p>Also, this app wouldn't have been possible without these third-party components, of which the license is specified in brackets:</p><ul>
<h3>Open-Source and Licensing</h3><p>
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>.
</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/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>
</ul>
<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>
<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>
@ -121,10 +172,6 @@
<li name="deviceCpuAbis"></li>
</div>
</ul>
<!-- <details>
<summary><code>getprop</code> Info dump</summary>
<ul name="devicePropDump"></ul>
</details> -->
</section>
</section>
<section class="holo-section" data-section="terminal">
@ -132,11 +179,8 @@
<input name="terminalInput" type="text" disabled="true" placeholder="&gt; Input any command..."/>
</section>
<section class="holo-section" data-section="packages">
<!-- TODO buttons -->
<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>
<ul class="holo-list" name="packageList"></ul>
<!-- <section class="holo-subsection" name="packageInfo"></section> -->
</section>
</section>
<script src="./bundle.js"></script>

View File

@ -35,7 +35,9 @@ const appPager = (content, title) => `${title ? `<h2>${title}</h2>` : ''}${conte
const newHtmlPage = (content, title) => `<!DOCTYPE html><html><head>
<meta charset="utf-8"/>
<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>
<style>
* {

View File

@ -1,7 +1,9 @@
<!DOCTYPE html>
<meta charset="utf-8"/>
<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="../../../SpaccDotWeb/SpaccDotWeb.Alt.js" module="SpaccDotWeb"></script><!-- offline -->