export interface UploadReaction { emoji: string count: number me: boolean names: string[] } export interface UploadReactionEventPayload { uploadId: string emoji: string actorId: number actorName: string active: boolean count: number } export function useUploadReactions() { const map = useState>('reactions:by-upload', () => ({})) const { user } = useAuth() function seed(uploadId: string, reactions: UploadReaction[]) { map.value = { ...map.value, [uploadId]: reactions } } function reactionsFor(uploadId: string): UploadReaction[] { return map.value[uploadId] ?? [] } function applyEvent(e: UploadReactionEventPayload) { const list = (map.value[e.uploadId] ??= []) let entry = list.find(r => r.emoji === e.emoji) if (e.active) { if (!entry) { entry = { emoji: e.emoji, count: 0, me: false, names: [] } list.push(entry) } if (!entry.names.includes(e.actorName)) { entry.names.push(e.actorName) } } else if (entry) { entry.names = entry.names.filter(n => n !== e.actorName) } if (!entry) return entry.count = e.count if (e.actorId === user.value?.id) { entry.me = e.active } if (entry.count <= 0) { list.splice(list.indexOf(entry), 1) } } async function toggle(uploadId: string, emoji: string) { const me = user.value const before = map.value[uploadId]?.find(r => r.emoji === emoji) // Optimistisch umschalten — die Reaktion ist sofort sichtbar, // die Server-Antwort korrigiert nur noch Zähler/Status if (me) { applyEvent({ uploadId, emoji, actorId: me.id, actorName: me.name, active: !before?.me, count: (before?.count ?? 0) + (before?.me ? -1 : 1), }) } try { const res = await $fetch<{ active: boolean, count: number, isSubscribed: boolean }>(`/api/uploads/${uploadId}/reactions`, { method: 'POST', body: { emoji }, }) if (me) { applyEvent({ uploadId, emoji, actorId: me.id, actorName: me.name, active: res.active, count: res.count }) } return res } catch (err) { if (me) { applyEvent({ uploadId, emoji, actorId: me.id, actorName: me.name, active: !!before?.me, count: before?.count ?? 0 }) } throw err } } return { reactionsFor, seed, applyEvent, toggle } }