Skip to content

Commit f243c4c

Browse files
committed
feat: show no network error in image gallery when device is not connected to the internet
1 parent 7a972bd commit f243c4c

File tree

5 files changed

+393
-48
lines changed

5 files changed

+393
-48
lines changed

src/LiveDevelopment/BrowserScripts/RemoteFunctions.js

Lines changed: 215 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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">&#8249;</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">&#8249;</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">&#8250;</div>
30183045
</div>
3019-
<div class="phoenix-image-gallery-nav right">&#8250;</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

Comments
 (0)