Skip to content

Commit 74e1e1f

Browse files
committed
webui: refactor seach
1 parent 7060eb4 commit 74e1e1f

4 files changed

Lines changed: 200 additions & 135 deletions

File tree

webui/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
<div id="kpm-page" class="page">
134134
<div class="page-content">
135135
<div class="module-list content-padding fab-padding" id="kpm-list"></div>
136-
<div class="empty-list" id="no-module">No module loaded</div>
136+
<div class="empty-list" id="kpm-empty-msg">No module loaded</div>
137137
</div>
138138
<md-fab variant="primary" aria-label="load" id="load">
139139
<md-icon slot="icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path d="M440-120v-320H120v-80h320v-320h80v320h320v80H520v320h-80Z"/></svg></md-icon>
@@ -143,7 +143,7 @@
143143
<div id="exclude-page" class="page">
144144
<div class="page-content">
145145
<div class="app-list" id="app-list"></div>
146-
<div class="empty-list" id="no-app">No app found</div>
146+
<div class="empty-list" id="exclude-empty-msg">No app found</div>
147147
</div>
148148
</div>
149149
<!-- Settings -->

webui/page/exclude.js

Lines changed: 102 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ const iconObserver = new IntersectionObserver((entries) => {
2525
iconObserver.unobserve(entry.target);
2626
}
2727
});
28-
}, { rootMargin: '200px' });
28+
}, { rootMargin: '100px' });
2929

