slack status without the slack
status.zzstoatzz.io
hatk
statusphere
1<script lang="ts">
2 import { page } from '$app/stores'
3 import { callXrpc } from '$hatk/client'
4 import { isCustomEmoji, customEmojiName, bufoImageUrl, bufoFallbackUrl } from '$lib/utils/emoji'
5 import EmojiPicker from './EmojiPicker.svelte'
6
7 let { currentEmoji = '😊', oncreated }: { currentEmoji?: string; oncreated?: () => void } = $props()
8
9 let selectedEmoji = $derived(currentEmoji)
10 let text = $state('')
11 let expiresValue = $state('')
12 let customDatetime = $state('')
13 let showPicker = $state(false)
14 let submitting = $state(false)
15
16 function toLocalDatetimeString(date: Date) {
17 const offset = date.getTimezoneOffset()
18 const local = new Date(date.getTime() - offset * 60 * 1000)
19 return local.toISOString().slice(0, 16)
20 }
21
22 function onExpiresChange() {
23 if (expiresValue === 'custom') {
24 const defaultTime = new Date(Date.now() + 60 * 60 * 1000)
25 customDatetime = toLocalDatetimeString(defaultTime)
26 }
27 }
28
29 async function submit(e: Event) {
30 e.preventDefault()
31 if (!selectedEmoji || !$page.data.viewer) return
32
33 submitting = true
34 try {
35 const record: Record<string, string> = {
36 emoji: selectedEmoji,
37 createdAt: new Date().toISOString(),
38 }
39 if (text.trim()) record.text = text.trim()
40 if (expiresValue === 'custom' && customDatetime) {
41 record.expires = new Date(customDatetime).toISOString()
42 } else if (expiresValue && expiresValue !== 'custom') {
43 record.expires = new Date(Date.now() + parseInt(expiresValue) * 60 * 1000).toISOString()
44 }
45
46 await callXrpc('dev.hatk.createRecord', {
47 collection: 'io.zzstoatzz.status.record',
48 repo: $page.data.viewer.did,
49 record,
50 })
51
52 text = ''
53 expiresValue = ''
54 oncreated?.()
55 } catch (err: any) {
56 alert('Failed to set status: ' + (err?.message ?? err))
57 } finally {
58 submitting = false
59 }
60 }
61</script>
62
63<form class="status-form" onsubmit={submit}>
64 <div class="emoji-input-row">
65 <button type="button" class="emoji-trigger" onclick={() => showPicker = true}>
66 {#if isCustomEmoji(selectedEmoji)}
67 {@const name = customEmojiName(selectedEmoji)}
68 <img src={bufoImageUrl(name)} alt={name} onerror={(e) => { (e.currentTarget as HTMLImageElement).src = bufoFallbackUrl(name) }} />
69 {:else}
70 {selectedEmoji}
71 {/if}
72 </button>
73 <input type="text" placeholder="what's happening?" maxlength="256" bind:value={text} />
74 </div>
75 <div class="form-actions">
76 <select bind:value={expiresValue} onchange={onExpiresChange}>
77 <option value="">don't clear</option>
78 <option value="30">30 min</option>
79 <option value="60">1 hour</option>
80 <option value="120">2 hours</option>
81 <option value="240">4 hours</option>
82 <option value="480">8 hours</option>
83 <option value="1440">1 day</option>
84 <option value="10080">1 week</option>
85 <option value="custom">custom...</option>
86 </select>
87 {#if expiresValue === 'custom'}
88 <input type="datetime-local" class="custom-datetime" bind:value={customDatetime} min={toLocalDatetimeString(new Date())} />
89 {/if}
90 <button type="submit" disabled={submitting}>
91 {submitting ? 'setting...' : 'set status'}
92 </button>
93 </div>
94</form>
95
96{#if showPicker}
97 <EmojiPicker
98 onselect={(emoji) => { selectedEmoji = emoji; showPicker = false }}
99 onclose={() => showPicker = false}
100 />
101{/if}