@@ -25,11 +25,14 @@ const iconObserver = new IntersectionObserver((entries) => {
2525 iconObserver . unobserve ( entry . target ) ;
2626 }
2727 } ) ;
28- } , { rootMargin : '200px ' } ) ;
28+ } , { rootMargin : '100px ' } ) ;
2929
3030async 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+
5460async 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
6571async 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