@@ -2959,6 +2959,11 @@ function RemoteFunctions(config = {}) {
29592959 this . allImages = [ ] ;
29602960 this . scrollPosition = 0 ;
29612961
2962+ this . _isOffline = ! navigator . onLine ;
2963+ this . _offlineBannerDismissed = false ;
2964+ this . _onlineHandler = null ;
2965+ this . _offlineHandler = null ;
2966+
29622967 this . create ( ) ;
29632968 }
29642969
@@ -2975,48 +2980,69 @@ function RemoteFunctions(config = {}) {
29752980
29762981 <div class='phoenix-image-gallery-container'>
29772982 <div class='phoenix-image-gallery-header'>
2978- <div class='phoenix-image-gallery-header-title'>
2979- <div class='phoenix-image-gallery-header-icon'>
2980- ${ icons . imageGallery }
2983+ <div class='phoenix-image-gallery-offline-banner hidden'>
2984+ <div class='phoenix-image-gallery-offline-banner-content'>
2985+ <div class='phoenix-image-gallery-offline-banner-icon'>
2986+ ${ icons . wifiOff }
2987+ </div>
2988+ <div class='phoenix-image-gallery-offline-banner-text'>
2989+ ${ strings . imageGalleryOfflineBanner }
2990+ </div>
29812991 </div>
2982- <div class='phoenix-image-gallery-header-text'>
2983- ${ strings . imageGallery }
2992+ <div class='phoenix-image-gallery-offline-banner-actions'>
2993+ <button class='phoenix-image-gallery-offline-retry-button'>
2994+ ${ icons . refresh }
2995+ <span>${ strings . imageGalleryOfflineRetry } </span>
2996+ </button>
29842997 </div>
29852998 </div>
29862999
2987- <div class=" phoenix-image-gallery-search-container" >
2988- <div class="search-wrapper" >
2989- <button class="search- icon" title=" ${ strings . imageGallerySearchButton } "> ${ icons . search } </button >
2990- <input
2991- type="text"
2992- placeholder=" ${ strings . imageGallerySearchPlaceholder } "
2993- />
2994- </div>
2995- </div>
3000+ <div class=' phoenix-image-gallery-header-row' >
3001+ <div class='phoenix-image-gallery-header-title' >
3002+ <div class='phoenix-image-gallery-header- icon' >
3003+ ${ icons . imageGallery }
3004+ </div>
3005+ <div class='phoenix-image-gallery-header-text'>
3006+ ${ strings . imageGallery }
3007+ </div>
3008+ </div>
29963009
2997- <div class='phoenix-image-gallery-upload-container'>
2998- <button title="${ strings . imageGallerySelectFromComputerTooltip } ">${ icons . selectImageFromComputer } ${ strings . imageGallerySelectFromComputer } </button>
2999- <input type="file" class="phoenix-file-input" accept="image/*" style="display: none !important;">
3000- </div>
3010+ <div class="phoenix-image-gallery-search-container">
3011+ <div class="search-wrapper">
3012+ <button class="search-icon" title="${ strings . imageGallerySearchButton } ">${ icons . search } </button>
3013+ <input
3014+ type="text"
3015+ placeholder="${ strings . imageGallerySearchPlaceholder } "
3016+ />
3017+ </div>
3018+ </div>
30013019
3002- <div class='phoenix-image-gallery-right-buttons'>
3003- <button class='phoenix-image-gallery-download-folder-button' title="${ strings . imageGallerySelectDownloadFolder } ">
3004- ${ icons . folderSettings }
3005- </button>
3020+ <div class='phoenix-image-gallery-upload-container'>
3021+ <button title="${ strings . imageGallerySelectFromComputerTooltip } ">${ icons . selectImageFromComputer } ${ strings . imageGallerySelectFromComputer } </button>
3022+ <input type="file" class="phoenix-file-input" accept="image/*" style="display: none !important;">
3023+ </div>
3024+
3025+ <div class='phoenix-image-gallery-right-buttons'>
3026+ <button class='phoenix-image-gallery-download-folder-button' title="${ strings . imageGallerySelectDownloadFolder } ">
3027+ ${ icons . folderSettings }
3028+ </button>
30063029
3007- <button class='phoenix-image-gallery-close-button' title="${ strings . imageGalleryClose } ">
3008- ${ icons . close }
3009- </button>
3030+ <button class='phoenix-image-gallery-close-button' title="${ strings . imageGalleryClose } ">
3031+ ${ icons . close }
3032+ </button>
3033+ </div>
30103034 </div>
30113035 </div>
30123036
3013- <div class="phoenix-image-gallery-nav left">‹</div>
3014- <div class="phoenix-image-gallery-strip">
3015- <div class="phoenix-image-gallery-row phoenix-image-gallery-loading">
3016- ${ strings . imageGalleryLoadingInitial }
3037+ <div class="phoenix-image-gallery-strip-container">
3038+ <div class="phoenix-image-gallery-nav left">‹</div>
3039+ <div class="phoenix-image-gallery-strip">
3040+ <div class="phoenix-image-gallery-row phoenix-image-gallery-loading">
3041+ ${ strings . imageGalleryLoadingInitial }
3042+ </div>
30173043 </div>
3044+ <div class="phoenix-image-gallery-nav right">›</div>
30183045 </div>
3019- <div class="phoenix-image-gallery-nav right">›</div>
30203046 </div>
30213047 ` ;
30223048 } ,
@@ -3039,15 +3065,115 @@ function RemoteFunctions(config = {}) {
30393065 return qualityQueries [ randIndex ] ;
30403066 } ,
30413067
3068+ _checkNetworkStatus : function ( ) {
3069+ const wasOffline = this . _isOffline ;
3070+ this . _isOffline = ! navigator . onLine ;
3071+ return wasOffline !== this . _isOffline ;
3072+ } ,
3073+
3074+ _showOfflineBanner : function ( ) {
3075+ if ( this . _offlineBannerDismissed ) { return ; }
3076+
3077+ const banner = this . _shadow . querySelector ( '.phoenix-image-gallery-offline-banner' ) ;
3078+ if ( ! banner ) { return ; }
3079+
3080+ banner . classList . remove ( 'hidden' , 'fade-out' ) ;
3081+ this . _setSearchInputDisabled ( true ) ;
3082+ } ,
3083+
3084+ _hideOfflineBanner : function ( withAnimation = true ) {
3085+ const banner = this . _shadow . querySelector ( '.phoenix-image-gallery-offline-banner' ) ;
3086+ if ( ! banner ) { return ; }
3087+
3088+ if ( withAnimation ) {
3089+ banner . classList . add ( 'fade-out' ) ;
3090+ setTimeout ( ( ) => {
3091+ banner . classList . add ( 'hidden' ) ;
3092+ banner . classList . remove ( 'fade-out' ) ;
3093+ } , 300 ) ;
3094+ } else {
3095+ banner . classList . add ( 'hidden' ) ;
3096+ }
3097+
3098+ this . _setSearchInputDisabled ( false ) ;
3099+ } ,
3100+
3101+ _setSearchInputDisabled : function ( disabled ) {
3102+ const searchWrapper = this . _shadow . querySelector ( '.search-wrapper' ) ;
3103+ const searchInput = this . _shadow . querySelector ( '.search-wrapper input' ) ;
3104+ const searchButton = this . _shadow . querySelector ( '.search-icon' ) ;
3105+
3106+ if ( disabled ) {
3107+ if ( searchWrapper ) { searchWrapper . classList . add ( 'disabled' ) ; }
3108+ if ( searchInput ) { searchInput . disabled = true ; }
3109+ if ( searchButton ) { searchButton . disabled = true ; }
3110+ } else {
3111+ if ( searchWrapper ) { searchWrapper . classList . remove ( 'disabled' ) ; }
3112+ if ( searchInput ) { searchInput . disabled = false ; }
3113+ if ( searchButton ) { searchButton . disabled = false ; }
3114+ }
3115+ } ,
3116+
3117+ _setupNetworkListeners : function ( ) {
3118+ this . _removeNetworkListeners ( ) ;
3119+
3120+ this . _onlineHandler = ( ) => {
3121+ const statusChanged = this . _checkNetworkStatus ( ) ;
3122+ if ( statusChanged && ! this . _isOffline ) {
3123+ this . _hideOfflineBanner ( true ) ;
3124+ this . _offlineBannerDismissed = false ;
3125+ }
3126+ } ;
3127+
3128+ this . _offlineHandler = ( ) => {
3129+ const statusChanged = this . _checkNetworkStatus ( ) ;
3130+ if ( statusChanged && this . _isOffline ) {
3131+ this . _showOfflineBanner ( ) ;
3132+ }
3133+ } ;
3134+
3135+ window . addEventListener ( 'online' , this . _onlineHandler ) ;
3136+ window . addEventListener ( 'offline' , this . _offlineHandler ) ;
3137+ } ,
3138+
3139+ _removeNetworkListeners : function ( ) {
3140+ if ( this . _onlineHandler ) {
3141+ window . removeEventListener ( 'online' , this . _onlineHandler ) ;
3142+ this . _onlineHandler = null ;
3143+ }
3144+ if ( this . _offlineHandler ) {
3145+ window . removeEventListener ( 'offline' , this . _offlineHandler ) ;
3146+ this . _offlineHandler = null ;
3147+ }
3148+ } ,
3149+
30423150 _fetchImages : function ( searchQuery , page = 1 , append = false ) {
30433151 this . _currentSearchQuery = searchQuery ;
3152+ this . _checkNetworkStatus ( ) ;
30443153
30453154 if ( ! append && this . _loadFromCache ( searchQuery ) ) { // try cache first
3155+ // if offline and cache loaded successfully, show offline banner
3156+ if ( this . _isOffline ) {
3157+ this . _showOfflineBanner ( ) ;
3158+ }
30463159 return ;
30473160 }
30483161 if ( append && this . _loadPageFromCache ( searchQuery , page ) ) { // try to load new page from cache
30493162 return ;
30503163 }
3164+
3165+ // if offline and no cache, show banner and don't make API call
3166+ if ( this . _isOffline ) {
3167+ if ( ! append ) {
3168+ this . _showOfflineBanner ( ) ;
3169+ this . _showError ( strings . imageGalleryLoadError ) ;
3170+ } else {
3171+ this . _isLoadingMore = false ;
3172+ this . _hideLoadingMore ( ) ;
3173+ }
3174+ return ;
3175+ }
3176+
30513177 // if unable to load from cache, we make the API call
30523178 this . _fetchFromAPI ( searchQuery , page , append ) ;
30533179 } ,
@@ -3096,11 +3222,22 @@ function RemoteFunctions(config = {}) {
30963222 } )
30973223 . catch ( error => {
30983224 console . error ( 'Failed to fetch images:' , error ) ;
3225+ // check if error is because of no network
3226+ this . _checkNetworkStatus ( ) ;
3227+
30993228 if ( ! append ) {
31003229 this . _showError ( strings . imageGalleryLoadError ) ;
3230+ // if user is offline, show the offline banner
3231+ if ( this . _isOffline ) {
3232+ this . _showOfflineBanner ( ) ;
3233+ }
31013234 } else {
31023235 this . _isLoadingMore = false ;
31033236 this . _hideLoadingMore ( ) ;
3237+ // if user is offline during pagination, show banner
3238+ if ( this . _isOffline ) {
3239+ this . _showOfflineBanner ( ) ;
3240+ }
31043241 }
31053242 } ) ;
31063243 } ,
@@ -3402,6 +3539,49 @@ function RemoteFunctions(config = {}) {
34023539 e . stopPropagation ( ) ;
34033540 } ) ;
34043541 }
3542+
3543+ // no network banner event handlers
3544+ const retryButton = this . _shadow . querySelector ( '.phoenix-image-gallery-offline-retry-button' ) ;
3545+
3546+ if ( retryButton ) {
3547+ retryButton . addEventListener ( 'click' , ( e ) => {
3548+ e . stopPropagation ( ) ;
3549+
3550+ const textElement = this . _shadow . querySelector ( '.phoenix-image-gallery-offline-banner-text' ) ;
3551+ if ( ! textElement ) { return ; }
3552+
3553+ const originalText = textElement . textContent ;
3554+
3555+ // add checking state - loading cursor and dots animation
3556+ retryButton . classList . add ( 'checking' ) ;
3557+ textElement . classList . add ( 'checking' ) ;
3558+ textElement . textContent = strings . imageGalleryCheckingConnection ;
3559+
3560+ // just a small 600ms timer because if its instant then it feels like we didn't even try to connect
3561+ setTimeout ( ( ) => {
3562+ this . _checkNetworkStatus ( ) ;
3563+
3564+ retryButton . classList . remove ( 'checking' ) ;
3565+ textElement . classList . remove ( 'checking' ) ;
3566+
3567+ if ( this . _isOffline ) {
3568+ // user is still offline, show message and keep it
3569+ textElement . textContent = strings . imageGalleryStillOffline ;
3570+ } else {
3571+ // user became online
3572+ textElement . textContent = originalText ;
3573+ this . _hideOfflineBanner ( true ) ;
3574+
3575+ if ( this . _currentSearchQuery ) {
3576+ this . currentPage = 1 ;
3577+ this . allImages = [ ] ;
3578+ this . scrollPosition = 0 ;
3579+ this . _fetchImages ( this . _currentSearchQuery ) ;
3580+ }
3581+ }
3582+ } , 600 ) ;
3583+ } ) ;
3584+ }
34053585 } ,
34063586
34073587 // append true means load more images (user clicked on nav-right)
@@ -3630,11 +3810,16 @@ function RemoteFunctions(config = {}) {
36303810 window . document . body . appendChild ( this . body ) ;
36313811 this . _attachEventHandlers ( ) ;
36323812
3813+ this . _setupNetworkListeners ( ) ;
3814+ this . _checkNetworkStatus ( ) ;
3815+
36333816 const queryToUse = _imageGalleryCache . currentQuery || this . _getDefaultQuery ( ) ;
36343817 this . _fetchImages ( queryToUse ) ;
36353818 } ,
36363819
36373820 remove : function ( ) {
3821+ this . _removeNetworkListeners ( ) ;
3822+
36383823 _imageRibbonGallery = null ;
36393824 if ( this . body && this . body . parentNode && this . body . parentNode === window . document . body ) {
36403825 window . document . body . removeChild ( this . body ) ;
0 commit comments