Skip to content

Commit 04c2aab

Browse files
authored
fix(hub-ui): show failed icons and retry on remount (#381)
1 parent 173ddd8 commit 04c2aab

2 files changed

Lines changed: 25 additions & 14 deletions

File tree

packages/hub-ui/src/client/components/icons/IconifyIcon.vue

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,40 @@ const iconifyParsed = computed(() => {
2020
})
2121
2222
const iconifyLoaded = ref<string | undefined>(undefined)
23-
watchEffect(async () => {
24-
if (!iconifyParsed.value) {
25-
iconifyLoaded.value = undefined
23+
const failed = ref(false)
24+
watchEffect(async (onCleanup) => {
25+
let active = true
26+
onCleanup(() => {
27+
active = false
28+
})
29+
iconifyLoaded.value = undefined
30+
failed.value = false
31+
if (!iconifyParsed.value)
2632
return
27-
}
2833
try {
29-
iconifyLoaded.value = await getIconifySvg(iconifyParsed.value.collection, iconifyParsed.value.icon)
34+
const svg = await getIconifySvg(iconifyParsed.value.collection, iconifyParsed.value.icon)
35+
if (active)
36+
iconifyLoaded.value = svg
3037
}
3138
catch {
32-
// A failed icon fetch (offline / flaky CDN) should degrade to a blank icon,
33-
// not throw out of the async effect and crash the surrounding panel.
34-
iconifyLoaded.value = undefined
39+
/** Keep fetch failures local to the icon so the surrounding panel remains usable. */
40+
if (active)
41+
failed.value = true
3542
}
3643
})
3744
</script>
3845

3946
<template>
47+
<div v-if="failed" class="i-ph:warning-duotone w-full h-full" aria-hidden="true" />
4048
<div
41-
v-if="iconifyParsed"
49+
v-else-if="iconifyParsed"
50+
aria-hidden="true"
4251
v-html="iconifyLoaded"
4352
/>
4453
<img
4554
v-else :src="icon"
55+
alt=""
56+
aria-hidden="true"
4657
class="w-full h-full m-auto"
4758
draggable="false"
4859
>

packages/hub-ui/src/client/utils/iconify.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ export async function getIconifySvg(collection: string, icon: string) {
2424

2525
async function _get() {
2626
const url = `https://api.iconify.design/${collection}/${icon}.svg?color=currentColor&width=100%`
27-
// Bound the request so a stalled connection (offline / flaky CDN / firewall
28-
// black-holing the host) rejects instead of hanging forever; the caller
29-
// already degrades a rejected fetch to a blank icon.
30-
const svg = await fetch(url, { signal: AbortSignal.timeout(10_000) }).then(res => res.text())
31-
return purify.sanitize(svg)
27+
/** Bound stalled requests so the caller can display its failure glyph. */
28+
const response = await fetch(url, { signal: AbortSignal.timeout(10_000) })
29+
if (!response.ok)
30+
throw new Error(`Iconify request failed: ${response.status}`)
31+
return purify.sanitize(await response.text())
3232
}
3333
}

0 commit comments

Comments
 (0)