Skip to content

Commit b6037ce

Browse files
committed
feat(hub-ui): add loading placeholder for iframes
1 parent bb83040 commit b6037ce

1 file changed

Lines changed: 31 additions & 6 deletions

File tree

packages/hub-ui/src/client/components/views/ViewIframe.vue

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,24 @@ const ADDRESS_BAR_HEIGHT = 40
5252
5353
const isLoading = ref(true)
5454
const isIframeLoading = ref(false)
55+
// Flips true once the pane is mounted so the hide/show effect can run — a plain
56+
// `pane.isMounted` read isn't reactive.
57+
const paneReady = ref(false)
5558
5659
// A devframe whose client assets are published as their own npm package
5760
// answers with a fallback page when it can reach neither a local install nor
5861
// the CDN they live on. That page reports itself over `postMessage`, so the
5962
// failure renders as a hub panel — with the install command and a retry —
6063
// rather than as a bare page inside the frame.
6164
const assetsError = ref<RemoteAssetsErrorMessage | null>(null)
65+
66+
// The blank iframe paints white while its content loads, so a placeholder is
67+
// only useful when the pane steps aside (`pane.hide()`) to reveal it — the same
68+
// layering trick `ViewAssetsError` relies on. Show it during the initial load
69+
// and any hard navigation/refresh, but never on top of the assets-error panel.
70+
const showLoadingPlaceholder = computed(
71+
() => !assetsError.value && (isLoading.value || isIframeLoading.value),
72+
)
6273
const viewFrame = useTemplateRef<HTMLDivElement>('viewFrame')
6374
const urlInputRef = useTemplateRef<HTMLInputElement>('urlInput')
6475
@@ -236,6 +247,10 @@ onMounted(() => {
236247
237248
if (existed)
238249
updateCurrentUrl()
250+
else
251+
// A freshly created pane is loading its initial content — reflect it so the
252+
// placeholder covers the first paint, not just later navigations.
253+
isIframeLoading.value = true
239254
240255
// Listen for iframe load events
241256
onIframeLoad = () => {
@@ -268,19 +283,23 @@ onMounted(() => {
268283
})
269284
270285
// The iframe lives in its own layer stacked over this view, so the error
271-
// panel is only visible once the pane steps aside. `hide()` keeps the frame
272-
// alive (and its state intact) for the retry.
286+
// panel and the loading placeholder are only visible once the pane steps
287+
// aside. `hide()` keeps the frame alive (and its state intact) so the content
288+
// keeps loading behind the placeholder and survives a retry.
273289
watchEffect(() => {
274-
if (assetsError.value)
290+
if (!paneReady.value)
291+
return
292+
if (assetsError.value || isIframeLoading.value)
275293
pane.hide()
276-
else if (pane.isMounted)
294+
else
277295
pane.show()
278296
})
279297
280298
window.addEventListener('message', onWindowMessage)
281299
282300
pane.mount(viewFrame.value!)
283301
isLoading.value = false
302+
paneReady.value = true
284303
nextTick(() => {
285304
pane.update()
286305
})
@@ -364,8 +383,14 @@ onUnmounted(() => {
364383
ref="viewFrame"
365384
class="devframes-view-iframe relative w-full h-full flex-1 items-center justify-center"
366385
>
367-
<div v-if="isLoading" class="op50 z--1">
368-
Loading iframe...
386+
<div
387+
v-if="showLoadingPlaceholder"
388+
class="devframes-view-iframe-loading absolute inset-0 flex flex-col items-center justify-center gap-2 bg-base"
389+
>
390+
<div class="i-ph:circle-notch-duotone animate-spin text-3xl color-faint" />
391+
<div class="text-sm color-muted">
392+
Loading…
393+
</div>
369394
</div>
370395
<ViewAssetsError
371396
v-if="assetsError"

0 commit comments

Comments
 (0)