Skip to content

Commit 7a2aab6

Browse files
committed
fix(hub-ui): show failed icons and retry on remount
1 parent 7405628 commit 7a2aab6

2 files changed

Lines changed: 30 additions & 14 deletions

File tree

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

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,35 @@ 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+
if (active)
40+
failed.value = true
3541
}
3642
})
3743
</script>
3844

3945
<template>
46+
<svg v-if="failed" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true" class="w-full h-full">
47+
<rect x="3" y="3" width="18" height="18" rx="3" />
48+
<path d="M12 7v6m0 3v1" />
49+
</svg>
4050
<div
41-
v-if="iconifyParsed"
51+
v-else-if="iconifyParsed"
4252
v-html="iconifyLoaded"
4353
/>
4454
<img

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,16 @@ 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+
const response = await fetch(url, { signal: AbortSignal.timeout(10_000) })
28+
if (!response.ok)
29+
throw new Error(`Iconify request failed: ${response.status}`)
30+
const svg = purify.sanitize(await response.text())
31+
const document = new DOMParser().parseFromString(svg, 'image/svg+xml')
32+
if (document.documentElement.localName !== 'svg'
33+
|| document.querySelector('parsererror')
34+
|| !document.querySelector('path, circle, ellipse, rect, line, polyline, polygon, text, use, image')) {
35+
throw new Error('Iconify returned an invalid SVG')
36+
}
37+
return svg
3238
}
3339
}

0 commit comments

Comments
 (0)