3030
async function refreshAppList() {
3131
const appList = document.getElementById('app-list');
32-
appList.innerHTML = '<div class="empty-list">Loading...</div>';
32+
const emptyMsg = document.getElementById('exclude-empty-msg');
33+
appList.innerHTML = '';
34+
emptyMsg.textContent = 'Loading...';
35+
emptyMsg.classList.remove('hidden');
3336

3437
try {
3538
if (import.meta.env.DEV) { // vite debug
@@ -47,10 +50,13 @@ async function refreshAppList() {
4750
}
4851
renderAppList();
4952
} catch (e) {
50-
appList.innerHTML = `<div class="empty-list">Error loading apps: ${e.message}</div>`;
53+
emptyMsg.textContent = `Error loading apps: ${e.message}`;
5154
}
5255
}
5356

57+
let excludedApps = [];
58+
const appItemMap = new Map();
59+
5460
async function saveExcludedList(excludedApps) {
5561
const header = 'pkg,exclude,allow,uid';
5662
const lines = excludedApps.map(app => `${app.packageName},1,0,${app.uid % 100000}`);
@@ -64,10 +70,9 @@ async function saveExcludedList(excludedApps) {
6470

6571
async function renderAppList() {
6672
const appList = document.getElementById('app-list');
73+
const emptyMsg = document.getElementById('exclude-empty-msg');
6774

6875
try {
69-
appList.innerHTML = '';
70-
let excludedApps = [];
7176
let rawContent = '';
7277
if (import.meta.env.DEV) {
7378
rawContent = localStorage.getItem('kp-next_excluded_mock') || '';
@@ -78,7 +83,7 @@ async function renderAppList() {
7883
rawContent = result.stdout.trim();
7984
}
8085
} catch (e) {
81-
console.warn('pacakge_config not available.')
86+
console.warn('package_config not available.')
8287
}
8388
}
8489

@@ -123,75 +128,103 @@ async function renderAppList() {
123128

124129
const excludedPkgNames = new Set(excludedApps.map(app => app.packageName));
125130

126-
let filteredApps = allApps.filter(app => {
127-
const label = app.appLabel || '';
128-
const pkgName = app.packageName || '';
129-
const matchesSearch = label.toLowerCase().includes(searchQuery.toLowerCase()) ||
130-
pkgName.toLowerCase().includes(searchQuery.toLowerCase());
131-
const isSystem = app.isSystem;
132-
return matchesSearch && (showSystemApp || !isSystem);
133-
});
134-
135-
filteredApps.sort((a, b) => {
131+
const sortedApps = [...allApps].sort((a, b) => {
136132
const aExcluded = excludedPkgNames.has(a.packageName);
137133
const bExcluded = excludedPkgNames.has(b.packageName);
138134
if (aExcluded !== bExcluded) return aExcluded ? -1 : 1;
139135
return (a.appLabel || '').localeCompare(b.appLabel || '');
140136
});
141137

142-
document.getElementById('no-app').classList.toggle('hidden', filteredApps.length > 0);
143-
144-
filteredApps.forEach(app => {
145-
const item = document.createElement('label');
146-
item.className = 'app-item';
147-
const isExcluded = excludedPkgNames.has(app.packageName);
148-
const userIdx = Math.floor(app.uid / 100000);
149-
const extraTags = [];
150-
if (userIdx > 0) extraTags.push(`USER ${userIdx}`);
151-
if (app.isSystem) extraTags.push('SYSTEM');
152-
const extraTagsHtml = extraTags.length > 0 ? `
153-
<div class="tag-wrapper">
154-
${extraTags.map(tag => `<div class="tag ${tag.toLowerCase()}">${tag}</div>`).join('')}
155-
</div>
156-
` : '';
157-
158-
item.innerHTML = `
159-
<md-ripple></md-ripple>
160-
<div class="icon-container">
161-
<div class="loader"></div>
162-
<img class="app-icon" data-package="${app.packageName || ''}" style="opacity: 0;">
163-
</div>
164-
<div class="app-info">
165-
<div class="app-label">${app.appLabel || 'Unknown'}</div>
166-
<div class="app-package">${app.packageName || 'Unknown'}</div>
167-
${extraTagsHtml}
168-
</div>
169-
<md-switch class="app-switch" ${isExcluded ? 'selected' : ''}></md-switch>
170-
`;
138+
emptyMsg.classList.add('hidden');
171139

172-
const toggle = item.querySelector('md-switch');
173-
let saveTimeout = null;
174-
toggle.addEventListener('change', () => {
175-
const realUid = app.uid % 100000;
176-
if (toggle.selected) {
177-
if (!excludedApps.some(e => e.packageName === app.packageName)) {
178-
excludedApps.push({ packageName: app.packageName, uid: realUid });
140+
sortedApps.forEach(app => {
141+
let item = appItemMap.get(app.packageName);
142+
if (!item) {
143+
item = document.createElement('label');
144+
item.className = 'app-item';
145+
const userIdx = Math.floor(app.uid / 100000);
146+
const extraTags = [];
147+
if (userIdx > 0) extraTags.push(`USER ${userIdx}`);
148+
if (app.isSystem) extraTags.push('SYSTEM');
149+
const extraTagsHtml = extraTags.length > 0 ? `
150+
<div class="tag-wrapper">
151+
${extraTags.map(tag => `<div class="tag ${tag.toLowerCase()}">${tag}</div>`).join('')}
152+
</div>
153+
` : '';
154+
155+
item.innerHTML = `
156+
<md-ripple></md-ripple>
157+
<div class="icon-container">
158+
<div class="loader"></div>
159+
<img class="app-icon" data-package="${app.packageName || ''}" style="opacity: 0;">
160+
</div>
161+
<div class="app-info">
162+
<div class="app-label">${app.appLabel || 'Unknown'}</div>
163+
<div class="app-package">${app.packageName || 'Unknown'}</div>
164+
${extraTagsHtml}
165+
</div>
166+
<md-switch class="app-switch"></md-switch>
167+
`;
168+
169+
const toggle = item.querySelector('md-switch');
170+
let saveTimeout = null;
171+
toggle.addEventListener('change', () => {
172+
const realUid = app.uid % 100000;
173+
if (toggle.selected) {
174+
if (!excludedApps.some(e => e.packageName === app.packageName)) {
175+
excludedApps.push({ packageName: app.packageName, uid: realUid });
176+
}
177+
} else {
178+
excludedApps = excludedApps.filter(e => e.packageName !== app.packageName);
179179
}
180-
} else {
181-
excludedApps = excludedApps.filter(e => e.packageName !== app.packageName);
182-
}
183-
if (saveTimeout) clearTimeout(saveTimeout);
184-
saveTimeout = setTimeout(() => {
185-
saveExcludedList(excludedApps);
186-
}, 500);
187-
exec(`kpatch ${superkey} exclude_set ${realUid} ${toggle.selected ? 1 : 0}`, { env: { PATH: `${modDir}/bin` } });
188-
});
180+
if (saveTimeout) clearTimeout(saveTimeout);
181+
saveTimeout = setTimeout(() => {
182+
saveExcludedList(excludedApps);
183+
}, 500);
184+
exec(`kpatch ${superkey} exclude_set ${realUid} ${toggle.selected ? 1 : 0}`, { env: { PATH: `${modDir}/bin` } });
185+
});
186+
187+
appItemMap.set(app.packageName, item);
188+
iconObserver.observe(item);
189+
}
190+
191+
// Update state
192+
const toggle = item.querySelector('md-switch');
193+
toggle.selected = excludedPkgNames.has(app.packageName);
189194

190195
appList.appendChild(item);
191-
iconObserver.observe(item);
192196
});
197+
198+
applyFilters();
193199
} catch (e) {
194-
appList.innerHTML = `<div class="empty-list">Error rendering apps: ${e.message}</div>`;
200+
emptyMsg.textContent = `Error rendering apps: ${e.message}`;
201+
emptyMsg.classList.remove('hidden');
202+
}
203+
}
204+
205+
function applyFilters() {
206+
const query = searchQuery.toLowerCase();
207+
let visibleCount = 0;
208+
209+
allApps.forEach(app => {
210+
const item = appItemMap.get(app.packageName);
211+
if (!item) return;
212+
213+
const matchesSearch = (app.appLabel || '').toLowerCase().includes(query) ||
214+
(app.packageName || '').toLowerCase().includes(query);
215+
const matchesSystem = showSystemApp || !app.isSystem;
216+
const isVisible = matchesSearch && matchesSystem;
217+
218+
item.classList.toggle('search-hidden', !isVisible);
219+
if (isVisible) visibleCount++;
220+
});
221+
222+
const emptyMsg = document.getElementById('exclude-empty-msg');
223+
if (visibleCount === 0) {
224+
emptyMsg.textContent = 'No app found';
225+
emptyMsg.classList.remove('hidden');
226+
} else {
227+
emptyMsg.classList.add('hidden');
195228
}
196229
}
197230

@@ -211,35 +244,34 @@ function initExcludePage() {
211244
searchInput.focus();
212245
};
213246

214-
closeBtn.onclick = (e) => {
247+
closeBtn.onclick = () => {
215248
searchBar.classList.remove('show');
216249
document.querySelectorAll('.search-bg').forEach(el => el.classList.remove('hide'));
217250
searchQuery = '';
218251
searchInput.blur();
219-
if (e && e.isTrusted && searchInput.value !== '') {
220-
searchInput.value = '';
221-
renderAppList();
222-
}
252+
searchInput.value = '';
253+
applyFilters();
223254
};
224255

225256
searchInput.addEventListener('input', () => {
226257
searchQuery = searchInput.value;
227-
renderAppList();
258+
applyFilters();
228259
});
229260

230261
menuBtn.onclick = () => menu.show();
231262

232263
systemAppCheckbox.addEventListener('change', () => {
233264
showSystemApp = systemAppCheckbox.checked;
234265
localStorage.setItem('kp-next_show_system_app', showSystemApp);
235-
renderAppList();
266+
applyFilters();
236267
});
237268
if (localStorage.getItem('kp-next_show_system_app') === 'true') {
238269
showSystemApp = true;
239270
systemAppCheckbox.checked = true;
240271
}
241272

242273
document.getElementById('refresh-app-list').onclick = () => {
274+
appItemMap.clear();
243275
refreshAppList();
244276
};
245277

0 commit comments

Comments
 (0)