Bring code up to date with the latest gomuks

This commit is contained in:
nexy7574 2025-01-12 21:40:08 +00:00
parent 13a45b7722
commit 0d8a6bce23
No known key found for this signature in database
GPG key ID: 0FA334385D0B689F
3 changed files with 105 additions and 65 deletions

View file

@ -119,7 +119,7 @@ div.right-panel-content.user {
justify-content: left; justify-content: left;
justify-items: left; justify-items: left;
gap: .5rem; gap: .5rem;
} }
div.presencesetter > button { div.presencesetter > button {

View file

@ -17,7 +17,7 @@ import { use, useCallback, useEffect, useState } from "react"
import { PuffLoader } from "react-spinners" import { PuffLoader } from "react-spinners"
import { getAvatarURL } from "@/api/media.ts" import { getAvatarURL } from "@/api/media.ts"
import { useRoomMember } from "@/api/statestore" import { useRoomMember } from "@/api/statestore"
import { MemberEventContent, UserID, UserProfile, Presence } from "@/api/types" import { MemberEventContent, UserID, UserProfile } from "@/api/types"
import { getLocalpart } from "@/util/validation.ts" import { getLocalpart } from "@/util/validation.ts"
import ClientContext from "../ClientContext.ts" import ClientContext from "../ClientContext.ts"
import { LightboxContext } from "../modal" import { LightboxContext } from "../modal"
@ -26,7 +26,6 @@ import UserExtendedProfile from "./UserExtendedProfile.tsx"
import DeviceList from "./UserInfoDeviceList.tsx" import DeviceList from "./UserInfoDeviceList.tsx"
import UserInfoError from "./UserInfoError.tsx" import UserInfoError from "./UserInfoError.tsx"
import MutualRooms from "./UserInfoMutualRooms.tsx" import MutualRooms from "./UserInfoMutualRooms.tsx"
import { ErrorResponse } from "@/api/rpc.ts"
import { UserPresence } from "./UserPresence.tsx" import { UserPresence } from "./UserPresence.tsx"
interface UserInfoProps { interface UserInfoProps {

View file

@ -1,5 +1,5 @@
// gomuks - A Matrix client written in Go. // gomuks - A Matrix client written in Go.
// Copyright (C) 2024 Tulir Asokan // Copyright (C) 2025 Nexus Nicholson
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
@ -13,10 +13,10 @@
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import { MouseEvent, useEffect, useState } from "react" import { FormEvent, MouseEvent, useEffect, useState } from "react"
import { UserID, Presence, RPCEvent } from "@/api/types"
import { ErrorResponse } from "@/api/rpc.ts"
import Client from "@/api/client" import Client from "@/api/client"
import { ErrorResponse } from "@/api/rpc.ts"
import { Presence, RPCEvent, UserID } from "@/api/types"
interface UserPresenceProps { interface UserPresenceProps {
client: Client, client: Client,
@ -28,98 +28,139 @@ interface EditUserPresenceProps {
setter: (presence: Presence) => void setter: (presence: Presence) => void
} }
const PresenceEmojis = { const PresenceEmojis = {
"online": <svg className="presence-indicator presence-online" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><circle cx="16" cy="16" r="50" /></svg>, "online": <svg
"offline": <svg className="presence-indicator presence-offline" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><circle cx="16" cy="16" r="50" /></svg>, className="presence-indicator presence-online"
"unavailable": <svg className="presence-indicator presence-unavailable" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><circle cx="16" cy="16" r="50" /></svg>, viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"><circle cx="16" cy="16" r="50" /></svg>,
"offline": <svg
className="presence-indicator presence-offline"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"><circle cx="16" cy="16" r="50" /></svg>,
"unavailable": <svg
className="presence-indicator presence-unavailable"
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"><circle cx="16" cy="16" r="50" /></svg>,
} }
export const UserPresence = ({ client, userID }: UserPresenceProps) => { export const UserPresence = ({ client, userID }: UserPresenceProps) => {
const [presence, setPresence] = useState<Presence | null>(null) const [presence, setPresence] = useState<Presence | null>(null)
const [errors, setErrors] = useState<string[] | null>(null) const [errors, setErrors] = useState<string[] | null>(null)
client.rpc.event.listen((event: RPCEvent) => { client.rpc.event.listen((event: RPCEvent) => {
if (event.command === "update_presence" && event.data.user_id === userID) { if (event.command === "update_presence" && event.data.user_id === userID) {
setPresence({ setPresence({
"presence": event.data.presence, "presence": event.data.presence,
"status_msg": event.data.status_msg || null "status_msg": event.data.status_msg || null,
}) })
} }
}) })
useEffect(() => { useEffect(() => {
client.rpc.getPresence(userID).then( client.rpc.getPresence(userID).then(
setPresence, setPresence,
err => { err => {
// A 404 is to be expected if the user has not federated presence. // A 404 is to be expected if the user has not federated presence.
if (err instanceof ErrorResponse && err.message.startsWith("M_NOT_FOUND")) { if (err instanceof ErrorResponse && err.message.startsWith("M_NOT_FOUND")) {
setPresence(null) setPresence(null)
} else { } else {
errors?.length ? setErrors([...errors, `${err}`]) : setErrors([`${err}`]) setErrors([...errors||[], `${err}`])
} }
} },
) )
}, [client, userID]) }, [client, userID])
if(!presence) return null; if(!presence) {return null}
return ( return (
<> <>
<DisplayUserPresence presence={presence} /> <DisplayUserPresence presence={presence} />
{ {
userID === client.userID && <EditUserPresence client={client} presence={presence} setter={setPresence}/> userID === client.userID && <EditUserPresence client={client} presence={presence} setter={setPresence}/>
} }
</> </>
) )
} }
export const DisplayUserPresence = ({ presence }: { presence: Presence | null }) => { export const DisplayUserPresence = ({ presence }: { presence: Presence | null }) => {
if(!presence) return null if(!presence) {return null}
return ( return (
<> <>
<div className="presence" title={presence.presence}>{PresenceEmojis[presence.presence]} {presence.presence}</div> <div className="presence" title={presence.presence}>{PresenceEmojis[presence.presence]} {presence.presence}
{ </div>
presence.status_msg && ( {
<div className="statusmessage" title={"Status message"}><blockquote>{presence.status_msg}</blockquote></div> presence.status_msg && (
) <div className="statusmessage" title={"Status message"}>
} <blockquote>{presence.status_msg}</blockquote>
</> </div>
) )
}
</>
)
} }
export const EditUserPresence = ({ client, presence, setter }: EditUserPresenceProps) => { export const EditUserPresence = ({ client, presence, setter }: EditUserPresenceProps) => {
const sendNewPresence = (newPresence: Presence) => { const sendNewPresence = (newPresence: Presence) => {
client.rpc.setPresence(newPresence).then( client.rpc.setPresence(newPresence).then(
() => setter(newPresence), () => setter(newPresence),
err => console.error(err), err => console.error(err),
) )
} }
const clearStatusMessage = (e: MouseEvent<HTMLButtonElement>) => { const createPresence = (status: "online" | "unavailable" | "offline") => {
let p = presence || {"presence": "offline"} return { ...(presence || {}), "presence": status }
if(p.status_msg) { }
delete p.status_msg const clearStatusMessage = (e: MouseEvent<HTMLButtonElement>) => {
} const p = presence || { "presence": "offline" }
const textInputElement = e.currentTarget.parentElement?.querySelector("input[type=text]") as HTMLInputElement if(p.status_msg) {
client.rpc.setPresence(p).then( delete p.status_msg
() => {setter(p); if(textInputElement) textInputElement.value = ""}, }
const textInputElement = e.currentTarget.parentElement?.querySelector("input[type=text]") as HTMLInputElement
client.rpc.setPresence(p).then(
() => {setter(p); if(textInputElement) {textInputElement.value = ""}},
err => console.error(err), err => console.error(err),
) )
} }
return ( const onFormSubmit = (e: FormEvent<HTMLFormElement>) => {
<> e.preventDefault()
<h4>Set presence</h4> const textInputElement = e.currentTarget[0] as HTMLInputElement
const newPresence = { ...(presence || {}), "status_msg": textInputElement.value }
client.rpc.setPresence(newPresence).then(
() => {setter(newPresence); textInputElement.value = ""},
err => console.error(err),
)
}
return (
<>
<h4>Set presence</h4>
<div className="presencesetter"> <div className="presencesetter">
<button title="Set presence to online" onClick={() => sendNewPresence({...(presence || {}), "presence": "online"})} type="button">{PresenceEmojis["online"]} Online</button> <button
<button title="Set presence to unavailable" onClick={() => sendNewPresence({...(presence || {}), "presence": "unavailable"})} type="button">{PresenceEmojis["unavailable"]} Unavailable</button> title="Set presence to online"
<button title="Set presence to offline" onClick={() => sendNewPresence({...(presence || {}), "presence": "offline"})} type="button">{PresenceEmojis["offline"]} Offline</button> onClick={() => sendNewPresence(createPresence("online"))}
type="button">
{PresenceEmojis["online"]} Online
</button>
<button
title="Set presence to unavailable"
onClick={() => sendNewPresence(createPresence("unavailable"))}
type="button">
{PresenceEmojis["unavailable"]} Unavailable
</button>
<button
title="Set presence to offline"
onClick={() => sendNewPresence(createPresence("offline"))}
type="button">
{PresenceEmojis["offline"]} Offline
</button>
</div> </div>
<p></p> <p></p>
<div className="statussetter"> <div className="statussetter">
<form className={presence?.status_msg ? "canclear" : "cannotclear"} onSubmit={(e) => {e.preventDefault(); sendNewPresence({...(presence || {}), "status_msg": (e.currentTarget[0] as HTMLInputElement).value})}}> <form className={presence?.status_msg ? "canclear" : "cannotclear"} onSubmit={onFormSubmit}>
<input type="text" placeholder="Status message" defaultValue={presence?.status_msg || ""}/> <input type="text" placeholder="Status message" defaultValue={presence?.status_msg || ""}/>
{presence?.status_msg && <button title="Clear status" type="button" onClick={(e) => clearStatusMessage(e)}>Clear</button>} {presence?.status_msg &&
<button title="Clear status" type="button" onClick={clearStatusMessage}>Clear</button>}
<button title="Set status message" type="submit">Set</button> <button title="Set status message" type="submit">Set</button>
</form> </form>
</div> </div>
</> </>
) )
} }