From da8c1456dd9724ea4fb23d3b72739317a4f319fd Mon Sep 17 00:00:00 2001 From: Robert Long Date: Wed, 26 Apr 2023 16:36:09 -0700 Subject: [PATCH 1/4] Store 3D avatar urls in account data --- src/plugins/thirdroom/thirdroom.common.ts | 1 + src/plugins/thirdroom/thirdroom.game.ts | 2 + src/plugins/thirdroom/thirdroom.main.ts | 3 +- src/ui/hooks/use3DAvatar.ts | 24 --- src/ui/hooks/useHydrogen.tsx | 13 +- src/ui/hooks/useUserProfile.ts | 138 ++++-------------- src/ui/hooks/useWorldLoader.ts | 4 +- src/ui/state/userProfile.ts | 17 ++- src/ui/utils/matrixUtils.ts | 30 ++-- src/ui/views/HydrogenRootView.tsx | 14 +- .../session/user-profile/Edit3DAvatar.tsx | 18 +-- .../user-profile/UserProfileOverview.tsx | 53 +++---- 12 files changed, 103 insertions(+), 214 deletions(-) delete mode 100644 src/ui/hooks/use3DAvatar.ts diff --git a/src/plugins/thirdroom/thirdroom.common.ts b/src/plugins/thirdroom/thirdroom.common.ts index 2c05f15c7..ed66c12d7 100644 --- a/src/plugins/thirdroom/thirdroom.common.ts +++ b/src/plugins/thirdroom/thirdroom.common.ts @@ -19,6 +19,7 @@ export enum ThirdRoomMessageType { export interface EnterWorldMessage { type: ThirdRoomMessageType.EnterWorld; id: number; + avatarUrl?: string; } export interface EnteredWorldMessage { diff --git a/src/plugins/thirdroom/thirdroom.game.ts b/src/plugins/thirdroom/thirdroom.game.ts index e6c2a4000..2fcb1685b 100644 --- a/src/plugins/thirdroom/thirdroom.game.ts +++ b/src/plugins/thirdroom/thirdroom.game.ts @@ -370,6 +370,8 @@ async function onEnterWorld(ctx: GameState, message: EnterWorldMessage) { const physics = getModule(ctx, PhysicsModule); const input = getModule(ctx, InputModule); + console.log(message.avatarUrl); + loadPlayerRig(ctx, physics, input, network); await waitUntil(() => ourPlayerQuery(ctx.world).length > 0); diff --git a/src/plugins/thirdroom/thirdroom.main.ts b/src/plugins/thirdroom/thirdroom.main.ts index b229fbc91..8bf0a7b2a 100644 --- a/src/plugins/thirdroom/thirdroom.main.ts +++ b/src/plugins/thirdroom/thirdroom.main.ts @@ -109,7 +109,7 @@ export async function loadWorld(ctx: IMainThreadContext, url: string, scriptUrl: return loadingEnvironment.promise; } -export async function enterWorld(ctx: IMainThreadContext) { +export async function enterWorld(ctx: IMainThreadContext, avatarUrl?: string) { const thirdroom = getModule(ctx, ThirdroomModule); const enteringWorld = createDeferred(false); @@ -141,6 +141,7 @@ export async function enterWorld(ctx: IMainThreadContext) { ctx.sendMessage(Thread.Game, { type: ThirdRoomMessageType.EnterWorld, id, + avatarUrl, }); return enteringWorld.promise; diff --git a/src/ui/hooks/use3DAvatar.ts b/src/ui/hooks/use3DAvatar.ts deleted file mode 100644 index d9c10fa9e..000000000 --- a/src/ui/hooks/use3DAvatar.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useCallback, useState } from "react"; -import { Room, StateEvent } from "@thirdroom/hydrogen-view-sdk"; - -import { useStateEventKeyCallback } from "./useStateEventKeyCallback"; - -export function use3DAvatar(profileRoom: Room) { - const [avatarUrl, setAvatarUrl] = useState(); - const [avatarPreviewUrl, setAvatarPreviewUrl] = useState(); - - const callback = useCallback((stateEvent?: StateEvent) => { - if (stateEvent) { - const { content } = stateEvent; - setAvatarUrl(content.avatar_url); - setAvatarPreviewUrl(content.avatar_preview_url); - } else { - setAvatarUrl(undefined); - setAvatarPreviewUrl(undefined); - } - }, []); - - useStateEventKeyCallback(profileRoom, "org.matrix.msc3815.world.profile", "", callback); - - return [avatarUrl, avatarPreviewUrl]; -} diff --git a/src/ui/hooks/useHydrogen.tsx b/src/ui/hooks/useHydrogen.tsx index bca772750..27f9bacaf 100644 --- a/src/ui/hooks/useHydrogen.tsx +++ b/src/ui/hooks/useHydrogen.tsx @@ -1,13 +1,4 @@ -import { - Platform, - Navigation, - Client, - Session, - ILogger, - URLRouter, - Room, - ILoginMethod, -} from "@thirdroom/hydrogen-view-sdk"; +import { Platform, Navigation, Client, Session, ILogger, URLRouter, ILoginMethod } from "@thirdroom/hydrogen-view-sdk"; import { createContext, useContext } from "react"; export interface HydrogenContext { @@ -15,7 +6,6 @@ export interface HydrogenContext { navigation: Navigation; platform: Platform; session?: Session; - profileRoom?: Room; logger: ILogger; urlRouter: URLRouter; login: (loginMethod: ILoginMethod) => Promise; @@ -24,7 +14,6 @@ export interface HydrogenContext { export interface AuthenticatedHydrogenContext extends HydrogenContext { session: Session; - profileRoom: Room; } export const HydrogenContext = createContext(undefined); diff --git a/src/ui/hooks/useUserProfile.ts b/src/ui/hooks/useUserProfile.ts index 9a402a6c5..912e9754b 100644 --- a/src/ui/hooks/useUserProfile.ts +++ b/src/ui/hooks/useUserProfile.ts @@ -1,128 +1,46 @@ -import { useCallback, useEffect, useState } from "react"; -import { Session, Client, SyncStatus, Room, RoomVisibility, RoomType } from "@thirdroom/hydrogen-view-sdk"; +import { useEffect } from "react"; +import { Session, Client } from "@thirdroom/hydrogen-view-sdk"; import { useSetAtom } from "jotai"; -import { getMxIdUsername, getProfileRoom, waitToCreateRoom } from "../utils/matrixUtils"; +import { getMxIdUsername, getUserProfile } from "../utils/matrixUtils"; import { useIsMounted } from "./useIsMounted"; import { userProfileAtom } from "../state/userProfile"; export function useUserProfile(client: Client, session?: Session) { const setUserProfile = useSetAtom(userProfileAtom); const isMounted = useIsMounted(); - const [profileRoom, setProfileRoom] = useState(); - - const updateProfile = useCallback( - (userId: string, displayName?: string, avatarUrl?: string) => { - setUserProfile({ - userId, - displayName: displayName || getMxIdUsername(userId), - avatarUrl, - }); - }, - [setUserProfile] - ); - - const updateFromServer = useCallback( - (session: Session) => { - session.hsApi - .profile(session.userId) - .response() - .then((data) => { - if (!isMounted()) return; - updateProfile(session.userId, data.displayname, data.avatar_url); - }) - .catch(() => { - // Silence error since not all users have profile routes - }); - }, - [isMounted, updateProfile] - ); - - const listenProfileChange = useCallback( - async (session: Session, profileRoom: Room) => { - setProfileRoom(profileRoom); - const memberObserver = await profileRoom.observeMember(session.userId); - - const unSubs = memberObserver?.subscribe((member) => { - if (member.membership !== "join") unSubs?.(); - - const { avatarUrl, displayName } = member; - updateProfile(session.userId, avatarUrl, displayName); - }); - }, - [updateProfile, setProfileRoom] - ); - - const initProfileRoom = useCallback( - async (session: Session) => { - const profileRoom = getProfileRoom(session.rooms); - - if (profileRoom) { - listenProfileChange(session, profileRoom); - } else { - const roomBeingCreated = session.createRoom({ - type: RoomType.Profile, - visibility: RoomVisibility.Private, - name: "Third Room - Profile", - topic: "This room contain profile information.", - isEncrypted: false, - isFederationDisabled: false, - powerLevelContentOverride: { - invite: 100, - kick: 100, - ban: 100, - redact: 100, - state_default: 100, - events_default: 100, - users_default: 0, - users: { - [session.userId]: 100, - }, - }, - }); - - const profileRoom = await waitToCreateRoom(session, roomBeingCreated); - if (!profileRoom) { - window.setTimeout(() => window.location.reload()); - return; - } - listenProfileChange(session, profileRoom); - } - }, - [listenProfileChange] - ); useEffect(() => { - let unSubs: () => void; - const { sync } = client; if (!session) { - setProfileRoom(undefined); return; } - updateProfile(session.userId); - updateFromServer(session); - - // Make sure catchup sync has completed - // so we don't create redundant profile room. - if (sync.status.get() === "Syncing" || getProfileRoom(session.rooms)) { - initProfileRoom(session); - } else { - let prevStatus: SyncStatus = sync.status.get(); - unSubs = sync.status.subscribe((syncStatus) => { - if (prevStatus === syncStatus) return; - prevStatus = syncStatus; - if (syncStatus === "Syncing") { - initProfileRoom(session); - unSubs?.(); - } + setUserProfile({ + userId: session.userId, + displayName: getMxIdUsername(session.userId), + }); + + Promise.all([session.hsApi.profile(session.userId).response(), getUserProfile(session)]) + .then(([profileData, userProfile]) => { + if (!isMounted()) return; + + setUserProfile({ + userId: session.userId, + displayName: profileData.displayname || getMxIdUsername(session.userId), + avatarUrl: profileData.avatar_url, + avatarModelUrl: userProfile?.avatar_url, + avatarModelPreviewUrl: userProfile?.avatar_preview_url, + }); + }) + .catch(() => { + // Silence error since not all users have profile routes }); - } + return () => { - unSubs?.(); - updateProfile("@dummy:server.xyz"); + setUserProfile({ + userId: "@dummy:server.xyz", + displayName: getMxIdUsername("@dummy:server.xyz"), + }); }; - }, [client, session, updateProfile, updateFromServer, initProfileRoom]); - - return profileRoom; + }, [client, session, setUserProfile, isMounted]); } diff --git a/src/ui/hooks/useWorldLoader.ts b/src/ui/hooks/useWorldLoader.ts index 2cae71fda..cb33883c2 100644 --- a/src/ui/hooks/useWorldLoader.ts +++ b/src/ui/hooks/useWorldLoader.ts @@ -125,7 +125,7 @@ export function useWorldLoader(): WorldLoader { groupCall = await connectGroupCall(world); } - await updateWorldProfile(session, world); + const profile = await updateWorldProfile(session, world); setLocalMediaStream(mainThread, groupCall.localMedia?.userMedia); @@ -138,7 +138,7 @@ export function useWorldLoader(): WorldLoader { ); registerMatrixNetworkInterface(matrixNetworkInterface); - await enterWorld(mainThread); + await enterWorld(mainThread, profile?.avatar_url); const audio = getModule(mainThread, AudioModule); audio.context.resume().catch(() => console.error("Couldn't resume audio context")); diff --git a/src/ui/state/userProfile.ts b/src/ui/state/userProfile.ts index 1d7cdbee8..8a9ac1b21 100644 --- a/src/ui/state/userProfile.ts +++ b/src/ui/state/userProfile.ts @@ -4,19 +4,32 @@ interface UserProfileState { userId: string; displayName: string; avatarUrl?: string; + avatarModelUrl?: string; + avatarModelPreviewUrl?: string; } const baseUserProfileAtom = atom({ userId: "@dummy:server.xyz", displayName: "dummy", avatarUrl: undefined, + avatarModelUrl: undefined, + avatarModelPreviewUrl: undefined, }); export const userProfileAtom = atom( (get) => get(baseUserProfileAtom), (get, set, update) => { - const { userId, displayName, avatarUrl } = get(baseUserProfileAtom); - if (userId === update.userId && avatarUrl === update.avatarUrl && displayName === update.displayName) return; + const { userId, displayName, avatarUrl, avatarModelUrl, avatarModelPreviewUrl } = get(baseUserProfileAtom); + if ( + userId === update.userId && + avatarUrl === update.avatarUrl && + displayName === update.displayName && + avatarModelUrl === update.avatarModelUrl && + avatarModelPreviewUrl === update.avatarModelPreviewUrl + ) { + return; + } + set(baseUserProfileAtom, update); } ); diff --git a/src/ui/utils/matrixUtils.ts b/src/ui/utils/matrixUtils.ts index c38760a37..002cbf388 100644 --- a/src/ui/utils/matrixUtils.ts +++ b/src/ui/utils/matrixUtils.ts @@ -71,24 +71,32 @@ export function roomIdToAlias(rooms: ObservableMap, roomId: string return rooms.get(roomId)?.canonicalAlias ?? undefined; } -export function getProfileRoom(rooms: ObservableMap) { - const type = "org.matrix.msc3815.profile"; - for (const room of rooms.values()) { - if (room.type === type) return room; - } +interface UserProfile { + avatar_url?: string; + avatar_preview_url?: string; } -export async function updateWorldProfile(session: Session, world: Room) { - const profileRoom = getProfileRoom(session.rooms); - if (profileRoom) { - const profileEvent = await profileRoom.getStateEvent("org.matrix.msc3815.world.profile", ""); +export function getUserProfile(session: Session): Promise { + return session.getAccountData("org.matrix.msc3815.profile"); +} - if (profileEvent && profileEvent.event.content.avatar_url) { +export async function setUserProfile(session: Session, profile: UserProfile) { + await session.setAccountData("org.matrix.msc3815.profile", profile); +} + +export async function updateWorldProfile(session: Session, world: Room) { + const profile = await getUserProfile(session); + + if (profile) { + if (profile && profile.avatar_url) { await session.hsApi.sendState(world.id, "org.matrix.msc3815.world.member", session.userId, { - avatar_url: profileEvent.event.content.avatar_url, + avatar_url: profile.avatar_url, + avatar_preview_url: profile.avatar_preview_url, }); } } + + return profile; } export async function isValidUserId(hsApi: HomeServerApi, userId: string) { diff --git a/src/ui/views/HydrogenRootView.tsx b/src/ui/views/HydrogenRootView.tsx index 2ce0c2af4..d26cf6ff5 100644 --- a/src/ui/views/HydrogenRootView.tsx +++ b/src/ui/views/HydrogenRootView.tsx @@ -366,15 +366,14 @@ function useSession(client: Client, platform: Platform, urlRouter: URLRouter) { }, [client, session]); const oidcCompleteError = useOidcComplete(platform, urlRouter, login); - const profileRoom = useUserProfile(client, session); + useUserProfile(client, session); - const loading = loadingInitialSession || loggingIn || loggingOut || (session && !profileRoom); + const loading = loadingInitialSession || loggingIn || loggingOut; const error = initialSessionLoadError || errorLoggingIn || errorLoggingOut; const errorMsg = oidcCompleteError ?? error?.message; return { session, - profileRoom, loadInitialSession, login, logout, @@ -388,11 +387,7 @@ export function HydrogenRootView() { const [{ client, containerEl, platform, navigation, urlRouter, logger }] = useState(initHydrogen); - const { session, profileRoom, loadInitialSession, login, logout, loading, errorMsg } = useSession( - client, - platform, - urlRouter - ); + const { session, loadInitialSession, login, logout, loading, errorMsg } = useSession(client, platform, urlRouter); useEffect(() => { return () => { @@ -410,11 +405,10 @@ export function HydrogenRootView() { urlRouter, logger, session, - profileRoom, login, logout, }), - [client, platform, navigation, containerEl, urlRouter, logger, session, profileRoom, login, logout] + [client, platform, navigation, containerEl, urlRouter, logger, session, login, logout] ); const previewPath = useMatch({ path: "/preview" }); diff --git a/src/ui/views/session/user-profile/Edit3DAvatar.tsx b/src/ui/views/session/user-profile/Edit3DAvatar.tsx index b00b5bdbf..5df751456 100644 --- a/src/ui/views/session/user-profile/Edit3DAvatar.tsx +++ b/src/ui/views/session/user-profile/Edit3DAvatar.tsx @@ -18,13 +18,14 @@ import { AutoFileUpload, AutoUploadInfo } from "../../components/AutoFileUpload" import { Label } from "../../../atoms/text/Label"; import { Icon } from "../../../atoms/icon/Icon"; import UploadIC from "../../../../../res/ic/upload.svg"; +import { setUserProfile } from "../../../utils/matrixUtils"; interface Edit3DAvatarProps { renderTrigger: (openModal: () => void) => ReactNode; } export function Edit3DAvatar({ renderTrigger }: Edit3DAvatarProps) { - const { session, profileRoom } = useHydrogen(true); + const { session } = useHydrogen(true); const [isOpen, setIsOpen] = useState(false); const openModal = () => setIsOpen(true); const closeModal = () => setIsOpen(false); @@ -34,21 +35,18 @@ export function Edit3DAvatar({ renderTrigger }: Edit3DAvatarProps) { const saveChanges = () => { if (!avatarInfo.mxc && !previewInfo.mxc) return; - const update = (avatarUrl: string, previewUrl: string) => { - session.hsApi.sendState(profileRoom.id, "org.matrix.msc3815.world.profile", "", { + + const update = (avatarUrl?: string, previewUrl?: string) => { + setUserProfile(session, { avatar_url: avatarUrl, avatar_preview_url: previewUrl, }); }; - if (avatarInfo.mxc && previewInfo.mxc) { + + if (avatarInfo.mxc || previewInfo.mxc) { update(avatarInfo.mxc, previewInfo.mxc); - } else { - profileRoom.getStateEvent("org.matrix.msc3815.world.profile").then((event) => { - const avatarUrl: string = event?.event.content.avatar_url; - const avatarPreviewUrl: string = event?.event.content.avatar_preview_url; - update(avatarInfo.mxc ?? avatarUrl, previewInfo.mxc ?? avatarPreviewUrl); - }); } + closeModal(); }; diff --git a/src/ui/views/session/user-profile/UserProfileOverview.tsx b/src/ui/views/session/user-profile/UserProfileOverview.tsx index 23d14afd0..d635d93c9 100644 --- a/src/ui/views/session/user-profile/UserProfileOverview.tsx +++ b/src/ui/views/session/user-profile/UserProfileOverview.tsx @@ -16,7 +16,6 @@ import { getAvatarHttpUrl, getHttpUrl } from "../../../utils/avatar"; import { Footer } from "../../../atoms/footer/Footer"; import { Button } from "../../../atoms/button/Button"; import { useDebounce } from "../../../hooks/useDebounce"; -import { use3DAvatar } from "../../../hooks/use3DAvatar"; import { Edit3DAvatar } from "./Edit3DAvatar"; import "./UserProfileOverview.css"; import { AvatarPicker } from "../../components/avatar-picker/AvatarPicker"; @@ -36,8 +35,8 @@ import { OverlayWindow, overlayWindowAtom } from "../../../state/overlayWindow"; import { userProfileAtom } from "../../../state/userProfile"; export function UserProfileOverview() { - const { session, platform, profileRoom } = useHydrogen(true); - const { displayName, avatarUrl } = useAtomValue(userProfileAtom); + const { session, platform } = useHydrogen(true); + const { displayName, avatarUrl, avatarModelUrl, avatarModelPreviewUrl } = useAtomValue(userProfileAtom); const setOverlayWindow = useSetAtom(overlayWindowAtom); const [newDisplayName, setNewDisplayName] = useState(displayName); @@ -57,8 +56,6 @@ export function UserProfileOverview() { return [{ value: RenderQualitySetting.Auto, label: `Auto (${currentQuality?.label})` }, ...RenderQualityOptions]; }, [mainThread.quality]); - const [, tDAvatarPreviewUrl] = use3DAvatar(profileRoom); - let httpAvatarUrl = avatarUrl ? getAvatarHttpUrl(avatarUrl, 150, platform, session.mediaRepository) ?? undefined : undefined; @@ -178,39 +175,31 @@ export function UserProfileOverview() { ( - - )} - /> - } - /> - } - /> - ) + ( + + )} + /> + } + /> + } + /> } > - Your 3D avatar preview will appear here. - - ) + + Your 3D avatar preview will appear here. + } /> From d60e35305a27e4e55a9e26b4f5edd8a2e01ecdbe Mon Sep 17 00:00:00 2001 From: Robert Long Date: Thu, 27 Apr 2023 10:27:08 -0700 Subject: [PATCH 2/4] Fix home world world.member event perms --- src/ui/hooks/useHomeWorld.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/hooks/useHomeWorld.ts b/src/ui/hooks/useHomeWorld.ts index 5ae0f4269..89dc72ad3 100644 --- a/src/ui/hooks/useHomeWorld.ts +++ b/src/ui/hooks/useHomeWorld.ts @@ -31,7 +31,7 @@ async function createHomeWorld(session: Session) { "m.room.encrypted": 100, "m.sticker": 0, "org.matrix.msc3401.call.member": 0, - "org.matrix.msc3815.member.world": 0, + "org.matrix.msc3815.world.member": 0, }, users: { [session.userId]: 100, From 8b17113f33807bf1fc8978ec3183e79393e4da7d Mon Sep 17 00:00:00 2001 From: Robert Long Date: Thu, 27 Apr 2023 10:52:14 -0700 Subject: [PATCH 3/4] Update world members map on game thread --- src/plugins/thirdroom/thirdroom.common.ts | 10 +++++++++ src/plugins/thirdroom/thirdroom.game.ts | 6 +++++ src/ui/hooks/useStateEvents.ts | 14 +++++++----- src/ui/hooks/useUpdateWorldMembers.ts | 23 ++++++++++++++++++++ src/ui/utils/matrixUtils.ts | 2 +- src/ui/views/session/world/WorldRootView.tsx | 3 +++ 6 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 src/ui/hooks/useUpdateWorldMembers.ts diff --git a/src/plugins/thirdroom/thirdroom.common.ts b/src/plugins/thirdroom/thirdroom.common.ts index ed66c12d7..d64e8cf00 100644 --- a/src/plugins/thirdroom/thirdroom.common.ts +++ b/src/plugins/thirdroom/thirdroom.common.ts @@ -14,6 +14,7 @@ export enum ThirdRoomMessageType { GLTFViewerLoadError = "gltf-viewer-load-error", ReticleFocus = "reticle-focus", FindResourceRetainers = "find-resource-retainers", + UpdateWorldMembers = "update-world-members", } export interface EnterWorldMessage { @@ -90,3 +91,12 @@ export interface FindResourceRetainersMessage { type: ThirdRoomMessageType.FindResourceRetainers; resourceId: number; } + +export interface WorldMember { + avatarUrl?: string; +} + +export interface UpdateWorldMembersMessage { + type: ThirdRoomMessageType.UpdateWorldMembers; + worldMembers: Map; +} diff --git a/src/plugins/thirdroom/thirdroom.game.ts b/src/plugins/thirdroom/thirdroom.game.ts index 2fcb1685b..8e904b3bb 100644 --- a/src/plugins/thirdroom/thirdroom.game.ts +++ b/src/plugins/thirdroom/thirdroom.game.ts @@ -24,6 +24,7 @@ import { EnteredWorldMessage, EnterWorldErrorMessage, FindResourceRetainersMessage, + UpdateWorldMembersMessage, } from "./thirdroom.common"; import { createNodeFromGLTFURI, loadDefaultGLTFScene, loadGLTF } from "../../engine/gltf/gltf.game"; import { createRemotePerspectiveCamera, getCamera } from "../../engine/camera/camera.game"; @@ -218,6 +219,7 @@ export const ThirdRoomModule = defineModule({ const disposables = [ registerMessageHandler(ctx, ThirdRoomMessageType.LoadWorld, onLoadWorld), registerMessageHandler(ctx, ThirdRoomMessageType.EnterWorld, onEnterWorld), + registerMessageHandler(ctx, ThirdRoomMessageType.UpdateWorldMembers, onUpdateWorldMembers), registerMessageHandler(ctx, ThirdRoomMessageType.ExitWorld, onExitWorld), registerMessageHandler(ctx, NetworkMessageType.AddPeerId, onAddPeerId), registerMessageHandler(ctx, ThirdRoomMessageType.PrintThreadState, onPrintThreadState), @@ -393,6 +395,10 @@ async function onEnterWorld(ctx: GameState, message: EnterWorldMessage) { } } +function onUpdateWorldMembers(ctx: GameState, message: UpdateWorldMembersMessage) { + console.log("onUpdateWorldMembers", message); +} + function onExitWorld(ctx: GameState, message: ExitWorldMessage) { disposeWorld(ctx.worldResource); ctx.sendMessage(Thread.Main, { diff --git a/src/ui/hooks/useStateEvents.ts b/src/ui/hooks/useStateEvents.ts index b23a76119..d386306b0 100644 --- a/src/ui/hooks/useStateEvents.ts +++ b/src/ui/hooks/useStateEvents.ts @@ -4,17 +4,21 @@ import { useEffect, useState } from "react"; import { useIsMounted } from "./useIsMounted"; import { useObservableMap } from "./useObservableMap"; -export function useStateEvents(room: Room, eventType: string) { +export function useStateEvents(room: Room | undefined, eventType: string) { const [eventObservable, setEventObservable] = useState(new ObservableMap()); const isMounted = useIsMounted(); const events = useObservableMap(() => eventObservable, [eventObservable]); useEffect(() => { - room.observeStateType(eventType).then((stateObserver) => { - if (!isMounted()) return; - setEventObservable(stateObserver); - }); + if (!room) { + setEventObservable(new ObservableMap()); + } else { + room.observeStateType(eventType).then((stateObserver) => { + if (!isMounted()) return; + setEventObservable(stateObserver); + }); + } }, [eventType, room, isMounted]); return events; diff --git a/src/ui/hooks/useUpdateWorldMembers.ts b/src/ui/hooks/useUpdateWorldMembers.ts new file mode 100644 index 000000000..cff647fba --- /dev/null +++ b/src/ui/hooks/useUpdateWorldMembers.ts @@ -0,0 +1,23 @@ +import { Room } from "@thirdroom/hydrogen-view-sdk"; +import { useEffect } from "react"; + +import { Thread } from "../../engine/module/module.common"; +import { ThirdRoomMessageType, UpdateWorldMembersMessage, WorldMember } from "../../plugins/thirdroom/thirdroom.common"; +import { useMainThreadContext } from "./useMainThread"; +import { useStateEvents } from "./useStateEvents"; + +export function useUpdateWorldMembers(world?: Room) { + const worldMembersEvents = useStateEvents(world, "org.matrix.msc3815.world.member"); + const mainThread = useMainThreadContext(); + + useEffect(() => { + const worldMembers: Map = new Map( + Array.from(worldMembersEvents).map(([userId, event]) => [userId, { avatarUrl: event.content.avatar_url }]) + ); + + mainThread.sendMessage(Thread.Game, { + type: ThirdRoomMessageType.UpdateWorldMembers, + worldMembers, + }); + }, [mainThread, worldMembersEvents]); +} diff --git a/src/ui/utils/matrixUtils.ts b/src/ui/utils/matrixUtils.ts index 002cbf388..39dcc905f 100644 --- a/src/ui/utils/matrixUtils.ts +++ b/src/ui/utils/matrixUtils.ts @@ -71,7 +71,7 @@ export function roomIdToAlias(rooms: ObservableMap, roomId: string return rooms.get(roomId)?.canonicalAlias ?? undefined; } -interface UserProfile { +export interface UserProfile { avatar_url?: string; avatar_preview_url?: string; } diff --git a/src/ui/views/session/world/WorldRootView.tsx b/src/ui/views/session/world/WorldRootView.tsx index c2d202466..8a2497cd3 100644 --- a/src/ui/views/session/world/WorldRootView.tsx +++ b/src/ui/views/session/world/WorldRootView.tsx @@ -5,6 +5,7 @@ import { useEffect, useState } from "react"; import { useHydrogen } from "../../../hooks/useHydrogen"; import { useIsMounted } from "../../../hooks/useIsMounted"; import { useRoom } from "../../../hooks/useRoom"; +import { useUpdateWorldMembers } from "../../../hooks/useUpdateWorldMembers"; import { useWorldPath } from "../../../hooks/useWorld"; import { useWorldLoader } from "../../../hooks/useWorldLoader"; import { overlayVisibilityAtom } from "../../../state/overlayVisibility"; @@ -30,6 +31,8 @@ export default function WorldRootView() { const [roomId, reloadId] = useWorldPath(); const navigatedWorld = useRoom(session, roomId); + useUpdateWorldMembers(navigatedWorld); + /** * Handle loading are reloading */ From 4f07ba824b239d3bf915c70fb7448b059e4bfe64 Mon Sep 17 00:00:00 2001 From: Robert Long Date: Thu, 27 Apr 2023 10:56:27 -0700 Subject: [PATCH 4/4] convert mxc urls to https --- src/ui/hooks/useUpdateWorldMembers.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ui/hooks/useUpdateWorldMembers.ts b/src/ui/hooks/useUpdateWorldMembers.ts index cff647fba..23c8a7108 100644 --- a/src/ui/hooks/useUpdateWorldMembers.ts +++ b/src/ui/hooks/useUpdateWorldMembers.ts @@ -3,21 +3,27 @@ import { useEffect } from "react"; import { Thread } from "../../engine/module/module.common"; import { ThirdRoomMessageType, UpdateWorldMembersMessage, WorldMember } from "../../plugins/thirdroom/thirdroom.common"; +import { useHydrogen } from "./useHydrogen"; import { useMainThreadContext } from "./useMainThread"; import { useStateEvents } from "./useStateEvents"; export function useUpdateWorldMembers(world?: Room) { + const { session } = useHydrogen(true); const worldMembersEvents = useStateEvents(world, "org.matrix.msc3815.world.member"); const mainThread = useMainThreadContext(); useEffect(() => { const worldMembers: Map = new Map( - Array.from(worldMembersEvents).map(([userId, event]) => [userId, { avatarUrl: event.content.avatar_url }]) + Array.from(worldMembersEvents).map(([userId, event]) => { + const avatarMxcUrl = event.content.avatar_url; + const avatarUrl = avatarMxcUrl ? session.mediaRepository.mxcUrl(avatarMxcUrl) : undefined; + return [userId, { avatarUrl }]; + }) ); mainThread.sendMessage(Thread.Game, { type: ThirdRoomMessageType.UpdateWorldMembers, worldMembers, }); - }, [mainThread, worldMembersEvents]); + }, [mainThread, worldMembersEvents, session]); }