atagen

malice

public
Star0
Watch0
77eddb6b710ffe2b884b8bdf7b0611041f7358e6
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git
web/src/lib/ImageLightbox.svelteSvelte102 lines3.0 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
    position: fixed;
70
    inset: 0;
71
    z-index: 100;
72
    display: flex;
73
    flex-direction: column;
74
    align-items: center;
75
    justify-content: center;
76
    gap: 12px;
77
    padding: 56px 24px 24px;
78
    background: rgb(0 0 0 / 0.82);
79
    backdrop-filter: blur(4px);
80
  }
81
  .lightbox-bar {
82
    position: absolute;
83
    top: 0;
84
    left: 0;
85
    right: 0;
86
    display: flex;
87
    align-items: center;
88
    gap: 8px;
89
    padding: 10px 16px;
90
    color: #fff;
91
  }
92
  .lightbox-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
93
  .lightbox-bar a { text-decoration: none; }
94
  .lightbox-image {
95
    max-width: 100%;
96
    max-height: 100%;
97
    object-fit: contain;
98
    border-radius: 6px;
99
    box-shadow: 0 12px 40px rgb(0 0 0 / 0.5);
100
    cursor: zoom-out;
101
  }
102
</style>