mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-19 18:13:41 -05:00
169 lines
5.9 KiB
TypeScript
169 lines
5.9 KiB
TypeScript
// gomuks - A Matrix client written in Go.
|
|
// Copyright (C) 2024 Tulir Asokan
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
import { useEffect, useMemo, useReducer, useState, useSyncExternalStore } from "react"
|
|
import Client from "@/api/client.ts"
|
|
import type { CustomEmojiPack } from "@/util/emoji"
|
|
import type {
|
|
EventID,
|
|
EventType,
|
|
MemDBEvent,
|
|
MemReceipt,
|
|
MemberEventContent,
|
|
UnknownEventContent,
|
|
UserID,
|
|
} from "../types"
|
|
import { Preferences, preferences } from "../types/preferences"
|
|
import type { StateStore } from "./main.ts"
|
|
import type { AutocompleteMemberEntry, RoomStateStore } from "./room.ts"
|
|
|
|
export function useRoomTimeline(room: RoomStateStore): (MemDBEvent | null)[] {
|
|
return useSyncExternalStore(
|
|
room.timelineSub.subscribe,
|
|
() => room.timelineCache,
|
|
)
|
|
}
|
|
|
|
export function useRoomTyping(room: RoomStateStore): string[] {
|
|
return useSyncExternalStore(room.typingSub.subscribe, () => room.typing)
|
|
}
|
|
|
|
export function useReadReceipts(room: RoomStateStore, evtID: EventID): MemReceipt[] {
|
|
return useSyncExternalStore(
|
|
room.receiptSubs.getSubscriber(evtID),
|
|
() => room.receiptsByEventID.get(evtID) ?? emptyArray,
|
|
)
|
|
}
|
|
|
|
export function useRoomState(
|
|
room?: RoomStateStore, type?: EventType, stateKey: string | undefined = "",
|
|
): MemDBEvent | null {
|
|
const isNoop = !room || !type || stateKey === undefined
|
|
return useSyncExternalStore(
|
|
isNoop ? noopSubscribe : room.stateSubs.getSubscriber(room.stateSubKey(type, stateKey)),
|
|
isNoop ? returnNull : (() => room.getStateEvent(type, stateKey) ?? null),
|
|
)
|
|
}
|
|
|
|
export function useRoomMember(
|
|
client: Client | undefined | null, room: RoomStateStore | undefined, userID: UserID,
|
|
): MemDBEvent | null {
|
|
const evt = useRoomState(room, "m.room.member", userID)
|
|
if (!evt && client && room) {
|
|
client.requestMemberEvent(room, userID)
|
|
}
|
|
return evt
|
|
}
|
|
|
|
export function useMultipleRoomMembers(
|
|
client: Client, room: RoomStateStore, userIDs: UserID[],
|
|
): [UserID, MemberEventContent | null][] {
|
|
const [, forceUpdate] = useReducer(x => x + 1, 0)
|
|
let promiseAwaited = false
|
|
return userIDs.map(userID => {
|
|
const evt = room.getStateEvent("m.room.member", userID)
|
|
if (!evt) {
|
|
const promise = client.requestMemberEvent(room, userID)
|
|
if (promise && !promiseAwaited) {
|
|
promiseAwaited = true
|
|
promise.then(forceUpdate)
|
|
}
|
|
}
|
|
const member = (evt?.content ?? null) as MemberEventContent | null
|
|
return [userID, member]
|
|
})
|
|
}
|
|
|
|
export function useRoomMembers(room?: RoomStateStore): AutocompleteMemberEntry[] {
|
|
return useSyncExternalStore(
|
|
room ? room.stateSubs.getSubscriber("m.room.member") : noopSubscribe,
|
|
room ? room.getMembers : returnEmptyArray,
|
|
)
|
|
}
|
|
|
|
const noopSubscribe = () => () => {}
|
|
const returnNull = () => null
|
|
const emptyArray: never[] = []
|
|
const returnEmptyArray = () => emptyArray
|
|
|
|
export function useRoomEvent(room: RoomStateStore, eventID: EventID | null): MemDBEvent | null {
|
|
return useSyncExternalStore(
|
|
eventID ? room.eventSubs.getSubscriber(eventID) : noopSubscribe,
|
|
eventID ? (() => room.eventsByID.get(eventID) ?? null) : returnNull,
|
|
)
|
|
}
|
|
|
|
export function useAccountData(ss: StateStore, type: EventType): UnknownEventContent | null {
|
|
return useSyncExternalStore(
|
|
ss.accountDataSubs.getSubscriber(type),
|
|
() => ss.accountData.get(type) ?? null,
|
|
)
|
|
}
|
|
|
|
export function useRoomAccountData(room: RoomStateStore | null, type: EventType): UnknownEventContent | null {
|
|
return useSyncExternalStore(
|
|
room ? room.accountDataSubs.getSubscriber(type) : noopSubscribe,
|
|
() => room?.accountData.get(type) ?? null,
|
|
)
|
|
}
|
|
|
|
export function usePreferences(ss: StateStore, room: RoomStateStore | null) {
|
|
useSyncExternalStore(ss.preferenceSub.subscribe, ss.preferenceSub.getData)
|
|
useSyncExternalStore(room?.preferenceSub.subscribe ?? noopSubscribe, room?.preferenceSub.getData ?? returnNull)
|
|
}
|
|
|
|
export function usePreference<T extends keyof Preferences>(
|
|
ss: StateStore, room: RoomStateStore | null, key: T,
|
|
): typeof preferences[T]["defaultValue"] {
|
|
const [val, setVal] = useState(
|
|
(room ? room.preferences[key] : ss.preferences[key]) ?? preferences[key].defaultValue,
|
|
)
|
|
useEffect(() => {
|
|
const checkChanges = () => {
|
|
setVal((room ? room.preferences[key] : ss.preferences[key]) ?? preferences[key].defaultValue)
|
|
}
|
|
const unsubMain = ss.preferenceSub.subscribe(checkChanges)
|
|
const unsubRoom = room?.preferenceSub.subscribe(checkChanges)
|
|
return () => {
|
|
unsubMain()
|
|
unsubRoom?.()
|
|
}
|
|
}, [ss, room, key])
|
|
return val
|
|
}
|
|
|
|
export function useCustomEmojis(
|
|
ss: StateStore, room: RoomStateStore, usage: "stickers" | "emojis" = "emojis",
|
|
): CustomEmojiPack[] {
|
|
const personalPack = useSyncExternalStore(
|
|
ss.accountDataSubs.getSubscriber("im.ponies.user_emotes"),
|
|
() => ss.getPersonalEmojiPack(),
|
|
)
|
|
const watchedRoomPacks = useSyncExternalStore(
|
|
ss.emojiRoomsSub.subscribe,
|
|
() => ss.getRoomEmojiPacks(),
|
|
)
|
|
const specialRoomPacks = useSyncExternalStore<Record<string, CustomEmojiPack>>(
|
|
room.stateSubs.getSubscriber("im.ponies.room_emotes"),
|
|
() => room.preferences.show_room_emoji_packs ? room.getAllEmojiPacks() : {},
|
|
)
|
|
return useMemo(() => {
|
|
const allPacksObject = { ...watchedRoomPacks, ...specialRoomPacks }
|
|
if (personalPack) {
|
|
allPacksObject.personal = personalPack
|
|
}
|
|
return Object.values(allPacksObject).filter(pack => pack[usage].length > 0)
|
|
}, [personalPack, watchedRoomPacks, specialRoomPacks, usage])
|
|
}
|