atagen

malice

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

Blame: web/src/lib/NotificationSettings.svelte

db4e552594cbatagen2026-10-26 04:43 UTC1
<script lang="ts">
db4e552594cbatagen2026-10-26 04:43 UTC2
  import { disableNotifications, enableNotifications, notificationPermission, notificationsEnabled } from './notifications';
db4e552594cbatagen2026-10-26 04:43 UTC3
c8d898e6eb59atagen2026-10-27 03:55 UTC4
  import type { MessageSoundMode } from './message-sound-model';
c8d898e6eb59atagen2026-10-27 03:55 UTC5
  import { messageSound, playMessageSound, saveMessageSound, uploadMessageSound } from './message-sound';
c8d898e6eb59atagen2026-10-27 03:55 UTC6
db4e552594cbatagen2026-10-26 04:43 UTC7
  let busy = false;
c8d898e6eb59atagen2026-10-27 03:55 UTC8
  let soundBusy = false;
c8d898e6eb59atagen2026-10-27 03:55 UTC9
  let soundError = '';
c8d898e6eb59atagen2026-10-27 03:55 UTC10
  let fileInput: HTMLInputElement;
c8d898e6eb59atagen2026-10-27 03:55 UTC11
c8d898e6eb59atagen2026-10-27 03:55 UTC12
  const SOUND_MODES: { id: MessageSoundMode; label: string }[] = [
c8d898e6eb59atagen2026-10-27 03:55 UTC13
    { id: 'all', label: 'All messages' },
c8d898e6eb59atagen2026-10-27 03:55 UTC14
    { id: 'mentions_dms', label: 'Mentions and DMs' },
c8d898e6eb59atagen2026-10-27 03:55 UTC15
    { id: 'off', label: 'Off' }
c8d898e6eb59atagen2026-10-27 03:55 UTC16
  ];
c8d898e6eb59atagen2026-10-27 03:55 UTC17
c8d898e6eb59atagen2026-10-27 03:55 UTC18
  async function guarded(task: () => Promise<void>): Promise<void> {
c8d898e6eb59atagen2026-10-27 03:55 UTC19
    if (soundBusy) return;
c8d898e6eb59atagen2026-10-27 03:55 UTC20
    soundBusy = true;
c8d898e6eb59atagen2026-10-27 03:55 UTC21
    soundError = '';
c8d898e6eb59atagen2026-10-27 03:55 UTC22
    try {
c8d898e6eb59atagen2026-10-27 03:55 UTC23
      await task();
c8d898e6eb59atagen2026-10-27 03:55 UTC24
    } catch (cause) {
c8d898e6eb59atagen2026-10-27 03:55 UTC25
      soundError = cause instanceof Error ? cause.message : 'Could not save the sound settings.';
c8d898e6eb59atagen2026-10-27 03:55 UTC26
    } finally {
c8d898e6eb59atagen2026-10-27 03:55 UTC27
      soundBusy = false;
c8d898e6eb59atagen2026-10-27 03:55 UTC28
    }
c8d898e6eb59atagen2026-10-27 03:55 UTC29
  }
c8d898e6eb59atagen2026-10-27 03:55 UTC30
c8d898e6eb59atagen2026-10-27 03:55 UTC31
  async function upload(event: Event): Promise<void> {
c8d898e6eb59atagen2026-10-27 03:55 UTC32
    const input = event.currentTarget as HTMLInputElement;
c8d898e6eb59atagen2026-10-27 03:55 UTC33
    const file = input.files?.[0];
c8d898e6eb59atagen2026-10-27 03:55 UTC34
    input.value = '';
c8d898e6eb59atagen2026-10-27 03:55 UTC35
    if (file) await guarded(() => uploadMessageSound(file));
c8d898e6eb59atagen2026-10-27 03:55 UTC36
  }
db4e552594cbatagen2026-10-26 04:43 UTC37
db4e552594cbatagen2026-10-26 04:43 UTC38
  $: denied = $notificationPermission === 'denied';
db4e552594cbatagen2026-10-26 04:43 UTC39
  $: unsupported = $notificationPermission === 'unsupported';
db4e552594cbatagen2026-10-26 04:43 UTC40
  $: on = $notificationsEnabled && $notificationPermission === 'granted';
db4e552594cbatagen2026-10-26 04:43 UTC41
db4e552594cbatagen2026-10-26 04:43 UTC42
  async function toggle(): Promise<void> {
db4e552594cbatagen2026-10-26 04:43 UTC43
    if (busy) return;
db4e552594cbatagen2026-10-26 04:43 UTC44
    busy = true;
db4e552594cbatagen2026-10-26 04:43 UTC45
    try {
db4e552594cbatagen2026-10-26 04:43 UTC46
      if (on) disableNotifications();
db4e552594cbatagen2026-10-26 04:43 UTC47
      else await enableNotifications();
db4e552594cbatagen2026-10-26 04:43 UTC48
    } finally {
db4e552594cbatagen2026-10-26 04:43 UTC49
      busy = false;
db4e552594cbatagen2026-10-26 04:43 UTC50
    }
db4e552594cbatagen2026-10-26 04:43 UTC51
  }
db4e552594cbatagen2026-10-26 04:43 UTC52
</script>
db4e552594cbatagen2026-10-26 04:43 UTC53
db4e552594cbatagen2026-10-26 04:43 UTC54
<section aria-label="Notifications">
db4e552594cbatagen2026-10-26 04:43 UTC55
  <div class="sec-head">
db4e552594cbatagen2026-10-26 04:43 UTC56
    <h3 class="sec-h">Notifications</h3>
db4e552594cbatagen2026-10-26 04:43 UTC57
    <span class="state" class:ok={on}>{unsupported ? 'Unavailable' : denied ? 'Blocked' : on ? 'On' : 'Off'}</span>
