From 6989162252470128bc7e11733c0f05ecff19e592 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Sun, 8 Sep 2024 10:55:29 +0100 Subject: [PATCH] Refactor to using a hook And automatically refresh the timezone every minute. --- src/components/views/right_panel/UserInfo.tsx | 28 +------ src/hooks/useUserTimezone.ts | 82 +++++++++++++++++++ 2 files changed, 86 insertions(+), 24 deletions(-) create mode 100644 src/hooks/useUserTimezone.ts diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index 8019bda1f1f..60af901975e 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -29,6 +29,7 @@ import { User, Device, EventType, + MatrixError, } from "matrix-js-sdk/src/matrix"; import { KnownMembership } from "matrix-js-sdk/src/types"; import { UserVerificationStatus, VerificationRequest } from "matrix-js-sdk/src/crypto-api"; @@ -93,7 +94,7 @@ import { SdkContextClass } from "../../../contexts/SDKContext"; import { asyncSome } from "../../../utils/arrays"; import { Flex } from "../../utils/Flex"; import CopyableText from "../elements/CopyableText"; - +import { useUserTimezone } from "../../../hooks/useUserTimezone"; export interface IDevice extends Device { ambiguous?: boolean; } @@ -1703,28 +1704,7 @@ export const UserInfoHeader: React.FC<{ } - // TODO: Check for support for this property. - const [timezone, setTimezone] = useState<{friendly: string, tz: string}>(); - useEffect(() => { - cli.getExtendedProfileProperty(member.userId, 'us.cloke.msc4175.tz').then((value) => { - if (typeof value !== "string") { - // Err, definitely not a tz. - return; - } - try { - setTimezone( - { - friendly: new Date().toLocaleString(undefined, { timeZone: value, hour12: true, hour: "2-digit", minute: "2-digit", timeZoneName: "shortOffset"}), - tz: value - } - ); - } catch (ex) { - console.error('Could not render current time for user', ex); - } - }).catch((ex) => { - console.error('Unable to load user timezone', ex); - }); - }, [member.userId]); + const timezoneInfo = useUserTimezone(member.userId); const e2eIcon = e2eStatus ? : null; const userIdentifier = UserIdentifierCustomisations.getDisplayUserIdentifier?.(member.userId, { @@ -1758,7 +1738,7 @@ export const UserInfoHeader: React.FC<{ {e2eIcon} - {presenceLabel} {timezone && {timezone.friendly}} + {presenceLabel} {timezoneInfo && {timezoneInfo.friendly}} userIdentifier} border={false}> {userIdentifier} diff --git a/src/hooks/useUserTimezone.ts b/src/hooks/useUserTimezone.ts new file mode 100644 index 00000000000..d47888315a8 --- /dev/null +++ b/src/hooks/useUserTimezone.ts @@ -0,0 +1,82 @@ +import { useEffect, useState } from "react"; +import { MatrixClientPeg } from "../MatrixClientPeg"; +import { MatrixError } from "matrix-js-sdk/src/matrix"; + +/** + * Fetch a user's delclared timezone through their profile, and return + * a friendly string of the current time for that user. This will keep + * in sync with the current time, and will be refreshed once a minute. + * + * @param userId The userID to fetch the timezone for. + * @returns A timezone name and friendly string for the user's timezone, or + * null if the user has no timezone or the timezone was not recognised + * by the browser. + */ +export const useUserTimezone = (userId: string): { timezone: string, friendly: string }|null => { + const [timezone, setTimezone] = useState(); + const [updateInterval, setUpdateInterval] = useState(); + const [friendly, setFriendly] = useState(); + const [supported, setSupported] = useState(); + const cli = MatrixClientPeg.safeGet(); + + useEffect(() => { + if (supported !== undefined) { + return; + } + cli.doesServerSupportExtendedProfiles().then(setSupported).catch((ex) => { + console.warn("Unable to determine if extended profiles are supported", ex); + }); + }, [supported]); + + useEffect(() => { + return () => { + if (updateInterval) { + clearInterval(updateInterval); + } + } + }, [updateInterval]); + + useEffect(() => { + if (supported !== true) { + return; + } + (async () => { + try { + const tz = await cli.getExtendedProfileProperty(userId, 'us.cloke.msc4175.tz'); + if (typeof tz !== "string") { + // Err, definitely not a tz. + throw Error('Timezone value was not a string'); + } + // This will validate the timezone for us. + Intl.DateTimeFormat(undefined, {timeZone: tz}); + + const updateTime = () => { + const currentTime = new Date(); + const friendly = currentTime.toLocaleString(undefined, { timeZone: tz, hour12: true, hour: "2-digit", minute: "2-digit", timeZoneName: "shortOffset"}); + setTimezone(tz); + setFriendly(friendly); + setUpdateInterval(setTimeout(updateTime, (60 - currentTime.getSeconds()) * 1000)); + } + updateTime(); + } catch (ex) { + setTimezone(undefined); + setFriendly(undefined); + setUpdateInterval(undefined); + if (ex instanceof MatrixError && ex.errcode === "M_NOT_FOUND") { + // No timezone set, ignore. + return; + } + console.error('Could not render current timezone for user', ex); + } + })(); + }, [supported, userId]); + + if (!timezone || !friendly) { + return null; + } + + return { + friendly, + timezone + }; +} \ No newline at end of file