atagen

malice

public
Star0
Watch0
d2a34d0f48616d4e9f590c082ba9f3f73091e402
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: web/src/lib/ImageLightbox.svelte

cf23dbf4e77catagen2026-10-23 06:36 UTC1
<script lang="ts">
cf23dbf4e77catagen2026-10-23 06:36 UTC2
  import { tick } from 'svelte';
cf23dbf4e77catagen2026-10-23 06:36 UTC3
  import { lightbox } from './lightbox';
445bf199a9cfatagen2026-10-25 02:56 UTC4
  import EmojiText from './EmojiText.svelte';
cf23dbf4e77catagen2026-10-23 06:36 UTC5
cf23dbf4e77catagen2026-10-23 06:36 UTC6
  let closeBtn: HTMLButtonElement | null = null;
7cca1d5628d1atagen2026-10-24 07:36 UTC7
  let copied = false;
7cca1d5628d1atagen2026-10-24 07:36 UTC8
  let copiedTimer: number | null = null;
7cca1d5628d1atagen2026-10-24 07:36 UTC9
7cca1d5628d1atagen2026-10-24 07:36 UTC10
  async function copyMxc(mxc: string): Promise<void> {
7cca1d5628d1atagen2026-10-24 07:36 UTC11
    try {
7cca1d5628d1atagen2026-10-24 07:36 UTC12
      await navigator.clipboard.writeText(mxc);
7cca1d5628d1atagen2026-10-24 07:36 UTC13
    } catch {
7cca1d5628d1atagen2026-10-24 07:36 UTC14
      return;
7cca1d5628d1atagen2026-10-24 07:36 UTC15
    }
7cca1d5628d1atagen2026-10-24 07:36 UTC16
    copied = true;
7cca1d5628d1atagen2026-10-24 07:36 UTC17
    if (copiedTimer !== null) window.clearTimeout(copiedTimer);
7cca1d5628d1atagen2026-10-24 07:36 UTC18
    copiedTimer = window.setTimeout(() => (copied = false), 1500);
7cca1d5628d1atagen2026-10-24 07:36 UTC19
  }
cf23dbf4e77catagen2026-10-23 06:36 UTC20
  let returnFocus: HTMLElement | null = null;
cf23dbf4e77catagen2026-10-23 06:36 UTC21
cf23dbf4e77catagen2026-10-23 06:36 UTC22
  $: if ($lightbox) void focusClose();
cf23dbf4e77catagen2026-10-23 06:36 UTC23
cf23dbf4e77catagen2026-10-23 06:36 UTC24
  async function focusClose(): Promise<void> {
cf23dbf4e77catagen2026-10-23 06:36 UTC25
    if (!returnFocus) returnFocus = document.activeElement as HTMLElement | null;
cf23dbf4e77catagen2026-10-23 06:36 UTC26
    await tick();
cf23dbf4e77catagen2026-10-23 06:36 UTC27
    closeBtn?.focus();
cf23dbf4e77catagen2026-10-23 06:36 UTC28
  }
cf23dbf4e77catagen2026-10-23 06:36 UTC29
cf23dbf4e77catagen2026-10-23 06:36 UTC30
  function close(): void {
cf23dbf4e77catagen2026-10-23 06:36 UTC31
    lightbox.set(null);
cf23dbf4e77catagen2026-10-23 06:36 UTC32
    returnFocus?.focus();
cf23dbf4e77catagen2026-10-23 06:36 UTC33
    returnFocus = null;
cf23dbf4e77catagen2026-10-23 06:36 UTC34
  }
cf23dbf4e77catagen2026-10-23 06:36 UTC35
cf23dbf4e77catagen2026-10-23 06:36 UTC36
  function onKey(ev: KeyboardEvent): void {
cf23dbf4e77catagen2026-10-23 06:36 UTC37
    if ($lightbox && ev.key === 'Escape') {
cf23dbf4e77catagen2026-10-23 06:36 UTC38
      ev.stopPropagation();
cf23dbf4e77catagen2026-10-23 06:36 UTC39
      close();
cf23dbf4e77catagen2026-10-23 06:36 UTC40
    }
cf23dbf4e77catagen2026-10-23 06:36 UTC41
  }
