away-window.svelte RawHistoryFolder
1<script lang="ts">
2 import { onMount } from 'svelte';
3 import { api } from '../../../convex/_generated/api';
4 import XpWindow from './xp-window.svelte';
5 import { desktop } from '$lib/states/desktop.svelte';
6 import { authQuery, useAimClient } from '$lib/aim/client';
7
8 const presets = [
9 'Je suis parti(e) manger',
10 'Au téléphone…',
11 'Parti(e) faire un tour, je reviens',
12 "En cours, j'ai pas le droit d'être là 😅",
13 'Je dors, laisse un message'
14 ];
15 const me = authQuery(api.aim.getMe, () => ({}));
16 const client = useAimClient();
17 let text = $state('');
18 let custom = $state('');
19 let selected = $state('custom');
20 let pending = $state(false);
21 let error = $state('');
22 let initialized = $state(false);
23
24 onMount(() => {
25 custom = localStorage.getItem('pdraim-away-last') ?? '';
26 });
27 $effect(() => {
28 const data = me?.data;
29 if (!initialized && data) {
30 const current = data.awayMessage ?? '';
31 text = current || custom;
32 selected = presets.includes(current) ? current : 'custom';
33 initialized = true;
34 }
35 });
36
37 function selectPreset(event: Event) {
38 selected = (event.currentTarget as HTMLSelectElement).value;
39 text = selected === 'custom' ? custom : selected;
40 }
41 function editText() {
42 selected = 'custom';
43 custom = text;
44 localStorage.setItem('pdraim-away-last', custom);
45 }
46 async function apply(value?: string) {
47 pending = true;
48 error = '';
49 try {
50 await client.setAwayMessage(value);
51 if (value && selected === 'custom') localStorage.setItem('pdraim-away-last', value);
52 desktop.close('away');
53 } catch (err) {
54 error = err instanceof Error ? err.message : 'Impossible de modifier le message.';
55 } finally {
56 pending = false;
57 }
58 }
59</script>
60
61<XpWindow id="away" width={360} height={340} resizableWindow={false}>
62 <div class="content">
63 <label for="away-preset">Messages d’absence enregistrés</label>
64 <!-- Inline list box (like AIM) so the choices always stay inside the window -->
65 <select
66 id="away-preset"
67 class="preset-list"
68 size={presets.length + 1}
69 value={selected}
70 onchange={selectPreset}
71 >
72 {#each presets as preset (preset)}<option value={preset}>{preset}</option>{/each}
73 <option value="custom">Personnalisé…</option>
74 </select>
75 <label for="away-text">Votre message</label>
76 <textarea id="away-text" maxlength="300" bind:value={text} oninput={editText}></textarea>
77 <small>{text.length}/300</small>
78 {#if error}<span role="alert">{error}</span>{/if}
79 <div class="actions">
80 <button disabled={pending || !text.trim()} onclick={() => apply(text)}
81 >Je suis absent(e)</button
82 >
83 <button disabled={pending} onclick={() => apply(undefined)}>Je suis de retour</button>
84 <button onclick={() => desktop.close('away')}>Annuler</button>
85 </div>
86 </div>
87</XpWindow>
88
89<style>
90 .content {
91 display: flex;
92 flex-direction: column;
93 gap: 7px;
94 height: 100%;
95 font:
96 11px Tahoma,
97 'Pixelated MS Sans Serif',
98 sans-serif;
99 }
100 select,
101 textarea {
102 width: 100%;
103 box-sizing: border-box;
104 font: inherit;
105 }
106 .preset-list {
107 height: auto;
108 padding: 1px;
109 background-image: none;
110 border: 1px solid #7f9db9;
111 overflow: hidden;
112 }
113 .preset-list:focus {
114 background-color: #fff;
115 color: inherit;
116 }
117 .preset-list option {
118 padding: 1px 3px;
119 overflow: hidden;
120 text-overflow: ellipsis;
121 white-space: nowrap;
122 }
123 .preset-list option:checked {
124 background: #316ac5;
125 color: #fff;
126 }
127 textarea {
128 flex: 1;
129 min-height: 60px;
130 resize: none;
131 border: 1px solid #7f9db9;
132 }
133 small {
134 align-self: flex-end;
135 }
136 .actions {
137 display: flex;
138 gap: 4px;
139 flex-wrap: wrap;
140 justify-content: flex-end;
141 margin-top: auto;
142 }
143 [role='alert'] {
144 color: #a00;
145 }
146</style>