You've already forked Tricky-Addon-Update-Target-List
mirror of
https://github.com/KOWX712/Tricky-Addon-Update-Target-List.git
synced 2025-09-06 06:37:09 +00:00
feat: add icon display for webuix
This commit is contained in:
@@ -7,6 +7,17 @@ export const appListContainer = document.getElementById('apps-list');
|
|||||||
export const updateCard = document.getElementById('update-card');
|
export const updateCard = document.getElementById('update-card');
|
||||||
|
|
||||||
let targetList = [];
|
let targetList = [];
|
||||||
|
let wrapInputStream;
|
||||||
|
|
||||||
|
if (typeof $packageManager !== 'undefined') {
|
||||||
|
import("https://mui.kernelsu.org/internal/assets/ext/wrapInputStream.mjs")
|
||||||
|
.then(module => {
|
||||||
|
wrapInputStream = module.wrapInputStream;
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error("Failed to load wrapInputStream:", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch and render applist
|
// Fetch and render applist
|
||||||
export async function fetchAppList() {
|
export async function fetchAppList() {
|
||||||
@@ -44,6 +55,13 @@ export async function fetchAppList() {
|
|||||||
packageName
|
packageName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (typeof $packageManager !== 'undefined') {
|
||||||
|
const info = $packageManager.getApplicationInfo(packageName, 0, 0);
|
||||||
|
return {
|
||||||
|
appName: info.getLabel(),
|
||||||
|
packageName
|
||||||
|
};
|
||||||
|
}
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const output = spawn('sh', [`${basePath}/common/get_extra.sh`, '--appname', packageName],
|
const output = spawn('sh', [`${basePath}/common/get_extra.sh`, '--appname', packageName],
|
||||||
{ env: { PATH: `$PATH:${basePath}/common:/data/data/com.termux/files/usr/bin` } });
|
{ env: { PATH: `$PATH:${basePath}/common:/data/data/com.termux/files/usr/bin` } });
|
||||||
@@ -75,24 +93,43 @@ function renderAppList(data) {
|
|||||||
return (a.appName || "").localeCompare(b.appName || "");
|
return (a.appName || "").localeCompare(b.appName || "");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Render
|
// Clear container
|
||||||
appListContainer.innerHTML = "";
|
appListContainer.innerHTML = "";
|
||||||
sortedApps.forEach(({ appName, packageName }) => {
|
loadingIndicator.style.display = "none";
|
||||||
|
hideFloatingBtn(false);
|
||||||
|
if (updateCard) appListContainer.appendChild(updateCard);
|
||||||
|
|
||||||
|
// Append app
|
||||||
|
const appendApps = (index) => {
|
||||||
|
if (index >= sortedApps.length) {
|
||||||
|
document.querySelector('.uninstall-container').classList.remove('hidden-uninstall');
|
||||||
|
toggleableCheckbox();
|
||||||
|
setupRadioButtonListeners();
|
||||||
|
setupModeMenu();
|
||||||
|
updateCheckboxColor();
|
||||||
|
applyRippleEffect();
|
||||||
|
if (typeof $packageManager !== 'undefined') {
|
||||||
|
setupIconIntersectionObserver();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { appName, packageName } = sortedApps[index];
|
||||||
const appElement = document.importNode(appTemplate, true);
|
const appElement = document.importNode(appTemplate, true);
|
||||||
const contentElement = appElement.querySelector(".content");
|
const contentElement = appElement.querySelector(".content");
|
||||||
contentElement.setAttribute("data-package", packageName);
|
contentElement.setAttribute("data-package", packageName);
|
||||||
|
|
||||||
// Set unique names for radio button groups
|
// Set unique names for radio button groups
|
||||||
const radioButtons = appElement.querySelectorAll('input[type="radio"]');
|
const radioButtons = appElement.querySelectorAll('input[type="radio"]');
|
||||||
radioButtons.forEach((radio) => {
|
radioButtons.forEach((radio) => {
|
||||||
radio.name = `mode-radio-${packageName}`;
|
radio.name = `mode-radio-${packageName}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Preselect the radio button based on the package name
|
// Preselect the radio button based on the package name
|
||||||
const generateRadio = appElement.querySelector('#generate-mode');
|
const generateRadio = appElement.querySelector('#generate-mode');
|
||||||
const hackRadio = appElement.querySelector('#hack-mode');
|
const hackRadio = appElement.querySelector('#hack-mode');
|
||||||
const normalRadio = appElement.querySelector('#normal-mode');
|
const normalRadio = appElement.querySelector('#normal-mode');
|
||||||
|
|
||||||
if (appsWithExclamation.includes(packageName)) {
|
if (appsWithExclamation.includes(packageName)) {
|
||||||
generateRadio.checked = true;
|
generateRadio.checked = true;
|
||||||
} else if (appsWithQuestion.includes(packageName)) {
|
} else if (appsWithQuestion.includes(packageName)) {
|
||||||
@@ -100,9 +137,13 @@ function renderAppList(data) {
|
|||||||
} else {
|
} else {
|
||||||
normalRadio.checked = true;
|
normalRadio.checked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameElement = appElement.querySelector(".name");
|
const nameElement = appElement.querySelector(".name");
|
||||||
nameElement.innerHTML = `
|
nameElement.innerHTML = `
|
||||||
|
<div class="app-icon-container" style="display:${typeof $packageManager !== 'undefined' ? 'flex' : 'none'};">
|
||||||
|
<div class="loader" data-package="${packageName}"></div>
|
||||||
|
<img src="" class="app-icon" data-package="${packageName}" />
|
||||||
|
</div>
|
||||||
<div class="app-info">
|
<div class="app-info">
|
||||||
<div class="app-name"><strong>${appName}</strong></div>
|
<div class="app-name"><strong>${appName}</strong></div>
|
||||||
<div class="package-name">${packageName}</div>
|
<div class="package-name">${packageName}</div>
|
||||||
@@ -111,19 +152,76 @@ function renderAppList(data) {
|
|||||||
const checkbox = appElement.querySelector(".checkbox");
|
const checkbox = appElement.querySelector(".checkbox");
|
||||||
checkbox.checked = targetList.includes(packageName);
|
checkbox.checked = targetList.includes(packageName);
|
||||||
appListContainer.appendChild(appElement);
|
appListContainer.appendChild(appElement);
|
||||||
|
appendApps(index + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
appendApps(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up an IntersectionObserver to load app icons when they enter the viewport
|
||||||
|
*/
|
||||||
|
function setupIconIntersectionObserver() {
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const container = entry.target;
|
||||||
|
const packageName = container.querySelector('.app-icon').getAttribute('data-package');
|
||||||
|
if (packageName) {
|
||||||
|
loadIcons(packageName);
|
||||||
|
observer.unobserve(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
rootMargin: '100px',
|
||||||
|
threshold: 0.1
|
||||||
});
|
});
|
||||||
|
|
||||||
loadingIndicator.style.display = "none";
|
const iconContainers = document.querySelectorAll('.app-icon-container');
|
||||||
hideFloatingBtn(false);
|
iconContainers.forEach(container => {
|
||||||
document.querySelector('.uninstall-container').classList.remove('hidden-uninstall');
|
observer.observe(container);
|
||||||
toggleableCheckbox();
|
});
|
||||||
if (appListContainer.firstChild !== updateCard) {
|
}
|
||||||
appListContainer.insertBefore(updateCard, appListContainer.firstChild);
|
|
||||||
|
const iconCache = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all app icons asynchronously after UI is rendered
|
||||||
|
* @param {Array<string>} packageName - package names to load icons for
|
||||||
|
*/
|
||||||
|
function loadIcons(packageName) {
|
||||||
|
const imgElement = document.querySelector(`.app-icon[data-package="${packageName}"]`);
|
||||||
|
const loader = document.querySelector(`.loader[data-package="${packageName}"]`);
|
||||||
|
|
||||||
|
if (iconCache.has(packageName)) {
|
||||||
|
imgElement.src = iconCache.get(packageName);
|
||||||
|
loader.style.display = 'none';
|
||||||
|
imgElement.style.opacity = '1';
|
||||||
|
} else {
|
||||||
|
const stream = $packageManager.getApplicationIcon(packageName, 0, 0);
|
||||||
|
wrapInputStream(stream)
|
||||||
|
.then(r => r.arrayBuffer())
|
||||||
|
.then(buffer => {
|
||||||
|
const base64 = 'data:image/png;base64,' + arrayBufferToBase64(buffer);
|
||||||
|
iconCache.set(packageName, base64);
|
||||||
|
imgElement.src = base64;
|
||||||
|
loader.style.display = 'none';
|
||||||
|
imgElement.style.opacity = '1';
|
||||||
|
})
|
||||||
}
|
}
|
||||||
setupRadioButtonListeners();
|
}
|
||||||
setupModeMenu();
|
|
||||||
updateCheckboxColor();
|
/**
|
||||||
applyRippleEffect();
|
* convert array buffer to base 64
|
||||||
|
* @param {string} buffer
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function arrayBufferToBase64(buffer) {
|
||||||
|
const uint8Array = new Uint8Array(buffer);
|
||||||
|
let binary = '';
|
||||||
|
uint8Array.forEach(byte => binary += String.fromCharCode(byte));
|
||||||
|
return btoa(binary);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to save app with ! and ? then process target list
|
// Function to save app with ! and ? then process target list
|
||||||
@@ -262,4 +360,4 @@ function updateCheckboxColor() {
|
|||||||
checkbox.classList.remove("checkbox-checked-generate", "checkbox-checked-hack");
|
checkbox.classList.remove("checkbox-checked-generate", "checkbox-checked-hack");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,13 +218,54 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
display: inline-block;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 15.5px;
|
font-size: 15.5px;
|
||||||
max-width: calc(100% - 30px);
|
max-width: calc(100% - 30px);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-info {
|
||||||
|
display: inline-block;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
user-select: none;
|
}
|
||||||
|
|
||||||
|
.app-icon-container {
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 3em;
|
||||||
|
width: 3em;
|
||||||
|
margin-right: 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, var(--surfaceContainer), var(--surfaceContainerHigh), var(--surfaceContainer));
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: shimmer 1.2s infinite linear;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-icon {
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: -200% 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox-wrapper {
|
.checkbox-wrapper {
|
||||||
|
|||||||
Reference in New Issue
Block a user