atagen

malice

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