atagen

malice

public
Star0
Watch0
e822c659890f1425e3506816b11a3f37e9cccbcd
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git
web/src/lib/SettingsPanel.svelteSvelte160 lines5.9 KB
1
<script lang="ts">
2
  import { createEventDispatcher } from 'svelte';
3
  import {
4
    LINE_HEIGHT_DEFAULT,
5
    LINE_HEIGHT_MAX,
6
    LINE_HEIGHT_MIN,
7
    MESSAGE_SPACING_DEFAULT,
8
    MESSAGE_SPACING_MAX,
9
    MESSAGE_SPACING_MIN,
10
    THEMES,
11
    accent,
12
    lineHeight,
13
    messageSpacing,
14
    theme
15
  } from './appearance';
16
  import { DEFAULT_QUICK_REACTIONS } from './quick-reactions';
17
  import ReactionPicker from './ReactionPicker.svelte';
18
  import type { ImagePack, QuickReaction } from './types';
19
20
  export let quick: QuickReaction[];
21
  export let packs: ImagePack[] = [];
22
  export let onQuickChange: (list: QuickReaction[]) => void;
23
24
  const dispatch = createEventDispatcher<{ close: void; packs: void }>();
25
26
  $: quickIsDefault =
27
    quick.length === DEFAULT_QUICK_REACTIONS.length && quick.every((r, i) => r.key === DEFAULT_QUICK_REACTIONS[i].key);
28
29
  function themeAccent(): string {
30
    return getComputedStyle(document.documentElement).getPropertyValue('--accent').trim() || '#5865f2';
31
  }
32
</script>
33
34
<div class="modal settings" role="dialog" aria-modal="true" aria-label="Settings">
35
  <div class="modal-tabs" role="tablist" aria-label="Settings">
36
    <button type="button" role="tab" aria-selected="true" class="active">Settings</button>
37
    <button type="button" class="icon-btn modal-x" aria-label="Close settings" on:click={() => dispatch('close')}>
38
      <span aria-hidden="true">✕</span>
39
    </button>
40
  </div>
41
42
  <section aria-label="Theme">
43
    <h3 class="sec-h">Theme</h3>
44
    <div class="themes" role="radiogroup" aria-label="Theme">
45
      {#each THEMES as t (t.id)}
46
        <button
47
          type="button"
48
          role="radio"
49
          aria-checked={$theme === t.id}
50
          class="theme-opt"
51
          class:active={$theme === t.id}
52
          data-swatch={t.id}
53
          on:click={() => theme.set(t.id)}
54
        >
55
          <span class="swatch" aria-hidden="true"></span>
56
          {t.label}
57
        </button>
58
      {/each}
59
    </div>
60
    <div class="range accent-row">
61
      <label for="accent-colour">Accent colour</label>
62
      <input id="accent-colour" type="color" value={$accent ?? themeAccent()} on:input={(e) => accent.set(e.currentTarget.value)} />
63
      <button type="button" class="ghost-btn" disabled={$accent === null} on:click={() => accent.set(null)}>Use theme colour</button>
64
    </div>
65
  </section>
66
67
  <section aria-label="Message spacing">
68
    <h3 class="sec-h">Space between messages</h3>
69
    <div class="range">
70
      <input
71
        type="range"
72
        min={MESSAGE_SPACING_MIN}
73
        max={MESSAGE_SPACING_MAX}
74
        step="1"
75
        bind:value={$messageSpacing}
76
        aria-label="Space between messages in pixels"
77
      />
78
      <span class="value">{$messageSpacing}px</span>
79
      <button
80
        type="button"
81
        class="ghost-btn"
82
        disabled={$messageSpacing === MESSAGE_SPACING_DEFAULT}
83
        on:click={() => messageSpacing.set(MESSAGE_SPACING_DEFAULT)}
84
      >
85
        Reset
86
      </button>
87
    </div>
88
    <h3 class="sec-h">Line height</h3>
89
    <div class="range">
90
      <input
91
        type="range"
92
        min={LINE_HEIGHT_MIN}
93
        max={LINE_HEIGHT_MAX}
94
        step="0.05"
95
        bind:value={$lineHeight}
96
        aria-label="Line height within messages"
97
      />
98
      <span class="value">{$lineHeight.toFixed(2)}</span>
99
      <button
100
        type="button"
101
        class="ghost-btn"
102
        disabled={$lineHeight === LINE_HEIGHT_DEFAULT}
103
        on:click={() => lineHeight.set(LINE_HEIGHT_DEFAULT)}
104
      >
105
        Reset
106
      </button>
107
    </div>
108
  </section>
109
110
  <section aria-label="Quick reactions">
111
    <div class="sec-head">
112
      <h3 class="sec-h">Quick reactions</h3>
113
      <button type="button" class="ghost-btn" disabled={quickIsDefault} on:click={() => onQuickChange(DEFAULT_QUICK_REACTIONS)}>
114
        Reset
115
      </button>
116
    </div>
117
    <ReactionPicker embedded {quick} {packs} {onQuickChange} onPick={() => {}} onClose={() => dispatch('close')} />
118
  </section>
119
120
  <section aria-label="Packs">
121
    <div class="sec-head">
122
      <h3 class="sec-h">Sticker and emoji packs</h3>
123
      <button type="button" class="ghost-btn" on:click={() => dispatch('packs')}>Manage</button>
124
    </div>
125
  </section>
126
</div>
127
128
<style>
129
  .settings { max-height: calc(100dvh - 32px); overflow-y: auto; }
130
  section + section { margin-top: 18px; }
131
  .sec-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
132
  .sec-head .sec-h { margin: 0; }
133
  .sec-h { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); margin: 0 0 6px; }
134
  .range + .sec-h { margin-top: 12px; }
135
  .range { display: flex; align-items: center; gap: 10px; }
136
  .range input[type='range'] { flex: 1; accent-color: var(--accent); }
137
  .value { font-size: 13px; min-width: 4ch; text-align: right; font-variant-numeric: tabular-nums; }
138
  .themes { display: grid; grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); gap: 6px; }
139
  .theme-opt {
140
    display: flex; align-items: center; gap: 8px;
141
    padding: 6px 8px;
142
    background: var(--bg-soft);
143
    border: 1px solid var(--border);
144
    border-radius: var(--r-md);
145
    color: var(--text);
146
    font-size: 13px;
147
    cursor: pointer;
148
    text-align: left;
149
  }
150
  .theme-opt.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
151
  .swatch { flex: none; width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--border); }
152
  [data-swatch='system'] .swatch { background: linear-gradient(135deg, #14151c 50%, #f7f7fa 50%); }
153
  [data-swatch='dark'] .swatch { background: #14151c; }
154
  [data-swatch='light'] .swatch { background: #f7f7fa; }
155
  [data-swatch='black'] .swatch { background: #000; }
156
  [data-swatch='nord'] .swatch { background: #2e3440; }
157
  .accent-row { margin-top: 10px; font-size: 13px; }
158
  .accent-row label { flex: 1; }
159
  .accent-row input[type='color'] { width: 36px; height: 26px; padding: 0; border: 1px solid var(--border); border-radius: var(--r-sm); background: none; cursor: pointer; }
160
</style>