Give a space with no icon a colored tile with its initials (#428)

This commit is contained in:
Coracle-Bot 2026-09-07 23:09:02 +00:00 committed by hodlbod
parent 3bd821efc1
commit 7b145c525d
7 changed files with 60 additions and 30 deletions

View file

@ -1,6 +1,6 @@
<script lang="ts">
import cx from "classnames"
import {hash, formatTimestampAsTime} from "@welshman/lib"
import {formatTimestampAsTime} from "@welshman/lib"
import type {Maybe} from "@welshman/lib"
import type {TrustedEvent, EventContent} from "@welshman/util"
import {Thunks} from "@welshman/app"
@ -21,7 +21,7 @@
import ChatMessageMenu from "@app/components/ChatMessageMenu.svelte"
import ChatMessageMenuMobile from "@app/components/ChatMessageMenuMobile.svelte"
import {app, profiles, user} from "@app/core"
import {colors} from "@app/theme"
import {colorFor} from "@app/theme"
import {pushModal} from "@app/modal"
interface Props {
@ -40,7 +40,7 @@
const profileDisplay = $profiles.display(event.pubkey).$
const thunks = $app.use(Thunks).history
const thunk = $app.use(Thunks).merge($thunks.filter(t => t.event.id === event.id))
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
const colorValue = colorFor(event.pubkey)
const reply = () => replyTo(event)
const edit = canEdit?.(event) ? () => onEdit?.(event) : undefined

View file

@ -1,7 +1,7 @@
<script lang="ts">
import Server from "@assets/icons/server.svg?dataurl"
import ImageIcon from "@lib/components/ImageIcon.svelte"
import {relays} from "@app/core"
import {relayIconColor, relayIconInitials} from "@app/relays"
type Props = {
url: string
@ -12,10 +12,20 @@
const {url, size = 7, ...props}: Props = $props()
const relay = $relays.one(url)
const rem = $derived(size * 0.25)
const fallbackStyle = $derived(
`width: ${rem}rem; height: ${rem}rem; min-width: ${rem}rem; min-height: ${rem}rem; font-size: ${rem * 0.4}rem; background-color: ${relayIconColor(url)}; color: white`,
)
</script>
{#if $relay?.icon}
<ImageIcon {size} alt="" src={$relay?.icon} class="rounded-full {props.class}" />
{:else}
<ImageIcon size={size - 2} alt="" src={Server} class={props.class} />
<div
class="flex select-none items-center justify-center rounded-full font-bold {props.class}"
style={fallbackStyle}>
{relayIconInitials(url)}
</div>
{/if}

View file

@ -26,7 +26,7 @@
<div class="relative">
<div
class="flex justify-center items-center h-12 w-12 min-w-12 rounded-full border-2 border-solid bg-surface-more border-line">
<RelayIcon {url} size={10} class="rounded-full" />
<RelayIcon {url} size={10} />
</div>
</div>
{#if !hideFavorites && $userSpaceUrls.includes(url)}

View file

@ -2,7 +2,6 @@
import cx from "classnames"
import {readable} from "svelte/store"
import {
hash,
gte,
now,
uniq,
@ -40,7 +39,7 @@
import RoomItemContent from "@app/components/RoomItemContent.svelte"
import {profiles, thunks, user} from "@app/core"
import {noThunks, thunksByEventId} from "@app/thunks"
import {colors} from "@app/theme"
import {colorFor} from "@app/theme"
import {ENABLE_ZAPS} from "@app/env"
import type {FeedContext} from "@app/feeds"
import {deriveEvent, deriveEventsForUrl} from "@app/repository"
@ -71,7 +70,7 @@
const today = formatTimestampAsDate(now())
const profileDisplay = $profiles.display(event.pubkey, [url]).$
const thunk = $derived($thunks.merge($thunksByEventId.get(event.id) ?? noThunks))
const [_, colorValue] = colors[hash(event.pubkey) % colors.length]
const colorValue = colorFor(event.pubkey)
const qTag = matchTag(tagSpec("q"), event.tags)
const isQuoteOnly = Boolean(

View file

@ -6,6 +6,7 @@ import {throttled} from "@welshman/store"
import {checkRelayHasLivekit} from "$lib/livekit"
import {app} from "@app/core"
import {relaysMostlyRestricted} from "@app/policies"
import {colorFor} from "@app/theme"
export const encodeRelay = (url: string) =>
encodeURIComponent(
@ -16,6 +17,23 @@ export const encodeRelay = (url: string) =>
export const decodeRelay = (url: string) => normalizeRelayUrl(decodeURIComponent(url))
const relayIconHost = (url: string) =>
normalizeRelayUrl(url)
.replace(/^wss?:\/\//, "")
.replace(/[:/].*$/, "")
// Darkened so white initials clear 4.5:1 on every color in the palette.
export const relayIconColor = (url: string) =>
`color-mix(in oklab, ${colorFor(relayIconHost(url))} 80%, black)`
export const relayIconInitials = (url: string) => {
const parts = relayIconHost(url).split(".")
const hasSubdomain = parts.length > 2
const [first = "", second = ""] = parts
return (hasSubdomain ? first.slice(0, 1) + second.slice(0, 1) : first.slice(0, 2)).toUpperCase()
}
export const deriveSocket = (url: string) => {
const socket = app.get().pool.get(url)

View file

@ -1,31 +1,34 @@
import twColors from "tailwindcss/colors"
import {derived, readable} from "svelte/store"
import {hash} from "@welshman/lib"
import {synced} from "@welshman/store"
import {FL_THEME} from "@app/env"
import {kv} from "@app/storage"
export const colors = [
["amber", twColors.amber[600]],
["blue", twColors.blue[600]],
["cyan", twColors.cyan[600]],
["emerald", twColors.emerald[600]],
["fuchsia", twColors.fuchsia[600]],
["green", twColors.green[600]],
["indigo", twColors.indigo[600]],
["sky", twColors.sky[600]],
["lime", twColors.lime[600]],
["orange", twColors.orange[600]],
["pink", twColors.pink[600]],
["purple", twColors.purple[600]],
["red", twColors.red[600]],
["rose", twColors.rose[600]],
["sky", twColors.sky[600]],
["teal", twColors.teal[600]],
["violet", twColors.violet[600]],
["yellow", twColors.yellow[600]],
["zinc", twColors.zinc[600]],
const colors = [
twColors.amber[600],
twColors.blue[600],
twColors.cyan[600],
twColors.emerald[600],
twColors.fuchsia[600],
twColors.green[600],
twColors.indigo[600],
twColors.sky[600],
twColors.lime[600],
twColors.orange[600],
twColors.pink[600],
twColors.purple[600],
twColors.red[600],
twColors.rose[600],
twColors.sky[600],
twColors.teal[600],
twColors.violet[600],
twColors.yellow[600],
twColors.zinc[600],
]
export const colorFor = (value: string) => colors[hash(value) % colors.length]
// Every theme with token values in lib/components/theme.css
export const flThemes = ["clay", "flat", "navy"]

View file

@ -31,7 +31,7 @@
<div class="card flex flex-col gap-4">
<div class="relative flex gap-4">
<div class="relative">
<RelayIcon {url} size={14} class="rounded-full" />
<RelayIcon {url} size={14} />
</div>
<div class="flex min-w-0 flex-col">
<h1 class="truncate min-w-0 whitespace-nowrap">