Skip to content

Commit 426d605

Browse files
igorDykhtaIhor Dykhta
andauthored
feat: add loading spinner to exported HTML maps (#3612)
* feat: add spinner for exported maps Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local> * follow up Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local> * follow up Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local> --------- Signed-off-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local> Co-authored-by: Ihor Dykhta <ihordykhta@Ihors-MacBook-Pro.local>
1 parent 56d4332 commit 426d605

1 file changed

Lines changed: 68 additions & 1 deletion

File tree

src/utils/src/export-map-html.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,29 @@ export const exportMapToHTML = (options, version = KEPLER_GL_VERSION) => {
8686
// native loader (which fails under file://).
8787
window.esmsInitOptions = {shimMode: true};
8888
</script>
89+
<script>
90+
// Replace the loading overlay with an error message.
91+
// Safe to call at any time — once React has mounted and removed
92+
// #kepler-load-screen this becomes a no-op.
93+
window._keplerShowLoadError = function(msg) {
94+
var loadScreen = document.getElementById('kepler-load-screen');
95+
if (!loadScreen) return;
96+
var spinner = loadScreen.querySelector('.kepler-spinner');
97+
var label = loadScreen.querySelector('.kepler-load-label');
98+
if (spinner) spinner.style.display = 'none';
99+
if (label) label.style.display = 'none';
100+
var errEl = document.createElement('div');
101+
errEl.className = 'kepler-load-error';
102+
errEl.textContent = msg || 'Failed to load the map. Check your internet connection and reload the page.';
103+
loadScreen.appendChild(errEl);
104+
};
105+
// Catch unhandled promise rejections (e.g. ES module imports failing
106+
// inside the module-shim script before loadScript is ever called).
107+
window.addEventListener('unhandledrejection', function(event) {
108+
console.error('kepler.gl: unhandled rejection during map load', event.reason);
109+
window._keplerShowLoadError('Failed to load the map. Check your internet connection and reload the page.');
110+
});
111+
</script>
89112
<script src="https://unpkg.com/es-module-shims@${ES_MODULE_SHIMS_VERSION}/dist/es-module-shims.js" crossorigin="anonymous"></script>
90113
91114
<script type="importmap-shim">
@@ -155,10 +178,44 @@ export const exportMapToHTML = (options, version = KEPLER_GL_VERSION) => {
155178
})
156179
.catch(function(error) {
157180
console.error('kepler.gl: failed to load UMD bundle', error);
181+
window._keplerShowLoadError('Failed to load the map bundle. Check your internet connection and reload the page.');
158182
});
159183
</script>
160184
<style type="text/css">
161185
body {margin: 0; padding: 0; overflow: hidden;}
186+
#kepler-load-screen {
187+
position: fixed;
188+
top: 0; left: 0;
189+
width: 100%; height: 100%;
190+
background: #29323c;
191+
display: flex;
192+
flex-direction: column;
193+
align-items: center;
194+
justify-content: center;
195+
gap: 20px;
196+
z-index: 9999;
197+
font-family: ff-clan-web-pro, 'Helvetica Neue', Helvetica, sans-serif;
198+
color: #a0a7b4;
199+
}
200+
#kepler-load-screen .kepler-spinner {
201+
width: 48px; height: 48px;
202+
border: 4px solid rgba(255,255,255,0.08);
203+
border-top-color: #1fbad6;
204+
border-radius: 50%;
205+
animation: kepler-spin 0.8s linear infinite;
206+
}
207+
#kepler-load-screen .kepler-load-label {
208+
font-size: 13px;
209+
letter-spacing: 0.5px;
210+
}
211+
#kepler-load-screen .kepler-load-error {
212+
font-size: 13px;
213+
color: #f05e45;
214+
max-width: 360px;
215+
text-align: center;
216+
line-height: 1.5;
217+
}
218+
@keyframes kepler-spin { to { transform: rotate(360deg); } }
162219
</style>
163220
164221
<!--MapBox token-->
@@ -199,7 +256,10 @@ export const exportMapToHTML = (options, version = KEPLER_GL_VERSION) => {
199256
<body>
200257
<!-- We will put our React component inside this div. -->
201258
<div id="app">
202-
<!-- Kepler.gl map will be placed here-->
259+
<div id="kepler-load-screen">
260+
<div class="kepler-spinner"></div>
261+
<div class="kepler-load-label">Loading map…</div>
262+
</div>
203263
</div>
204264
205265
<!--
@@ -363,6 +423,13 @@ export const exportMapToHTML = (options, version = KEPLER_GL_VERSION) => {
363423
window.addEventListener('resize', handleResize);
364424
return function() {window.removeEventListener('resize', handleResize);};
365425
}, []);
426+
// Remove the loading overlay after the first render is painted.
427+
// useEffect fires post-commit, so this is safe under React 19's
428+
// concurrent renderer — the map is visible before the overlay goes away.
429+
react.useEffect(function removeLoadScreen() {
430+
var loadScreen = document.getElementById('kepler-load-screen');
431+
if (loadScreen) loadScreen.remove();
432+
}, []);
366433
return react.createElement(
367434
'div',
368435
{style: {position: 'absolute', left: 0, width: '100vw', height: '100vh'}},

0 commit comments

Comments
 (0)