cf23dbf4e77catagen2026-10-23 06:36 UTC42
</script>
cf23dbf4e77catagen2026-10-23 06:36 UTC43
cf23dbf4e77catagen2026-10-23 06:36 UTC44
<svelte:window on:keydown|capture={onKey} />
cf23dbf4e77catagen2026-10-23 06:36 UTC45
cf23dbf4e77catagen2026-10-23 06:36 UTC46
{#if $lightbox}
cf23dbf4e77catagen2026-10-23 06:36 UTC47
  <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
f12c7de2a3abatagen2026-10-23 08:10 UTC48
  <div class="lightbox" role="dialog" tabindex="-1" aria-modal="true" aria-label={$lightbox.filename} on:click|self={close}>
cf23dbf4e77catagen2026-10-23 06:36 UTC49
    <div class="lightbox-bar">
445bf199a9cfatagen2026-10-25 02:56 UTC50
      <span class="lightbox-name"><EmojiText text={$lightbox.filename} /></span>
01351a71d24aatagen2026-10-24 12:16 UTC51
      <button
01351a71d24aatagen2026-10-24 12:16 UTC52
        type="button"
01351a71d24aatagen2026-10-24 12:16 UTC53
        class="ghost-btn"
01351a71d24aatagen2026-10-24 12:16 UTC54
        aria-label="Copy MXC URI"
01351a71d24aatagen2026-10-24 12:16 UTC55
        title={$lightbox.mxc ?? 'MXC URI unavailable for this attachment'}
01351a71d24aatagen2026-10-24 12:16 UTC56
        disabled={!$lightbox.mxc}
01351a71d24aatagen2026-10-24 12:16 UTC57
        on:click={() => $lightbox.mxc && void copyMxc($lightbox.mxc)}
01351a71d24aatagen2026-10-24 12:16 UTC58
      >
01351a71d24aatagen2026-10-24 12:16 UTC59
        {copied ? 'Copied' : 'Copy MXC'}
01351a71d24aatagen2026-10-24 12:16 UTC60
      </button>
cf23dbf4e77catagen2026-10-23 06:36 UTC61
      <a class="ghost-btn" href={$lightbox.src} target="_blank" rel="noopener noreferrer">Open original</a>
cf23dbf4e77catagen2026-10-23 06:36 UTC62
      <a class="ghost-btn" href={$lightbox.src} download={$lightbox.filename}>Download</a>
cf23dbf4e77catagen2026-10-23 06:36 UTC63
      <button type="button" class="icon-btn" aria-label="Close image" title="Close (Esc)" bind:this={closeBtn} on:click={close}>
cf23dbf4e77catagen2026-10-23 06:36 UTC64
        <span aria-hidden="true">✕</span>
cf23dbf4e77catagen2026-10-23 06:36 UTC65
      </button>
cf23dbf4e77catagen2026-10-23 06:36 UTC66
    </div>
cf23dbf4e77catagen2026-10-23 06:36 UTC67
    <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
cf23dbf4e77catagen2026-10-23 06:36 UTC68
    <img class="lightbox-image" src={$lightbox.src} alt={$lightbox.alt} on:click={close} />
cf23dbf4e77catagen2026-10-23 06:36 UTC69
  </div>
cf23dbf4e77catagen2026-10-23 06:36 UTC70
{/if}
cf23dbf4e77catagen2026-10-23 06:36 UTC71
cf23dbf4e77catagen2026-10-23 06:36 UTC72
<style>
cf23dbf4e77catagen2026-10-23 06:36 UTC73
  .lightbox {
05a48e9e7942atagen2026-10-24 09:32 UTC74
    animation: fade-in var(--pop);
cf23dbf4e77catagen2026-10-23 06:36 UTC75
    position: fixed;
cf23dbf4e77catagen2026-10-23 06:36 UTC76
    inset: 0;
cf23dbf4e77catagen2026-10-23 06:36 UTC77
    z-index: 100;
cf23dbf4e77catagen2026-10-23 06:36 UTC78
    display: flex;
cf23dbf4e77catagen2026-10-23 06:36 UTC79
    flex-direction: column;
cf23dbf4e77catagen2026-10-23 06:36 UTC80
    align-items: center;
cf23dbf4e77catagen2026-10-23 06:36 UTC81
    justify-content: center;
cf23dbf4e77catagen2026-10-23 06:36 UTC82
    gap: 12px;
cf23dbf4e77catagen2026-10-23 06:36 UTC83
    padding: 56px 24px 24px;
cf23dbf4e77catagen2026-10-23 06:36 UTC84
    background: rgb(0 0 0 / 0.82);
cf23dbf4e77catagen2026-10-23 06:36 UTC85
    backdrop-filter: blur(4px);
cf23dbf4e77catagen2026-10-23 06:36 UTC86
  }
cf23dbf4e77catagen2026-10-23 06:36 UTC87
  .lightbox-bar {
cf23dbf4e77catagen2026-10-23 06:36 UTC88
    position: absolute;
cf23dbf4e77catagen2026-10-23 06:36 UTC89
    top: 0;
cf23dbf4e77catagen2026-10-23 06:36 UTC90
    left: 0;
cf23dbf4e77catagen2026-10-23 06:36 UTC91
    right: 0;
cf23dbf4e77catagen2026-10-23 06:36 UTC92
    display: flex;
cf23dbf4e77catagen2026-10-23 06:36 UTC93
    align-items: center;
cf23dbf4e77catagen2026-10-23 06:36 UTC94
    gap: 8px;
cf23dbf4e77catagen2026-10-23 06:36 UTC95
    padding: 10px 16px;
cf23dbf4e77catagen2026-10-23 06:36 UTC96
    color: #fff;
cf23dbf4e77catagen2026-10-23 06:36 UTC97
  }
cf23dbf4e77catagen2026-10-23 06:36 UTC98
  .lightbox-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
cf23dbf4e77catagen2026-10-23 06:36 UTC99
  .lightbox-bar a { text-decoration: none; }
cf23dbf4e77catagen2026-10-23 06:36 UTC100
  .lightbox-image {
cf23dbf4e77catagen2026-10-23 06:36 UTC101
    max-width: 100%;
cf23dbf4e77catagen2026-10-23 06:36 UTC102
    max-height: 100%;
cf23dbf4e77catagen2026-10-23 06:36 UTC103
    object-fit: contain;
05a48e9e7942atagen2026-10-24 09:32 UTC104
    border-radius: var(--r-sm);
cf23dbf4e77catagen2026-10-23 06:36 UTC105
    box-shadow: 0 12px 40px rgb(0 0 0 / 0.5);
cf23dbf4e77catagen2026-10-23 06:36 UTC106
    cursor: zoom-out;
cf23dbf4e77catagen2026-10-23 06:36 UTC107
  }
cf23dbf4e77catagen2026-10-23 06:36 UTC108
</style>