Skip to content

Commit

Permalink
feat: fallback when feed icon load error
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Jul 30, 2024
1 parent b3de0be commit 9628aae
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 59 deletions.
126 changes: 87 additions & 39 deletions src/renderer/src/components/feed-icon.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,110 @@
import { Avatar, AvatarFallback, AvatarImage } from "@radix-ui/react-avatar"
import { SiteIcon } from "@renderer/components/site-icon"
import { Media } from "@renderer/components/ui/media"
import { getColorScheme, stringToHue } from "@renderer/lib/color"
import { cn } from "@renderer/lib/utils"
import type { CombinedEntryModel, FeedModel } from "@renderer/models"
import type { ReactNode } from "react"
import { useMemo } from "react"

export function FeedIcon({
feed,
entry,
fallbackUrl,
className,
size = 20,
fallback = false,
}: {
feed: FeedModel
entry?: CombinedEntryModel["entries"]
fallbackUrl?: string
className?: string
size?: number
/**
* Image loading error fallback to site icon
*/
fallback?: boolean
}) {
const image = entry?.authorAvatar || feed.image
if (image) {
return (
<Media
src={image}
type="photo"
loading="lazy"
className={cn("mr-2 shrink-0 rounded-sm", className)}
style={{
width: size,
height: size,
}}
proxy={{
width: size * 2,
height: size * 2,
}}
/>
)
} else if (feed.siteUrl) {
return (
<SiteIcon
url={feed.siteUrl}
className={className}
style={{
width: size,
height: size,
}}
/>
)
} else if (fallbackUrl) {

let ImageElement: ReactNode

switch (true) {
case !!image: {
ImageElement = (
<Media
src={image}
type="photo"
loading="lazy"
className={cn("mr-2 shrink-0 rounded-sm", className)}
style={{
width: size,
height: size,
}}
proxy={{
width: size * 2,
height: size * 2,
}}
/>
)
break
}
case !!fallbackUrl:
case !!feed.siteUrl: {
ImageElement = (
<SiteIcon
fallbackText={feed.title!}
fallback={fallback}
url={feed.siteUrl || fallbackUrl}
className={className}
style={{
width: size,
height: size,
}}
/>
)
break
}
}

const colors = useMemo(
() => getColorScheme(stringToHue(feed.title || feed.url), true),
[feed.title, feed.url],
)

if (!ImageElement) {
return null
}

if (fallback && !!image) {
return (
<SiteIcon
url={fallbackUrl}
className={className}
style={{
width: size,
height: size,
}}
/>
<Avatar>
<AvatarImage src={image || ""} asChild>
{ImageElement}
</AvatarImage>
<AvatarFallback asChild>
<span
style={
{
"width": size,
"height": size,

"--fo-light-background": colors.light.background,
"--fo-dark-background": colors.dark.background,
} as any
}
className={cn(
"mr-2 inline-flex shrink-0 items-center justify-center rounded-sm text-xs font-medium",
"bg-[var(--fo-light-background)] text-white dark:bg-[var(--fo-dark-background)] dark:text-black",
className,
)}
>
{!!feed.title && feed.title[0]}
</span>
</AvatarFallback>
</Avatar>
)
} else {
return null
}

return ImageElement
}
70 changes: 53 additions & 17 deletions src/renderer/src/components/site-icon.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { getColorScheme, stringToHue } from "@renderer/lib/color"
import { cn } from "@renderer/lib/utils"
import { useEffect, useMemo, useState } from "react"
import { parse } from "tldts"

import { PlatformIcon } from "./ui/platform-icon"
Expand All @@ -7,21 +9,38 @@ export function SiteIcon({
url,
className,
style,
fallback,
fallbackText,
}: {
url?: string
className?: string
style?: React.CSSProperties
/**
* Fallback image when the site icon is not available
*/
fallback?: boolean
fallbackText?: string
}) {
let host
let src
let fallback
let host: string
let src: string
let fallbackUrl = ""

const colors = useMemo(
() => (url && fallback ? getColorScheme(stringToHue(url), true) : null),
[fallback, url],
)

const [showFallback, setShowFallback] = useState(false)
useEffect(() => {
showFallback && fallback && setShowFallback(false)
}, [fallback, showFallback, url])

if (!url) return null

try {
host = new URL(url).host
const pureDomain = parse(host).domainWithoutSuffix
fallback = `https://avatar.vercel.sh/${pureDomain}.svg?text=${pureDomain
fallbackUrl = `https://avatar.vercel.sh/${pureDomain}.svg?text=${pureDomain
?.slice(0, 2)
.toUpperCase()}`
src = `https://unavatar.follow.is/${host}?fallback=${fallback}`
Expand All @@ -36,21 +55,38 @@ export function SiteIcon({
<PlatformIcon
url={url}
style={style}
className={cn("mr-2 size-5 shrink-0 rounded-sm", className)}
className={cn("relative mr-2 size-5 shrink-0 rounded-sm", className)}
>
<img
src={src}
loading="lazy"
onError={(e) => {
if (fallback && e.currentTarget.src !== fallback) {
e.currentTarget.src = fallback
} else {
// Empty svg
e.currentTarget.src =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3C/svg%3E"
{showFallback && fallback ? (
<div
data-error-image={src || fallbackUrl}
className="center absolute inset-0 bg-[var(--fo-light-background)] text-[9px] text-white dark:bg-[var(--fo-dark-background)] dark:text-black"
style={
{
"--fo-light-background": colors.light.background,
"--fo-dark-background": colors.dark.background,
} as any
}
}}
/>
>
{!!fallbackText && fallbackText[0]}
</div>
) : (
<img
src={src}
loading="lazy"
onError={(e) => {
if (fallbackUrl && e.currentTarget.src !== fallbackUrl) {
e.currentTarget.src = fallbackUrl
} else if (fallback) {
setShowFallback(true)
} else {
// Empty svg
e.currentTarget.src =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3C/svg%3E"
}
}}
/>
)}
</PlatformIcon>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export function GridItem({
</div>
<div className="flex items-center gap-1 truncate text-[13px]">
<FeedIcon
fa

Check failure on line 54 in src/renderer/src/modules/entry-column/grid-item-template.tsx

View workflow job for this annotation

GitHub Actions / Lint and Typecheck (18.x)

Type '{ fa: true; className: string; feed: { url: string; checkedAt: string; description?: string | null | undefined; title?: string | null | undefined; id?: string | undefined; image?: string | null | undefined; ... 6 more ...; ownerUserId?: string | ... 1 more ... | undefined; }; entry: { ...; } & { ...; }; size: number...' is not assignable to type 'IntrinsicAttributes & { feed: { url: string; checkedAt: string; description?: string | null | undefined; title?: string | null | undefined; id?: string | undefined; ... 7 more ...; ownerUserId?: string | ... 1 more ... | undefined; }; ... 4 more ...; fallback?: boolean | undefined; }'.

Check failure on line 54 in src/renderer/src/modules/entry-column/grid-item-template.tsx

View workflow job for this annotation

GitHub Actions / release (macos-latest)

Type '{ fa: true; className: string; feed: { url: string; checkedAt: string; description?: string | null | undefined; title?: string | null | undefined; id?: string | undefined; image?: string | null | undefined; ... 6 more ...; ownerUserId?: string | ... 1 more ... | undefined; }; entry: { ...; } & { ...; }; size: number...' is not assignable to type 'IntrinsicAttributes & { feed: { url: string; checkedAt: string; description?: string | null | undefined; title?: string | null | undefined; id?: string | undefined; ... 7 more ...; ownerUserId?: string | ... 1 more ... | undefined; }; ... 4 more ...; fallback?: boolean | undefined; }'.

Check failure on line 54 in src/renderer/src/modules/entry-column/grid-item-template.tsx

View workflow job for this annotation

GitHub Actions / release (ubuntu-latest)

Type '{ fa: true; className: string; feed: { url: string; checkedAt: string; description?: string | null | undefined; title?: string | null | undefined; id?: string | undefined; image?: string | null | undefined; ... 6 more ...; ownerUserId?: string | ... 1 more ... | undefined; }; entry: { ...; } & { ...; }; size: number...' is not assignable to type 'IntrinsicAttributes & { feed: { url: string; checkedAt: string; description?: string | null | undefined; title?: string | null | undefined; id?: string | undefined; ... 7 more ...; ownerUserId?: string | ... 1 more ... | undefined; }; ... 4 more ...; fallback?: boolean | undefined; }'.

Check failure on line 54 in src/renderer/src/modules/entry-column/grid-item-template.tsx

View workflow job for this annotation

GitHub Actions / release (windows-latest)

Type '{ fa: true; className: string; feed: { url: string; checkedAt: string; description?: string | null | undefined; title?: string | null | undefined; id?: string | undefined; image?: string | null | undefined; ... 6 more ...; ownerUserId?: string | ... 1 more ... | undefined; }; entry: { ...; } & { ...; }; size: number...' is not assignable to type 'IntrinsicAttributes & { feed: { url: string; checkedAt: string; description?: string | null | undefined; title?: string | null | undefined; id?: string | undefined; ... 7 more ...; ownerUserId?: string | ... 1 more ... | undefined; }; ... 4 more ...; fallback?: boolean | undefined; }'.
className="mr-0.5 inline-block"
feed={feeds!}
entry={entry.entries}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function ListItem({
"before:absolute before:-left-0.5 before:top-[18px] before:block before:size-2 before:rounded-full before:bg-theme-accent",
)}
>
{!withAudio && <FeedIcon feed={feed} entry={entry.entries} />}
{!withAudio && <FeedIcon feed={feed} fallback entry={entry.entries} />}
<div className="-mt-0.5 line-clamp-4 flex-1 text-sm leading-tight">
<div
className={cn(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const SocialMediaItem: EntryListItemFC = ({
)}
>
<FeedIcon
fallback
className="mask-squircle mask"
feed={feed}
entry={entry.entries}
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/modules/entry-content/read-history.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const EntryUser: Component<{
presentUserProfile(userId)
}}
>
<Avatar className="aspect-square size-8 border border-border ring-1 ring-background">
<Avatar className="aspect-square size-7 border border-border ring-1 ring-background">
<AvatarImage src={user?.image || undefined} />
<AvatarFallback>{user.name?.slice(0, 2)}</AvatarFallback>
</Avatar>
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/src/modules/feed-column/item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const FeedItemImpl = ({
feed.errorAt && "text-red-900 dark:text-red-500",
)}
>
<FeedIcon feed={feed} size={16} />
<FeedIcon fallback feed={feed} size={16} />
<div
className={cn(
"truncate",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export function Component() {
{feed.data?.feed && (
<div className="mx-auto mt-12 flex max-w-5xl flex-col items-center justify-center p-4 lg:p-0">
<FeedIcon
fallback
feed={feed.data.feed}
className="mask-squircle mask shrink-0"
size={64}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export function Component() {
target="_blank"
>
<FeedIcon
fa

Check failure on line 67 in src/renderer/src/pages/(external)/(with-layout)/profile/[id]/index.tsx

View workflow job for this annotation

GitHub Actions / Lint and Typecheck (18.x)

Type '{ fa: true; feed: { description: string | null; title: string | null; id: string; image: string | null; url: string; siteUrl: string | null; checkedAt: string; lastModifiedHeader: string | null; ... 4 more ...; ownerUserId: string | null; }; size: number; className: string; }' is not assignable to type 'IntrinsicAttributes & { feed: { url: string; checkedAt: string; description?: string | null | undefined; title?: string | null | undefined; id?: string | undefined; ... 7 more ...; ownerUserId?: string | ... 1 more ... | undefined; }; ... 4 more ...; fallback?: boolean | undefined; }'.

Check failure on line 67 in src/renderer/src/pages/(external)/(with-layout)/profile/[id]/index.tsx

View workflow job for this annotation

GitHub Actions / release (macos-latest)

Type '{ fa: true; feed: { description: string | null; title: string | null; id: string; image: string | null; url: string; siteUrl: string | null; checkedAt: string; lastModifiedHeader: string | null; ... 4 more ...; ownerUserId: string | null; }; size: number; className: string; }' is not assignable to type 'IntrinsicAttributes & { feed: { url: string; checkedAt: string; description?: string | null | undefined; title?: string | null | undefined; id?: string | undefined; ... 7 more ...; ownerUserId?: string | ... 1 more ... | undefined; }; ... 4 more ...; fallback?: boolean | undefined; }'.

Check failure on line 67 in src/renderer/src/pages/(external)/(with-layout)/profile/[id]/index.tsx

View workflow job for this annotation

GitHub Actions / release (ubuntu-latest)

Type '{ fa: true; feed: { description: string | null; title: string | null; id: string; image: string | null; url: string; siteUrl: string | null; checkedAt: string; lastModifiedHeader: string | null; ... 4 more ...; ownerUserId: string | null; }; size: number; className: string; }' is not assignable to type 'IntrinsicAttributes & { feed: { url: string; checkedAt: string; description?: string | null | undefined; title?: string | null | undefined; id?: string | undefined; ... 7 more ...; ownerUserId?: string | ... 1 more ... | undefined; }; ... 4 more ...; fallback?: boolean | undefined; }'.

Check failure on line 67 in src/renderer/src/pages/(external)/(with-layout)/profile/[id]/index.tsx

View workflow job for this annotation

GitHub Actions / release (windows-latest)

Type '{ fa: true; feed: { description: string | null; title: string | null; id: string; image: string | null; url: string; siteUrl: string | null; checkedAt: string; lastModifiedHeader: string | null; ... 4 more ...; ownerUserId: string | null; }; size: number; className: string; }' is not assignable to type 'IntrinsicAttributes & { feed: { url: string; checkedAt: string; description?: string | null | undefined; title?: string | null | undefined; id?: string | undefined; ... 7 more ...; ownerUserId?: string | ... 1 more ... | undefined; }; ... 4 more ...; fallback?: boolean | undefined; }'.
feed={subscription.feeds}
size={22}
className="mr-3"
Expand Down

0 comments on commit 9628aae

Please sign in to comment.