diff --git a/pkg/hicli/json-commands.go b/pkg/hicli/json-commands.go index 0a07c25..9be033a 100644 --- a/pkg/hicli/json-commands.go +++ b/pkg/hicli/json-commands.go @@ -86,6 +86,10 @@ func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any return unmarshalAndCall(req.Data, func(params *getProfileParams) (*mautrix.RespUserProfile, error) { return h.Client.GetProfile(ctx, params.UserID) }) + case "set_profile_field": + return unmarshalAndCall(req.Data, func(params *setProfileFieldParams) (bool, error) { + return true, h.Client.UnstableSetProfileField(ctx, params.Field, params.Value) + }) case "get_mutual_rooms": return unmarshalAndCall(req.Data, func(params *getProfileParams) ([]id.RoomID, error) { return h.GetMutualRooms(ctx, params.UserID) @@ -273,6 +277,11 @@ type getProfileParams struct { UserID id.UserID `json:"user_id"` } +type setProfileFieldParams struct { + Field string `json:"field"` + Value any `json:"value"` +} + type getEventParams struct { RoomID id.RoomID `json:"room_id"` EventID id.EventID `json:"event_id"` diff --git a/web/src/api/rpc.ts b/web/src/api/rpc.ts index b967010..df9131f 100644 --- a/web/src/api/rpc.ts +++ b/web/src/api/rpc.ts @@ -39,7 +39,7 @@ import type { RoomSummary, TimelineRowID, UserID, - UserProfile, + UserProfile, JSONValue, } from "./types" export interface ConnectionEvent { @@ -180,6 +180,10 @@ export default abstract class RPCClient { return this.request("get_profile", { user_id }) } + setProfileField(field: string, value: JSONValue): Promise { + return this.request("set_profile_field", { field, value }) + } + getMutualRooms(user_id: UserID): Promise { return this.request("get_mutual_rooms", { user_id }) } diff --git a/web/src/api/types/mxtypes.ts b/web/src/api/types/mxtypes.ts index 5932e3e..e91ad02 100644 --- a/web/src/api/types/mxtypes.ts +++ b/web/src/api/types/mxtypes.ts @@ -25,6 +25,14 @@ export type RoomVersion = "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | export type RoomType = "" | "m.space" export type RelationType = "m.annotation" | "m.reference" | "m.replace" | "m.thread" +export type JSONValue = + | string + | number + | boolean + | null + | JSONValue[] + | {[key: string]: JSONValue} + export interface RoomPredecessor { room_id: RoomID event_id: EventID diff --git a/web/src/ui/rightpanel/RightPanel.css b/web/src/ui/rightpanel/RightPanel.css index efb0b70..71ab3de 100644 --- a/web/src/ui/rightpanel/RightPanel.css +++ b/web/src/ui/rightpanel/RightPanel.css @@ -72,6 +72,13 @@ div.right-panel-content.user { } } + input.text-input { + border: 0; + width: 100%; + box-sizing: border-box; + border-bottom: 1px solid var(--blockquote-border-color); + } + div.displayname { font-size: 1.5rem; font-weight: bold; @@ -92,14 +99,9 @@ div.right-panel-content.user { } div.extended-profile { - display: flex; - flex-direction: column; + display: grid; gap: 0.25rem; - - div.profile-row { - display: grid; - grid-template-columns: 1fr 1fr; - } + grid-template-columns: 1fr 1fr; } hr { diff --git a/web/src/ui/rightpanel/UserExtendedProfile.tsx b/web/src/ui/rightpanel/UserExtendedProfile.tsx index 8d03a29..aa9bde3 100644 --- a/web/src/ui/rightpanel/UserExtendedProfile.tsx +++ b/web/src/ui/rightpanel/UserExtendedProfile.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from "react" import { UserProfile } from "@/api/types" import { ensureArray } from "@/util/validation.ts" +import Client from "@/api/client.ts"; interface PronounSet { subject: string @@ -18,13 +19,25 @@ interface ExtendedProfileAttributes { interface ExtendedProfileProps { profile: UserProfile & ExtendedProfileAttributes + client: Client + userID: string } +interface SetTimezoneProps { + tz?: string + client: Client +} + +const getCurrentTimezone = () => Intl.DateTimeFormat().resolvedOptions().timeZone const currentTimeAdjusted = (tz: string) => { const lang = navigator.language || "en-US" const now = new Date() - return new Intl.DateTimeFormat(lang, { timeStyle: "long", timeZone: tz }).format(now) + try { + return new Intl.DateTimeFormat(lang, { timeStyle: "long", timeZone: tz }).format(now) + } catch (e) { + return `Error: ${e}` + } } function ClockElement({ tz }: { tz: string }) { @@ -38,37 +51,82 @@ function ClockElement({ tz }: { tz: string }) { return
{time}
} +function SetTimezoneElement({ tz, client }: SetTimezoneProps) { + const zones = Intl.supportedValuesOf("timeZone") + const setTz = (newTz: string) => { + if (zones.includes(newTz) && newTz !== tz) { + return client.rpc.setProfileField("us.cloke.msc4175.tz", newTz).then( + () => client.rpc.getProfile(client.userID), + (err) => console.error("Error setting timezone", err), + ) + } + } -export default function UserExtendedProfile({ profile }: ExtendedProfileProps) { + return ( + <> + setTz(e.currentTarget.value)} + /> + + { + zones.map((zone) => + + ) +} + + +export default function UserExtendedProfile({ profile, client, userID }: ExtendedProfileProps) { if (!profile) return null + const extendedProfileKeys = ["us.cloke.msc4175.tz", "io.fsky.nyx.pronouns"] + const hasExtendedProfile = extendedProfileKeys.some((key) => key in profile) + if (!hasExtendedProfile && client.userID !== userID) return null + // Explicitly only return something if the profile has the keys we're looking for. + // otherwise there's an ugly and pointless
for no real reason. + const pronouns: PronounSet[] = ensureArray(profile["io.fsky.nyx.pronouns"]) as PronounSet[] return ( -
- { - profile["us.cloke.msc4175.tz"] && ( -
-
Time:
- -
- ) - } - { - pronouns.length >= 1 && ( -
-
Pronouns:
-
- { - pronouns.map( - (pronounSet: PronounSet) => ( - pronounSet.summary || `${pronounSet.subject}/${pronounSet.object}` - ), - ).join("/") - } -
-
- ) - } -
+ <> +
+
+ { + profile["us.cloke.msc4175.tz"] && ( + <> +
Time:
+ + + ) + } + { + userID === client.userID && ( + <> +
Set Timezone:
+ + + ) + } + { + pronouns.length >= 1 && ( + <> +
Pronouns:
+
+ { + pronouns.map( + (pronounSet: PronounSet) => ( + pronounSet.summary || `${pronounSet.subject}/${pronounSet.object}` + ), + ).join("/") + } +
+ + ) + } +
+ ) } diff --git a/web/src/ui/rightpanel/UserInfo.tsx b/web/src/ui/rightpanel/UserInfo.tsx index 71d530f..fd4275a 100644 --- a/web/src/ui/rightpanel/UserInfo.tsx +++ b/web/src/ui/rightpanel/UserInfo.tsx @@ -47,6 +47,8 @@ const UserInfo = ({ userID }: UserInfoProps) => { err => setErrors([`${err}`]), ) }, [roomCtx, userID, client]) + + const displayname = member?.displayname || globalProfile?.displayname || getLocalpart(userID) return <>
@@ -63,7 +65,7 @@ const UserInfo = ({ userID }: UserInfoProps) => {
{displayname}
{userID}
- {globalProfile && <>
} + {globalProfile && }
{userID !== client.userID && <>