atagen

malice

public
Star0
Watch0
dfd1feaae855133a79f75d35ea0aa7f5eeaf24ac
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git
web/src/lib/ImageLightbox.svelteSvelte103 lines3.1 KB
1
<script lang="ts">
2
  import { tick } from 'svelte';
3
  import { lightbox } from './lightbox';
4
5
  let closeBtn: HTMLButtonElement | null = null;
6
  let copied = false;
7
  let copiedTimer: number | null = null;
8
9
  async function copyMxc(mxc: string): Promise<void> {
10
    try {
11
      await navigator.clipboard.writeText(mxc);
12
    } catch {
13
      return;
14
    }
15
    copied = true;
16
    if (copiedTimer !== null) window.clearTimeout(copiedTimer);
17
    copiedTimer = window.setTimeout(() => (copied = false), 1500);
18
  }
19
  let returnFocus: HTMLElement | null = null;
20
21
  $: if ($lightbox) void focusClose();
22
23
  async function focusClose(): Promise<void> {
24
    if (!returnFocus) returnFocus = document.activeElement as HTMLElement | null;
25
    await tick();
26
    closeBtn?.focus();
27
  }
28
29
  function close(): void {
30
    lightbox.set(null);
31
    returnFocus?.focus();
32
    returnFocus = null;
33
  }
34
35
  function onKey(ev: KeyboardEvent): void {
36
    if ($lightbox && ev.key === 'Escape') {
37
      ev.stopPropagation();
38
      close();
39
    }
40
  }
41
</script>
42
43
<svelte:window on:keydown|capture={onKey} />
44
45
{#if $lightbox}
46
  <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
47
  <div class="lightbox" role="dialog" tabindex="-1" aria-modal="true" aria-label={$lightbox.filename} on:click|self={close}>
48
    <div class="lightbox-bar">
49
      <span class="lightbox-name">{$lightbox.filename}</span>
50
      {#if $lightbox.mxc}
51
        {@const mxc = $lightbox.mxc}
52
        <button type="button" class="ghost-btn" title={mxc} on:click={() => void copyMxc(mxc)}>
53
          {copied ? 'Copied' : 'Copy mxc'}
54
        </button>
55
      {/if}
56
      <a class="ghost-btn" href={$lightbox.src} target="_blank" rel="noopener noreferrer">Open original</a>
57
      <a class="ghost-btn" href={$lightbox.src} download={$lightbox.filename}>Download</a>
58
      <button type="button" class="icon-btn" aria-label="Close image" title="Close (Esc)" bind:this={closeBtn} on:click={close}>
59
        <span aria-hidden="true">✕</span>
60
      </button>
61
    </div>
62
    <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
63
    <img class="lightbox-image" src={$lightbox.src} alt={$lightbox.alt} on:click={close} />
64
  </div>
65
{/if}
66
67
<style>
68
  .lightbox {
69
    animation: fade-in var(--pop);
70
    position: fixed;
71
    inset: 0;
72
    z-index: 100;
73
    display: flex;
74
    flex-direction: column;
75
    align-items: center;
76
    justify-content: center;
77
    gap: 12px;
78
    padding: 56px 24px 24px;
79
    background: rgb(0 0 0 / 0.82);
80
    backdrop-filter: blur(4px);
81
  }
82
  .lightbox-bar {
83
    position: absolute;
84
    top: 0;
85
    left: 0;
86
    right: 0;
87
    display: flex;
88
    align-items: center;
89
    gap: 8px;
90
    padding: 10px 16px;
91
    color: #fff;
92
  }
93
  .lightbox-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
94
  .lightbox-bar a { text-decoration: none; }
95
  .lightbox-image {
96
    max-width: 100%;
97
    max-height: 100%;
98
    object-fit: contain;
99
    border-radius: var(--r-sm);
100
    box-shadow: 0 12px 40px rgb(0 0 0 / 0.5);
101
    cursor: zoom-out;
102
  }
103
</style>