1+ //FAV WITH PILL AND ANIM
12let appJSON = [ ] ; // List of apps and info from apps.json
23let appSortInfo = { } ; // list of data to sort by, from appdates.csv { created, modified }
34let appCounts = { } ;
@@ -501,7 +502,7 @@ function handleAppInterface(app) {
501502 } ) ;
502503}
503504
504- function changeAppFavourite ( favourite , app ) {
505+ function changeAppFavourite ( favourite , app , refresh = true ) {
505506 if ( favourite ) {
506507 SETTINGS . appsFavoritedThisSession . push ( { "id" :app . id , "favs" :appSortInfo [ app . id ] && appSortInfo [ app . id ] . favourites ?appSortInfo [ app . id ] . favourites :0 } ) ;
507508 SETTINGS . favourites = SETTINGS . favourites . concat ( [ app . id ] ) ;
@@ -510,10 +511,11 @@ function changeAppFavourite(favourite, app) {
510511 SETTINGS . favourites = SETTINGS . favourites . filter ( e => e != app . id ) ;
511512 }
512513 saveSettings ( ) ;
513- refreshLibrary ( ) ;
514- refreshMyApps ( ) ;
514+ if ( refresh ) {
515+ refreshLibrary ( ) ;
516+ refreshMyApps ( ) ;
517+ }
515518}
516-
517519// =========================================== Top Navigation
518520function showTab ( tabname ) {
519521 htmlToArray ( document . querySelectorAll ( "#tab-navigate .tab-item" ) ) . forEach ( tab => {
@@ -543,6 +545,29 @@ librarySearchInput.addEventListener('input', evt => {
543545
544546// =========================================== App Info
545547
548+
549+
550+
551+ function getAppFavorites ( app ) {
552+ let info = appSortInfo [ app . id ] ;
553+ let appFavourites = 0 ;
554+ if ( info . favourites ) {
555+ let favsThisSession = SETTINGS . appsFavoritedThisSession . find ( obj => obj . id === app . id ) ;
556+ appFavourites = info . favourites ;
557+ if ( favsThisSession ) {
558+ if ( info . favourites != favsThisSession . favs ) {
559+ //database has been updated, remove app from favsThisSession
560+ SETTINGS . appsFavoritedThisSession = SETTINGS . appsFavoritedThisSession . filter ( obj => obj . id !== app . id ) ;
561+ }
562+ else {
563+ appFavourites += 1 ; //add one to give the illusion of immediate database changes
564+ }
565+ }
566+ }
567+ return appFavourites ;
568+ }
569+
570+
546571function getAppHTML ( app , appInstalled , forInterface ) {
547572 let version = getVersionInfo ( app , appInstalled ) ;
548573 let versionInfo = version . text ;
@@ -559,21 +584,11 @@ function getAppHTML(app, appInstalled, forInterface) {
559584 infoTxt . push ( `${ info . installs } reported installs (${ percentText } )` ) ;
560585 }
561586 if ( info . favourites ) {
562- let favsThisSession = SETTINGS . appsFavoritedThisSession . find ( obj => obj . id === app . id ) ;
563- let percent = ( info . favourites / info . installs * 100 ) . toFixed ( 0 ) ;
587+ appFavourites = getAppFavorites ( app ) ;
588+ let percent = ( appFavourites / info . installs * 100 ) . toFixed ( 0 ) ;
564589 let percentText = percent > 100 ?"More than 100% of installs" :percent + "% of installs" ;
565- if ( ! info . installs || info . installs < 1 ) { infoTxt . push ( `${ info . favourites } users favourited` ) }
566- else { infoTxt . push ( `${ info . favourites } users favourited (${ percentText } )` ) }
567- appFavourites = info . favourites ;
568- if ( favsThisSession ) {
569- if ( info . favourites != favsThisSession . favs ) {
570- //database has been updated, remove app from favsThisSession
571- SETTINGS . appsFavoritedThisSession = SETTINGS . appsFavoritedThisSession . filter ( obj => obj . id !== app . id ) ;
572- }
573- else {
574- appFavourites += 1 ; //add one to give the illusion of immediate database changes
575- }
576- }
590+ if ( ! info . installs || info . installs < 1 ) { infoTxt . push ( `${ appFavourites } users favourited` ) }
591+ else { infoTxt . push ( `${ appFavourites } users favourited (${ percentText } )` ) }
577592 }
578593 if ( infoTxt . length )
579594 versionTitle = `title="${ infoTxt . join ( "\n" ) } "` ;
@@ -585,9 +600,10 @@ function getAppHTML(app, appInstalled, forInterface) {
585600 let githubLink = Const . APP_SOURCECODE_URL ?
586601 `<a href="${ Const . APP_SOURCECODE_URL } /${ app . id } " target="_blank" class="link-github"><img src="core/img/github-icon-sml.png" alt="See the code on GitHub"/></a>` : "" ;
587602 let getAppFavouritesHTML = cnt => {
588- if ( ! cnt ) return "" ;
589- let txt = ( cnt > 999 ) ? Math . round ( cnt / 1000 ) + "k" : cnt ;
590- return `<span>${ txt } </span>` ;
603+ // Always show a count (0 if none) and format large numbers with 'k'
604+ let n = ( cnt && typeof cnt === 'number' ) ? cnt : 0 ;
605+ let txt = ( n > 999 ) ? Math . round ( n / 1000 ) + "k" : n ;
606+ return `<span class="fav-count">${ txt } </span>` ;
591607 } ;
592608
593609 let html = `<div class="tile column col-6 col-sm-12 col-xs-12 app-tile">
@@ -601,20 +617,21 @@ function getAppHTML(app, appInstalled, forInterface) {
601617 <a href="${ appurl } " class="link-copy-url" appid="${ app . id } " title="Copy link to app" style="position:absolute;top: 56px;left: -24px;"><img src="core/img/copy-icon.png" alt="Copy link to app"/></a>
602618 </div>
603619 <div class="tile-action">` ;
620+ html += `<div class="pill-container">` ;
604621 if ( forInterface == "library" ) html += `
605- <button class="btn btn-link btn-action btn-lg btn-favourite" appid="${ app . id } " title="Favourite"><i class="icon icon-favourite${ favourite ?" icon-favourite-active" :"" } ">${ getAppFavouritesHTML ( appFavourites ) } </i></button>
622+ <button class="btn btn-link btn-action btn-lg btn-favourite" appid="${ app . id } " title="Favourite">${ getAppFavouritesHTML ( appFavourites ) } <i class="icon icon-favourite${ favourite ?" icon-favourite-active" :"" } "></i></button>
606623 <button class="btn btn-link btn-action btn-lg ${ ( appInstalled && app . interface ) ?"" :"d-hide" } " appid="${ app . id } " title="Download data from app"><i class="icon icon-interface"></i></button>
607624 <button class="btn btn-link btn-action btn-lg ${ app . allow_emulator ?"" :"d-hide" } " appid="${ app . id } " title="Try in Emulator"><i class="icon icon-emulator"></i></button>
608625 <button class="btn btn-link btn-action btn-lg ${ ( SETTINGS . alwaysAllowUpdate && appInstalled ) || version . canUpdate ?"" :"d-hide" } " appid="${ app . id } " title="Update App"><i class="icon icon-refresh"></i></button>
609626 <button class="btn btn-link btn-action btn-lg ${ ( ! appInstalled && ! app . custom ) ?"" :"d-hide" } " appid="${ app . id } " title="Upload App"><i class="icon icon-upload"></i></button>
610627 <button class="btn btn-link btn-action btn-lg ${ appInstalled ?"" :"d-hide" } " appid="${ app . id } " title="Remove App"><i class="icon icon-delete"></i></button>
611628 <button class="btn btn-link btn-action btn-lg ${ app . custom ?"" :"d-hide" } " appid="${ app . id } " title="Customise and Upload App"><i class="icon icon-menu"></i></button>` ;
612629 if ( forInterface == "myapps" ) html += `
613- <button class="btn btn-link btn-action btn-lg btn-favourite" appid="${ app . id } " title="Favourite"><i class="icon icon-favourite${ favourite ?" icon-favourite-active" :"" } ">${ getAppFavouritesHTML ( appFavourites ) } </i></button>
630+ <button class="btn btn-link btn-action btn-lg btn-favourite" appid="${ app . id } " title="Favourite">${ getAppFavouritesHTML ( appFavourites ) } <i class="icon icon-favourite${ favourite ?" icon-favourite-active" :"" } "></i></button>
614631 <button class="btn btn-link btn-action btn-lg ${ ( appInstalled && app . interface ) ?"" :"d-hide" } " appid="${ app . id } " title="Download data from app"><i class="icon icon-interface"></i></button>
615632 <button class="btn btn-link btn-action btn-lg ${ ( SETTINGS . alwaysAllowUpdate && appInstalled ) || version . canUpdate ?'' :'d-hide' } " appid="${ app . id } " title="Update App"><i class="icon icon-refresh"></i></button>
616633 <button class="btn btn-link btn-action btn-lg" appid="${ app . id } " title="Remove App"><i class="icon icon-delete"></i></button>` ;
617- html += "</div>" ;
634+ html += "</div></div> " ;
618635 if ( forInterface == "library" ) {
619636 let screenshots = ( app . screenshots || [ ] ) . filter ( s => s . url ) ;
620637 if ( screenshots . length )
@@ -789,7 +806,6 @@ function refreshLibrary(options) {
789806 visibleApps = visibleApps . slice ( 0 , Const . MAX_APPS_SHOWN - 1 ) ;
790807 }
791808
792-
793809 panelbody . innerHTML = visibleApps . map ( ( app , idx ) => {
794810 let appInstalled = device . appsInstalled . find ( a => a . id == app . id ) ;
795811 return getAppHTML ( app , appInstalled , "library" ) ;
@@ -801,7 +817,7 @@ function refreshLibrary(options) {
801817 htmlToArray ( panelbody . getElementsByTagName ( "button" ) ) . forEach ( button => {
802818 button . addEventListener ( "click" , event => {
803819 let button = event . currentTarget ;
804- let icon = button . firstChild ;
820+ let icon = ( button . querySelector && ( button . querySelector ( 'i.icon' ) ) ) || button . firstElementChild || button . firstChild ;
805821 let appid = button . getAttribute ( "appid" ) ;
806822 let app = appNameToApp ( appid ) ;
807823 if ( ! app ) throw new Error ( "App " + appid + " not found" ) ;
@@ -842,8 +858,21 @@ function refreshLibrary(options) {
842858 if ( err != "" ) showToast ( "Failed, " + err , "error" ) ;
843859 } ) ;
844860 } else if ( button . classList . contains ( "btn-favourite" ) ) {
861+ // clicked: animate and toggle favourite state immediately for instant feedback
845862 let favourite = SETTINGS . favourites . find ( e => e == app . id ) ;
846- changeAppFavourite ( ! favourite , app ) ;
863+ changeAppFavourite ( ! favourite , app , false ) ;
864+ if ( icon ) icon . classList . toggle ( "icon-favourite-active" , ! favourite ) ;
865+ if ( icon ) icon . classList . add ( "favoriteAnim" ) ;
866+ // update visible count optimistically
867+ let cnt = getAppFavorites ( app ) ;
868+ if ( ! cnt ) return "" ;
869+ let txt = ( cnt > 999 ) ? Math . round ( cnt / 1000 ) + "k" : cnt ;
870+ let countEl = button . querySelector ( '.fav-count' ) ;
871+ countEl . textContent = String ( txt ) ;
872+ const ANIM_MS = 500 ;
873+ setTimeout ( ( ) => {
874+ try { if ( icon ) icon . classList . remove ( "favoriteAnim" ) ; } catch ( e ) { }
875+ } , ANIM_MS ) ;
847876 }
848877 } ) ;
849878 } ) ;
@@ -1109,7 +1138,7 @@ function refreshMyApps() {
11091138 htmlToArray ( panelbody . getElementsByTagName ( "button" ) ) . forEach ( button => {
11101139 button . addEventListener ( "click" , event => {
11111140 let button = event . currentTarget ;
1112- let icon = button . firstChild ;
1141+ let icon = ( button . querySelector && ( button . querySelector ( 'i.icon' ) ) ) || button . firstElementChild || button . firstChild ;
11131142 let appid = button . getAttribute ( "appid" ) ;
11141143 let app = appNameToApp ( appid ) ;
11151144 if ( ! app ) throw new Error ( "App " + appid + " not found" ) ;
@@ -1122,7 +1151,26 @@ function refreshMyApps() {
11221151 } ) ;
11231152 if ( icon . classList . contains ( "icon-favourite" ) ) {
11241153 let favourite = SETTINGS . favourites . find ( e => e == app . id ) ;
1125- changeAppFavourite ( ! favourite , app ) ;
1154+ try {
1155+ if ( icon ) icon . classList . toggle ( "icon-favourite-active" , ! favourite ) ;
1156+ if ( icon ) icon . classList . add ( "favoriteAnim" ) ;
1157+ } catch ( e ) { }
1158+ // update visible count optimistically
1159+ try {
1160+ let countEl = button . querySelector ( '.fav-count' ) ;
1161+ if ( countEl ) {
1162+ let txt = countEl . textContent . trim ( ) ;
1163+ let val = 0 ;
1164+ if ( txt . endsWith ( 'k' ) ) val = Math . round ( parseFloat ( txt ) * 1000 ) ;
1165+ else val = parseInt ( txt ) || 0 ;
1166+ val = ( ! favourite ) ? val + 1 : Math . max ( 0 , val - 1 ) ;
1167+ countEl . textContent = ( val > 999 ) ? Math . round ( val / 1000 ) + 'k' : String ( val ) ;
1168+ }
1169+ } catch ( e ) { }
1170+ const ANIM_MS = 500 ;
1171+ setTimeout ( ( ) => {
1172+ try { if ( icon ) icon . classList . remove ( "favoriteAnim" ) ; } catch ( e ) { }
1173+ } , ANIM_MS ) ;
11261174 }
11271175 } ) ;
11281176 } ) ;
0 commit comments