db4e552594cbatagen2026-10-26 04:43 UTC58
  </div>
db4e552594cbatagen2026-10-26 04:43 UTC59
  <button type="button" class="ghost-btn" disabled={busy || denied || unsupported} aria-pressed={on} on:click={() => void toggle()}>
db4e552594cbatagen2026-10-26 04:43 UTC60
    {on ? 'Turn off' : 'Turn on'}
db4e552594cbatagen2026-10-26 04:43 UTC61
  </button>
c8d898e6eb59atagen2026-10-27 03:55 UTC62
  <div class="sound-modes" role="radiogroup" aria-label="Message sound">
c8d898e6eb59atagen2026-10-27 03:55 UTC63
    {#each SOUND_MODES as option (option.id)}
c8d898e6eb59atagen2026-10-27 03:55 UTC64
      <button
c8d898e6eb59atagen2026-10-27 03:55 UTC65
        type="button"
c8d898e6eb59atagen2026-10-27 03:55 UTC66
        role="radio"
c8d898e6eb59atagen2026-10-27 03:55 UTC67
        aria-checked={$messageSound.mode === option.id}
c8d898e6eb59atagen2026-10-27 03:55 UTC68
        class:active={$messageSound.mode === option.id}
c8d898e6eb59atagen2026-10-27 03:55 UTC69
        disabled={soundBusy}
c8d898e6eb59atagen2026-10-27 03:55 UTC70
        on:click={() => void guarded(() => saveMessageSound({ ...$messageSound, mode: option.id }))}
c8d898e6eb59atagen2026-10-27 03:55 UTC71
      >
c8d898e6eb59atagen2026-10-27 03:55 UTC72
        {option.label}
c8d898e6eb59atagen2026-10-27 03:55 UTC73
      </button>
c8d898e6eb59atagen2026-10-27 03:55 UTC74
    {/each}
c8d898e6eb59atagen2026-10-27 03:55 UTC75
  </div>
c8d898e6eb59atagen2026-10-27 03:55 UTC76
  <div class="sound-actions">
c8d898e6eb59atagen2026-10-27 03:55 UTC77
    <button type="button" class="ghost-btn" on:click={() => void playMessageSound()}>Play</button>
c8d898e6eb59atagen2026-10-27 03:55 UTC78
    <button type="button" class="ghost-btn" disabled={soundBusy} on:click={() => fileInput.click()}>Upload sound</button>
c8d898e6eb59atagen2026-10-27 03:55 UTC79
    <button
c8d898e6eb59atagen2026-10-27 03:55 UTC80
      type="button"
c8d898e6eb59atagen2026-10-27 03:55 UTC81
      class="ghost-btn"
c8d898e6eb59atagen2026-10-27 03:55 UTC82
      disabled={soundBusy || $messageSound.sound === null}
c8d898e6eb59atagen2026-10-27 03:55 UTC83
      on:click={() => void guarded(() => saveMessageSound({ ...$messageSound, sound: null }))}
c8d898e6eb59atagen2026-10-27 03:55 UTC84
    >
c8d898e6eb59atagen2026-10-27 03:55 UTC85
      Reset
c8d898e6eb59atagen2026-10-27 03:55 UTC86
    </button>
c8d898e6eb59atagen2026-10-27 03:55 UTC87
    <input bind:this={fileInput} type="file" accept="audio/*" hidden on:change={(e) => void upload(e)} />
c8d898e6eb59atagen2026-10-27 03:55 UTC88
  </div>
c8d898e6eb59atagen2026-10-27 03:55 UTC89
  {#if soundError}
c8d898e6eb59atagen2026-10-27 03:55 UTC90
    <p class="sound-error" role="alert">{soundError}</p>
c8d898e6eb59atagen2026-10-27 03:55 UTC91
  {/if}
db4e552594cbatagen2026-10-26 04:43 UTC92
</section>
db4e552594cbatagen2026-10-26 04:43 UTC93
db4e552594cbatagen2026-10-26 04:43 UTC94
<style>
db4e552594cbatagen2026-10-26 04:43 UTC95
  section { margin-top: 18px; }
db4e552594cbatagen2026-10-26 04:43 UTC96
  .sec-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
db4e552594cbatagen2026-10-26 04:43 UTC97
  .sec-h { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); margin: 0; }
db4e552594cbatagen2026-10-26 04:43 UTC98
  .state { font-size: 12px; color: var(--muted); }
db4e552594cbatagen2026-10-26 04:43 UTC99
  .state.ok { color: var(--accent); }
c8d898e6eb59atagen2026-10-27 03:55 UTC100
  .sound-modes { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
c8d898e6eb59atagen2026-10-27 03:55 UTC101
  .sound-modes button { border: 1px solid var(--border); background: transparent; color: var(--muted); border-radius: var(--r-sm); padding: 3px 10px; font: inherit; font-size: 12.5px; cursor: pointer; transition: color var(--pop), border-color var(--pop), background var(--pop); }
c8d898e6eb59atagen2026-10-27 03:55 UTC102
  .sound-modes button:hover:not(:disabled) { color: var(--text); }
c8d898e6eb59atagen2026-10-27 03:55 UTC103
  .sound-modes button.active { color: var(--text); border-color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent); }
c8d898e6eb59atagen2026-10-27 03:55 UTC104
  .sound-modes button:disabled { cursor: wait; }
c8d898e6eb59atagen2026-10-27 03:55 UTC105
  .sound-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
c8d898e6eb59atagen2026-10-27 03:55 UTC106
  .sound-error { margin: 8px 0 0; color: var(--danger); font-size: 13px; }
db4e552594cbatagen2026-10-26 04:43 UTC107
</style>