atagen

malice

public
Star0
Watch0
294426c7f88fd4348a61b8a8590233237a1e52c0
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git
web/src/lib/MxcAvatar.svelteSvelte68 lines2.0 KB
1
<script lang="ts">
2
  import { mediaUrl } from './api';
3
  import { initialsOf, senderHue } from './format';
4
  import EmojiText from './EmojiText.svelte';
5
6
  export let mxc: string | null;
7
  export let label: string;
8
  export let size = 34;
9
  /** 'circle' for people, 'rounded' for rooms/spaces */
10
  export let shape: 'circle' | 'rounded' = 'circle';
11
  export let seed: string | null = null;
12
  export let eager = false;
13
14
  const density = typeof window === 'undefined' ? 1 : Math.min(window.devicePixelRatio || 1, 3);
15
  // rounding up to a 32px step keeps the number of cached variants per avatar small
16
  $: src = mediaUrl(mxc, Math.ceil((size * density) / 32) * 32);
17
18
  let failed = false;
19
  $: if (mxc) failed = false;
20
  $: px = `${size}px`;
21
  $: compact = size < 24;
22
  $: fontPx = `${compact ? Math.round(size * 0.56) : Math.max(10, Math.round(size * 0.36))}px`;
23
  $: initials = initialsOf(label || '#');
24
  $: shown = compact ? ([...new Intl.Segmenter().segment(initials)][0]?.segment ?? '#') : initials;
25
  $: hue = senderHue(seed ?? label ?? '#');
26
</script>
27
28
{#if src && !failed}
29
  <img
30
    {src}
31
    alt=""
32
    aria-hidden="true"
33
    width={size}
34
    height={size}
35
    loading={eager ? 'eager' : 'lazy'}
36
    decoding="async"
37
    class="mxc-avatar"
38
    class:circle={shape === 'circle'}
39
    style={`width:${px};height:${px};`}
40
    on:error={() => (failed = true)}
41
  />
42
{:else}
43
  <span class="mxc-fallback" class:circle={shape === 'circle'} aria-hidden="true" style={`width:${px};height:${px};font-size:${fontPx};background:hsl(${hue} 32% 30%);`}>
44
    <span><EmojiText text={shown} /></span>
45
  </span>
46
{/if}
47
48
<style>
49
  .mxc-avatar {
50
    flex: none;
51
    object-fit: cover;
52
    border-radius: var(--r-md);
53
    background: var(--bg-soft);
54
  }
55
  .mxc-avatar.circle { border-radius: 50%; }
56
  .mxc-fallback {
57
    flex: none;
58
    display: inline-grid;
59
    place-items: center;
60
    border-radius: var(--r-md);
61
    color: #fff;
62
    font-weight: 700;
63
    letter-spacing: 0.02em;
64
    line-height: 1;
65
    overflow: hidden;
66
  }
67
  .mxc-fallback.circle { border-radius: 50%; }
68
</style>