move room user profile to new sdk
This commit is contained in:
parent
baf281bca9
commit
23537b7a76
12 changed files with 360 additions and 284 deletions
|
|
@ -4,22 +4,15 @@ import FocusTrap from 'focus-trap-react';
|
|||
import { useCloseUserRoomProfile, useUserRoomProfileState } from '../state/hooks/userRoomProfile';
|
||||
import { UserRoomProfile } from './user-profile';
|
||||
import { UserRoomProfileState } from '../state/userRoomProfile';
|
||||
import { useAllJoinedRoomsSet, useGetRoom } from '../hooks/useGetRoom';
|
||||
import { stopPropagation } from '../utils/keyboard';
|
||||
import { SpaceProvider } from '../hooks/useSpace';
|
||||
import { RoomProvider } from '../hooks/useRoom';
|
||||
|
||||
function UserRoomProfileContextMenu({ state }: { state: UserRoomProfileState }) {
|
||||
const { roomId, spaceId, userId, cords, position } = state;
|
||||
const allJoinedRooms = useAllJoinedRoomsSet();
|
||||
const getRoom = useGetRoom(allJoinedRooms);
|
||||
const room = getRoom(roomId);
|
||||
const space = spaceId ? getRoom(spaceId) : undefined;
|
||||
|
||||
const close = useCloseUserRoomProfile();
|
||||
|
||||
if (!room) return null;
|
||||
|
||||
return (
|
||||
<PopOut
|
||||
anchor={cords}
|
||||
|
|
@ -35,8 +28,8 @@ function UserRoomProfileContextMenu({ state }: { state: UserRoomProfileState })
|
|||
}}
|
||||
>
|
||||
<Menu style={{ width: toRem(340) }}>
|
||||
<SpaceProvider value={space ?? null}>
|
||||
<RoomProvider value={room}>
|
||||
<SpaceProvider value={spaceId ?? null}>
|
||||
<RoomProvider value={roomId}>
|
||||
<UserRoomProfile userId={userId} />
|
||||
</RoomProvider>
|
||||
</SpaceProvider>
|
||||
|
|
|
|||
|
|
@ -11,13 +11,14 @@ import {
|
|||
toRem,
|
||||
} from 'folds';
|
||||
import React, { ReactNode, useId } from 'react';
|
||||
import { Presence } from 'neurone/lib/api/types';
|
||||
import * as css from './styles.css';
|
||||
import { Presence, usePresenceLabel } from '../../hooks/useUserPresence';
|
||||
import { usePresenceLabel } from '../../hooks/useUserPresence';
|
||||
|
||||
const PresenceToColor: Record<Presence, MainColor> = {
|
||||
[Presence.Online]: 'Success',
|
||||
[Presence.Unavailable]: 'Warning',
|
||||
[Presence.Offline]: 'Secondary',
|
||||
online: 'Success',
|
||||
unavailable: 'Warning',
|
||||
offline: 'Secondary',
|
||||
};
|
||||
|
||||
type PresenceBadgeProps = {
|
||||
|
|
@ -51,7 +52,7 @@ export function PresenceBadge({ presence, status, size }: PresenceBadgeProps) {
|
|||
ref={triggerRef}
|
||||
size={size}
|
||||
variant={PresenceToColor[presence]}
|
||||
fill={presence === Presence.Offline ? 'Soft' : 'Solid'}
|
||||
fill={presence === 'offline' ? 'Soft' : 'Solid'}
|
||||
radii="Pill"
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { isKeyHotkey } from 'is-hotkey';
|
|||
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
|
||||
import { PowerColorBadge, PowerIcon } from '../power';
|
||||
import { getPowerTagIconSrc } from '../../hooks/useMemberPowerTag';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import { useRoom } from '../../hooks/useRoom';
|
||||
|
|
@ -14,18 +13,19 @@ import { useOpenRoomSettings } from '../../state/hooks/roomSettings';
|
|||
import { useOpenSpaceSettings } from '../../state/hooks/spaceSettings';
|
||||
import { SpaceSettingsPage } from '../../state/spaceSettings';
|
||||
import { RoomSettingsPage } from '../../state/roomSettings';
|
||||
import { useClient } from '../../hooks/useClient';
|
||||
|
||||
export function CreatorChip() {
|
||||
const mx = useMatrixClient();
|
||||
const ct = useClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const room = useRoom();
|
||||
const space = useSpaceOptionally();
|
||||
const roomId = useRoom();
|
||||
const spaceId = useSpaceOptionally();
|
||||
const openRoomSettings = useOpenRoomSettings();
|
||||
const openSpaceSettings = useOpenSpaceSettings();
|
||||
|
||||
const [cords, setCords] = useState<RectCords>();
|
||||
const tag = useRoomCreatorsTag();
|
||||
const tagIconSrc = tag.icon && getPowerTagIconSrc(mx, useAuthentication, tag.icon);
|
||||
const tagIconSrc = tag.icon && getPowerTagIconSrc(ct, useAuthentication, tag.icon);
|
||||
|
||||
const open: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
setCords(evt.currentTarget.getBoundingClientRect());
|
||||
|
|
@ -58,14 +58,10 @@ export function CreatorChip() {
|
|||
size="300"
|
||||
radii="300"
|
||||
onClick={() => {
|
||||
if (room.isSpaceRoom()) {
|
||||
openSpaceSettings(
|
||||
room.roomId,
|
||||
space?.roomId,
|
||||
SpaceSettingsPage.PermissionsPage
|
||||
);
|
||||
if (ct.spaceRooms.read.has(roomId)) {
|
||||
openSpaceSettings(roomId, spaceId, SpaceSettingsPage.PermissionsPage);
|
||||
} else {
|
||||
openRoomSettings(room.roomId, space?.roomId, RoomSettingsPage.PermissionsPage);
|
||||
openRoomSettings(roomId, spaceId, RoomSettingsPage.PermissionsPage);
|
||||
}
|
||||
close();
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,20 @@ import {
|
|||
import React, { MouseEventHandler, useCallback, useState } from 'react';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { isKeyHotkey } from 'is-hotkey';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import produce from 'immer';
|
||||
import { putStateEvent } from 'neurone/lib/api/matrix/client/v3/rooms';
|
||||
import { parseMatrixResp, printMatrixError } from 'neurone/lib/matrix-request';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { PowerColorBadge, PowerIcon } from '../power';
|
||||
import { useGetMemberPowerLevel, usePowerLevels } from '../../hooks/usePowerLevels';
|
||||
import { getPowers, usePowerLevelTags } from '../../hooks/usePowerLevelTags';
|
||||
import {
|
||||
applyPermissionPower,
|
||||
PermissionLocation,
|
||||
useGetMemberPowerLevel,
|
||||
usePowerLevelsContext,
|
||||
} from '../../hooks/usePowerLevels';
|
||||
import { getPowers, usePowerLevelTagsContext } from '../../hooks/usePowerLevelTags';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import { StateEvent } from '../../../types/matrix/room';
|
||||
import { StateEvent, StateEventType } from '../../../types/matrix/room';
|
||||
import { useOpenRoomSettings } from '../../state/hooks/roomSettings';
|
||||
import { RoomSettingsPage } from '../../state/roomSettings';
|
||||
import { useRoom } from '../../hooks/useRoom';
|
||||
|
|
@ -40,9 +47,10 @@ import { SpaceSettingsPage } from '../../state/spaceSettings';
|
|||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||
import { BreakWord } from '../../styles/Text.css';
|
||||
import { getPowerTagIconSrc, useGetMemberPowerTag } from '../../hooks/useMemberPowerTag';
|
||||
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
||||
import { useMemberPowerCompare } from '../../hooks/useMemberPowerCompare';
|
||||
import { useClient } from '../../hooks/useClient';
|
||||
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||
|
||||
type SelfDemoteAlertProps = {
|
||||
power: number;
|
||||
|
|
@ -145,30 +153,29 @@ function SharedPowerAlert({ power, onCancel, onChange }: SharedPowerAlertProps)
|
|||
}
|
||||
|
||||
export function PowerChip({ userId }: { userId: string }) {
|
||||
const mx = useMatrixClient();
|
||||
const room = useRoom();
|
||||
const space = useSpaceOptionally();
|
||||
const ct = useClient();
|
||||
const roomId = useRoom();
|
||||
const spaceId = useSpaceOptionally();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const openRoomSettings = useOpenRoomSettings();
|
||||
const openSpaceSettings = useOpenSpaceSettings();
|
||||
|
||||
const powerLevels = usePowerLevels(room);
|
||||
const creators = useRoomCreators(room);
|
||||
|
||||
const creators = useRoomCreators(roomId);
|
||||
const powerLevels = usePowerLevelsContext();
|
||||
const permissions = useRoomPermissions(creators, powerLevels);
|
||||
const getMemberPowerLevel = useGetMemberPowerLevel(powerLevels);
|
||||
const { hasMorePower } = useMemberPowerCompare(creators, powerLevels);
|
||||
|
||||
const powerLevelTags = usePowerLevelTags(room, powerLevels);
|
||||
const getMemberPowerTag = useGetMemberPowerTag(room, creators, powerLevels);
|
||||
const powerLevelTags = usePowerLevelTagsContext();
|
||||
const getMemberPowerTag = useGetMemberPowerTag(creators, powerLevels);
|
||||
|
||||
const myUserId = mx.getSafeUserId();
|
||||
const myUserId = ct.session.userId;
|
||||
const canChangePowers =
|
||||
permissions.stateEvent(StateEvent.RoomPowerLevels, myUserId) &&
|
||||
(myUserId === userId ? true : hasMorePower(myUserId, userId));
|
||||
|
||||
const tag = getMemberPowerTag(userId);
|
||||
const tagIconSrc = tag.icon && getPowerTagIconSrc(mx, useAuthentication, tag.icon);
|
||||
const tagIconSrc = tag.icon && getPowerTagIconSrc(ct, useAuthentication, tag.icon);
|
||||
|
||||
const [cords, setCords] = useState<RectCords>();
|
||||
|
||||
|
|
@ -178,12 +185,28 @@ export function PowerChip({ userId }: { userId: string }) {
|
|||
|
||||
const close = () => setCords(undefined);
|
||||
|
||||
const [powerState, changePower] = useAsyncCallback<undefined, Error, [number]>(
|
||||
const [powerState, changePower] = useAsyncCallback<void, Error, [number]>(
|
||||
useCallback(
|
||||
async (power: number) => {
|
||||
await mx.setPowerLevel(room.roomId, userId, power);
|
||||
const editedPowerLevels = produce(powerLevels, (draftPowerLevels) => {
|
||||
const location: PermissionLocation = {
|
||||
user: true,
|
||||
key: userId,
|
||||
};
|
||||
applyPermissionPower(draftPowerLevels, location, power);
|
||||
|
||||
return draftPowerLevels;
|
||||
});
|
||||
const resp = await ct.requestAuthJSON(
|
||||
putStateEvent({
|
||||
pathParams: { roomId, eventType: StateEventType.RoomPowerLevels, stateKey: '' },
|
||||
body: editedPowerLevels,
|
||||
})
|
||||
);
|
||||
const [err] = await parseMatrixResp(resp);
|
||||
if (err) throw new Error(printMatrixError(err));
|
||||
},
|
||||
[mx, userId, room]
|
||||
[ct, userId, roomId, powerLevels]
|
||||
)
|
||||
);
|
||||
const changing = powerState.status === AsyncStatus.Loading;
|
||||
|
|
@ -196,11 +219,11 @@ export function PowerChip({ userId }: { userId: string }) {
|
|||
if (!canChangePowers) return;
|
||||
if (power === getMemberPowerLevel(userId)) return;
|
||||
|
||||
if (userId === mx.getSafeUserId()) {
|
||||
if (userId === myUserId) {
|
||||
setSelfDemote(power);
|
||||
return;
|
||||
}
|
||||
if (!creators.has(myUserId) && power === getMemberPowerLevel(myUserId)) {
|
||||
if (!creators?.has(myUserId) && power === getMemberPowerLevel(myUserId)) {
|
||||
setSharedPower(power);
|
||||
return;
|
||||
}
|
||||
|
|
@ -252,10 +275,10 @@ export function PowerChip({ userId }: { userId: string }) {
|
|||
{getPowers(powerLevelTags).map((power) => {
|
||||
const powerTag = powerLevelTags[power];
|
||||
const powerTagIconSrc =
|
||||
powerTag.icon && getPowerTagIconSrc(mx, useAuthentication, powerTag.icon);
|
||||
powerTag.icon && getPowerTagIconSrc(ct, useAuthentication, powerTag.icon);
|
||||
|
||||
const selected = getMemberPowerLevel(userId) === power;
|
||||
const canAssignPower = creators.has(myUserId)
|
||||
const canAssignPower = creators?.has(myUserId)
|
||||
? true
|
||||
: power <= getMemberPowerLevel(myUserId);
|
||||
|
||||
|
|
@ -293,18 +316,10 @@ export function PowerChip({ userId }: { userId: string }) {
|
|||
size="300"
|
||||
radii="300"
|
||||
onClick={() => {
|
||||
if (room.isSpaceRoom()) {
|
||||
openSpaceSettings(
|
||||
room.roomId,
|
||||
space?.roomId,
|
||||
SpaceSettingsPage.PermissionsPage
|
||||
);
|
||||
if (ct.spaceRooms.read.has(roomId)) {
|
||||
openSpaceSettings(roomId, spaceId, SpaceSettingsPage.PermissionsPage);
|
||||
} else {
|
||||
openRoomSettings(
|
||||
room.roomId,
|
||||
space?.roomId,
|
||||
RoomSettingsPage.PermissionsPage
|
||||
);
|
||||
openRoomSettings(roomId, spaceId, RoomSettingsPage.PermissionsPage);
|
||||
}
|
||||
close();
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React, { MouseEventHandler, useCallback, useMemo, useState } from 'react'
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { isKeyHotkey } from 'is-hotkey';
|
||||
import { Room } from 'matrix-js-sdk';
|
||||
import {
|
||||
PopOut,
|
||||
Menu,
|
||||
|
|
@ -20,7 +19,7 @@ import {
|
|||
Scroll,
|
||||
Avatar,
|
||||
} from 'folds';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { putAccountData } from 'neurone/lib/api/matrix/client/v3/user';
|
||||
import { getMxIdServer } from '../../utils/matrix';
|
||||
import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
|
|
@ -28,27 +27,27 @@ import { copyToClipboard } from '../../utils/dom';
|
|||
import { getExploreServerPath } from '../../pages/pathUtils';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||
import { factoryRoomIdByAtoZ } from '../../utils/sort';
|
||||
import {
|
||||
useMutualRooms,
|
||||
useMutualRoomsSupport,
|
||||
useUnstableMutualRoomsSupport,
|
||||
} from '../../hooks/useMutualRooms';
|
||||
import { useMutualRooms } from '../../hooks/useMutualRooms';
|
||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||
import { useDirectRooms } from '../../pages/client/direct/useDirectRooms';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { useAllJoinedRoomsSet, useGetRoom } from '../../hooks/useGetRoom';
|
||||
import { RoomAvatar, RoomIcon } from '../room-avatar';
|
||||
import { getDirectRoomAvatarUrl, getRoomAvatarUrl } from '../../utils/room';
|
||||
import { nameInitials } from '../../utils/common';
|
||||
import { getMatrixToUser } from '../../plugins/matrix-to';
|
||||
import { useTimeoutToggle } from '../../hooks/useTimeoutToggle';
|
||||
import { useIgnoredUsers } from '../../hooks/useIgnoredUsers';
|
||||
import { getIgnoredUsersContent, useIgnoredUsers } from '../../hooks/useIgnoredUsers';
|
||||
import { CutoutCard } from '../cutout-card';
|
||||
import { SettingTile } from '../setting-tile';
|
||||
import { useClient } from '../../hooks/useClient';
|
||||
import { AccountDataType } from '../../../types/matrix/accountData';
|
||||
import { useRoomHeroes } from '../../hooks/storage/useRoomHeros';
|
||||
import { useGetRoomName } from '../../hooks/storage/useGetRoomName';
|
||||
import { useGetRoomAvatar } from '../../hooks/storage/useGetRoomAvatar';
|
||||
import { mxcToHttp } from '../../../client/mxcToHttp';
|
||||
|
||||
export function ServerChip({ server }: { server: string }) {
|
||||
const mx = useMatrixClient();
|
||||
const myServer = getMxIdServer(mx.getSafeUserId());
|
||||
const ct = useClient();
|
||||
const myServer = getMxIdServer(ct.session.userId);
|
||||
const navigate = useNavigate();
|
||||
const closeProfile = useCloseUserRoomProfile();
|
||||
const [copied, setCopied] = useTimeoutToggle();
|
||||
|
|
@ -229,24 +228,26 @@ export function ShareChip({ userId }: { userId: string }) {
|
|||
}
|
||||
|
||||
type MutualRoomsData = {
|
||||
rooms: Room[];
|
||||
spaces: Room[];
|
||||
directs: Room[];
|
||||
rooms: string[];
|
||||
spaces: string[];
|
||||
directs: string[];
|
||||
};
|
||||
|
||||
const EMPTY_LIST: string[] = [];
|
||||
export function MutualRoomsChip({ userId }: { userId: string }) {
|
||||
const mx = useMatrixClient();
|
||||
const mutualRoomSupported = useMutualRoomsSupport();
|
||||
const mutualRoomUnstable = useUnstableMutualRoomsSupport();
|
||||
const ct = useClient();
|
||||
const mutualRoomsState = useMutualRooms(userId);
|
||||
console.log(mutualRoomSupported, mutualRoomsState);
|
||||
|
||||
const { navigateRoom, navigateSpace } = useRoomNavigate();
|
||||
const closeUserRoomProfile = useCloseUserRoomProfile();
|
||||
const directs = useDirectRooms();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
|
||||
const allJoinedRooms = useAllJoinedRoomsSet();
|
||||
const getRoom = useGetRoom(allJoinedRooms);
|
||||
const [getRoomHeroes, roomToHeroMembersResult] = useRoomHeroes(
|
||||
mutualRoomsState.status === AsyncStatus.Success ? mutualRoomsState.data : EMPTY_LIST
|
||||
);
|
||||
const getRoomName = useGetRoomName(getRoomHeroes, roomToHeroMembersResult.value);
|
||||
const getRoomAvatar = useGetRoomAvatar(getRoomHeroes, roomToHeroMembersResult.value);
|
||||
|
||||
const [cords, setCords] = useState<RectCords>();
|
||||
|
||||
|
|
@ -264,36 +265,33 @@ export function MutualRoomsChip({ userId }: { userId: string }) {
|
|||
};
|
||||
|
||||
if (mutualRoomsState.status === AsyncStatus.Success) {
|
||||
const mutualRooms = mutualRoomsState.data
|
||||
.sort(factoryRoomIdByAtoZ(mx))
|
||||
.map(getRoom)
|
||||
.filter((room) => !!room);
|
||||
mutualRooms.forEach((room) => {
|
||||
if (room.isSpaceRoom()) {
|
||||
data.spaces.push(room);
|
||||
const mutualRooms = mutualRoomsState.data.sort(factoryRoomIdByAtoZ(getRoomName));
|
||||
|
||||
mutualRooms.forEach((roomId) => {
|
||||
if (ct.spaceRooms.read.has(roomId)) {
|
||||
data.spaces.push(roomId);
|
||||
return;
|
||||
}
|
||||
if (directs.includes(room.roomId)) {
|
||||
data.directs.push(room);
|
||||
if (directs.includes(roomId)) {
|
||||
data.directs.push(roomId);
|
||||
return;
|
||||
}
|
||||
data.rooms.push(room);
|
||||
data.rooms.push(roomId);
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}, [mutualRoomsState, getRoom, directs, mx]);
|
||||
}, [mutualRoomsState, getRoomName, directs, ct]);
|
||||
|
||||
if (
|
||||
userId === mx.getSafeUserId() ||
|
||||
(!mutualRoomSupported && !mutualRoomUnstable) ||
|
||||
mutualRoomsState.status === AsyncStatus.Error
|
||||
) {
|
||||
if (userId === ct.session.userId || mutualRoomsState.status === AsyncStatus.Error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const renderItem = (room: Room) => {
|
||||
const { roomId } = room;
|
||||
const renderItem = (roomId: string) => {
|
||||
const dm = directs.includes(roomId);
|
||||
const space = ct.spaceRooms.read.has(roomId);
|
||||
|
||||
const avatarUrl = getRoomAvatar(roomId);
|
||||
const roomName = getRoomName(roomId) ?? 'Unknown';
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
|
|
@ -304,7 +302,7 @@ export function MutualRoomsChip({ userId }: { userId: string }) {
|
|||
radii="300"
|
||||
style={{ paddingLeft: config.space.S100 }}
|
||||
onClick={() => {
|
||||
if (room.isSpaceRoom()) {
|
||||
if (space) {
|
||||
navigateSpace(roomId);
|
||||
} else {
|
||||
navigateRoom(roomId);
|
||||
|
|
@ -313,29 +311,37 @@ export function MutualRoomsChip({ userId }: { userId: string }) {
|
|||
}}
|
||||
before={
|
||||
<Avatar size="200" radii={dm ? '400' : '300'}>
|
||||
{dm || room.isSpaceRoom() ? (
|
||||
{dm || space ? (
|
||||
<RoomAvatar
|
||||
roomId={room.roomId}
|
||||
roomId={roomId}
|
||||
src={
|
||||
dm
|
||||
? getDirectRoomAvatarUrl(mx, room, 96, useAuthentication)
|
||||
: getRoomAvatarUrl(mx, room, 96, useAuthentication)
|
||||
avatarUrl
|
||||
? mxcToHttp(ct.session.baseUrl, avatarUrl, useAuthentication).thumbnail({
|
||||
width: 96,
|
||||
height: 96,
|
||||
method: 'crop',
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
alt={room.name}
|
||||
alt={roomName}
|
||||
renderFallback={() => (
|
||||
<Text as="span" size="H6">
|
||||
{nameInitials(room.name)}
|
||||
{nameInitials(roomName)}
|
||||
</Text>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<RoomIcon size="100" joinRule={room.getJoinRule()} roomType={room.getType()} />
|
||||
<RoomIcon
|
||||
size="100"
|
||||
joinRule={ct.roomToJoinRule.read.get(roomId)}
|
||||
roomType={ct.roomToType.read.get(roomId)}
|
||||
/>
|
||||
)}
|
||||
</Avatar>
|
||||
}
|
||||
>
|
||||
<Text size="B300" truncate>
|
||||
{room.name}
|
||||
{roomName}
|
||||
</Text>
|
||||
</MenuItem>
|
||||
);
|
||||
|
|
@ -443,7 +449,7 @@ export function IgnoredUserAlert() {
|
|||
}
|
||||
|
||||
export function OptionsChip({ userId }: { userId: string }) {
|
||||
const mx = useMatrixClient();
|
||||
const ct = useClient();
|
||||
const [cords, setCords] = useState<RectCords>();
|
||||
|
||||
const open: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
|
|
@ -459,8 +465,14 @@ export function OptionsChip({ userId }: { userId: string }) {
|
|||
useCallback(async () => {
|
||||
const users = ignoredUsers.filter((u) => u !== userId);
|
||||
if (!ignored) users.push(userId);
|
||||
await mx.setIgnoredUsers(users);
|
||||
}, [mx, ignoredUsers, userId, ignored])
|
||||
|
||||
return ct.requestAuthJSON(
|
||||
putAccountData({
|
||||
pathParams: { userId: ct.session.userId, type: AccountDataType.IgnoredUserList },
|
||||
body: getIgnoredUsersContent(users),
|
||||
})
|
||||
);
|
||||
}, [ct, ignoredUsers, userId, ignored])
|
||||
);
|
||||
const ignoring = ignoreState.status === AsyncStatus.Loading;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import { Box, Button, color, config, Icon, Icons, Spinner, Text, Input } from 'folds';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { postBan, postInvite, postKick, postUnban } from 'neurone/lib/api/matrix/client/v3/rooms';
|
||||
import { parseMatrixResp } from 'neurone/lib/matrix-request';
|
||||
import { useRoom } from '../../hooks/useRoom';
|
||||
import { CutoutCard } from '../cutout-card';
|
||||
import { SettingTile } from '../setting-tile';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { BreakWord } from '../../styles/Text.css';
|
||||
import { useSetting } from '../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../state/settings';
|
||||
import { timeDayMonYear, timeHourMinute } from '../../utils/time';
|
||||
import { useClient } from '../../hooks/useClient';
|
||||
|
||||
type UserKickAlertProps = {
|
||||
reason?: string;
|
||||
|
|
@ -64,18 +66,28 @@ type UserBanAlertProps = {
|
|||
ts?: number;
|
||||
};
|
||||
export function UserBanAlert({ userId, reason, canUnban, bannedBy, ts }: UserBanAlertProps) {
|
||||
const mx = useMatrixClient();
|
||||
const room = useRoom();
|
||||
const ct = useClient();
|
||||
const roomId = useRoom();
|
||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
||||
|
||||
const time = ts ? timeHourMinute(ts, hour24Clock) : undefined;
|
||||
const date = ts ? timeDayMonYear(ts, dateFormatString) : undefined;
|
||||
|
||||
const [unbanState, unban] = useAsyncCallback<undefined, Error, []>(
|
||||
const [unbanState, unban] = useAsyncCallback<void, Error, []>(
|
||||
useCallback(async () => {
|
||||
await mx.unban(room.roomId, userId);
|
||||
}, [mx, room, userId])
|
||||
const resp = await ct.requestAuthJSON(
|
||||
postUnban({
|
||||
pathParams: { roomId },
|
||||
body: { user_id: userId },
|
||||
})
|
||||
);
|
||||
|
||||
const [err] = await parseMatrixResp(resp);
|
||||
if (err) {
|
||||
throw new Error(err.data.error ?? 'Unknown error.');
|
||||
}
|
||||
}, [ct, roomId, userId])
|
||||
);
|
||||
const banning = unbanState.status === AsyncStatus.Loading;
|
||||
const error = unbanState.status === AsyncStatus.Error;
|
||||
|
|
@ -139,18 +151,25 @@ type UserInviteAlertProps = {
|
|||
ts?: number;
|
||||
};
|
||||
export function UserInviteAlert({ userId, reason, canKick, invitedBy, ts }: UserInviteAlertProps) {
|
||||
const mx = useMatrixClient();
|
||||
const room = useRoom();
|
||||
const ct = useClient();
|
||||
const roomId = useRoom();
|
||||
const [hour24Clock] = useSetting(settingsAtom, 'hour24Clock');
|
||||
const [dateFormatString] = useSetting(settingsAtom, 'dateFormatString');
|
||||
|
||||
const time = ts ? timeHourMinute(ts, hour24Clock) : undefined;
|
||||
const date = ts ? timeDayMonYear(ts, dateFormatString) : undefined;
|
||||
|
||||
const [kickState, kick] = useAsyncCallback<undefined, Error, []>(
|
||||
const [kickState, kick] = useAsyncCallback<void, Error, []>(
|
||||
useCallback(async () => {
|
||||
await mx.kick(room.roomId, userId);
|
||||
}, [mx, room, userId])
|
||||
const resp = await ct.requestAuthJSON(
|
||||
postKick({
|
||||
pathParams: { roomId },
|
||||
body: { user_id: userId },
|
||||
})
|
||||
);
|
||||
const [err] = await parseMatrixResp(resp);
|
||||
if (err) throw new Error(err.data.error);
|
||||
}, [ct, roomId, userId])
|
||||
);
|
||||
const kicking = kickState.status === AsyncStatus.Loading;
|
||||
const error = kickState.status === AsyncStatus.Error;
|
||||
|
|
@ -215,8 +234,8 @@ type UserModerationProps = {
|
|||
canInvite: boolean;
|
||||
};
|
||||
export function UserModeration({ userId, canKick, canBan, canInvite }: UserModerationProps) {
|
||||
const mx = useMatrixClient();
|
||||
const room = useRoom();
|
||||
const ct = useClient();
|
||||
const roomId = useRoom();
|
||||
const reasonInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const getReason = useCallback((): string | undefined => {
|
||||
|
|
@ -227,22 +246,43 @@ export function UserModeration({ userId, canKick, canBan, canInvite }: UserModer
|
|||
return reason;
|
||||
}, []);
|
||||
|
||||
const [kickState, kick] = useAsyncCallback<undefined, Error, []>(
|
||||
const [kickState, kick] = useAsyncCallback<void, Error, []>(
|
||||
useCallback(async () => {
|
||||
await mx.kick(room.roomId, userId, getReason());
|
||||
}, [mx, room, userId, getReason])
|
||||
const resp = await ct.requestAuthJSON(
|
||||
postKick({
|
||||
pathParams: { roomId },
|
||||
body: { user_id: userId, reason: getReason() },
|
||||
})
|
||||
);
|
||||
const [err] = await parseMatrixResp(resp);
|
||||
if (err) throw new Error(err.data.error);
|
||||
}, [ct, roomId, userId, getReason])
|
||||
);
|
||||
|
||||
const [banState, ban] = useAsyncCallback<undefined, Error, []>(
|
||||
const [banState, ban] = useAsyncCallback<void, Error, []>(
|
||||
useCallback(async () => {
|
||||
await mx.ban(room.roomId, userId, getReason());
|
||||
}, [mx, room, userId, getReason])
|
||||
const resp = await ct.requestAuthJSON(
|
||||
postBan({
|
||||
pathParams: { roomId },
|
||||
body: { user_id: userId, reason: getReason() },
|
||||
})
|
||||
);
|
||||
const [err] = await parseMatrixResp(resp);
|
||||
if (err) throw new Error(err.data.error);
|
||||
}, [ct, roomId, userId, getReason])
|
||||
);
|
||||
|
||||
const [inviteState, invite] = useAsyncCallback<undefined, Error, []>(
|
||||
const [inviteState, invite] = useAsyncCallback<void, Error, []>(
|
||||
useCallback(async () => {
|
||||
await mx.invite(room.roomId, userId, getReason());
|
||||
}, [mx, room, userId, getReason])
|
||||
const resp = await ct.requestAuthJSON(
|
||||
postInvite({
|
||||
pathParams: { roomId },
|
||||
body: { user_id: userId, reason: getReason() },
|
||||
})
|
||||
);
|
||||
const [err] = await parseMatrixResp(resp);
|
||||
if (err) throw new Error(err.data.error);
|
||||
}, [ct, roomId, userId, getReason])
|
||||
);
|
||||
|
||||
const disabled =
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import { Box, Button, config, Icon, Icons, Text } from 'folds';
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { ObjectReader } from 'neurone/lib/utils';
|
||||
import { UserHero, UserHeroName } from './UserHero';
|
||||
import { getMxIdServer, mxcUrlToHttp } from '../../utils/matrix';
|
||||
import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { getMxIdServer } from '../../utils/matrix';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { usePowerLevels } from '../../hooks/usePowerLevels';
|
||||
import {
|
||||
PowerLevelsContextProvider,
|
||||
usePowerLevels,
|
||||
usePowerLevelsState,
|
||||
} from '../../hooks/usePowerLevels';
|
||||
import { useRoom } from '../../hooks/useRoom';
|
||||
import { useUserPresence } from '../../hooks/useUserPresence';
|
||||
import { IgnoredUserAlert, MutualRoomsChip, OptionsChip, ServerChip, ShareChip } from './UserChips';
|
||||
|
|
@ -14,7 +17,6 @@ import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile';
|
|||
import { PowerChip } from './PowerChip';
|
||||
import { UserInviteAlert, UserBanAlert, UserModeration, UserKickAlert } from './UserModeration';
|
||||
import { useIgnoredUsers } from '../../hooks/useIgnoredUsers';
|
||||
import { useMembership } from '../../hooks/useMembership';
|
||||
import { Membership } from '../../../types/matrix/room';
|
||||
import { useRoomCreators } from '../../hooks/useRoomCreators';
|
||||
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
|
||||
|
|
@ -22,40 +24,54 @@ import { useMemberPowerCompare } from '../../hooks/useMemberPowerCompare';
|
|||
import { CreatorChip } from './CreatorChip';
|
||||
import { getDirectCreatePath, withSearchParam } from '../../pages/pathUtils';
|
||||
import { DirectCreateSearchParams } from '../../pages/paths';
|
||||
import { useRoomMember } from '../../hooks/useRoomMember';
|
||||
import { mxcToHttp } from '../../../client/mxcToHttp';
|
||||
import { useClient } from '../../hooks/useClient';
|
||||
import {
|
||||
PowerLevelTagsContextProvider,
|
||||
usePowerLevelTags,
|
||||
usePowerLevelTagsState,
|
||||
} from '../../hooks/usePowerLevelTags';
|
||||
|
||||
type UserRoomProfileProps = {
|
||||
userId: string;
|
||||
};
|
||||
export function UserRoomProfile({ userId }: UserRoomProfileProps) {
|
||||
const mx = useMatrixClient();
|
||||
const ct = useClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const navigate = useNavigate();
|
||||
const closeUserRoomProfile = useCloseUserRoomProfile();
|
||||
const ignoredUsers = useIgnoredUsers();
|
||||
const ignored = ignoredUsers.includes(userId);
|
||||
|
||||
const room = useRoom();
|
||||
const powerLevels = usePowerLevels(room);
|
||||
const creators = useRoomCreators(room);
|
||||
const roomId = useRoom();
|
||||
const stateObjResult = usePowerLevelsState(roomId);
|
||||
const powerLevels = usePowerLevels(stateObjResult.value);
|
||||
const creators = useRoomCreators(roomId);
|
||||
const powerLevelTagsResult = usePowerLevelTagsState(roomId);
|
||||
const powerLevelTags = usePowerLevelTags(powerLevels, powerLevelTagsResult.value);
|
||||
|
||||
const permissions = useRoomPermissions(creators, powerLevels);
|
||||
const { hasMorePower } = useMemberPowerCompare(creators, powerLevels);
|
||||
|
||||
const myUserId = mx.getSafeUserId();
|
||||
const creator = creators.has(userId);
|
||||
const myUserId = ct.session.userId;
|
||||
const creator = creators?.has(userId);
|
||||
|
||||
const canKickUser = permissions.action('kick', myUserId) && hasMorePower(myUserId, userId);
|
||||
const canBanUser = permissions.action('ban', myUserId) && hasMorePower(myUserId, userId);
|
||||
const canUnban = permissions.action('ban', myUserId);
|
||||
const canInvite = permissions.action('invite', myUserId);
|
||||
|
||||
const member = room.getMember(userId);
|
||||
const membership = useMembership(room, userId);
|
||||
const { memberEventResult, getMembership, getName, getAvatar } = useRoomMember(roomId, userId);
|
||||
|
||||
const membership = getMembership();
|
||||
|
||||
const server = getMxIdServer(userId);
|
||||
const displayName = getMemberDisplayName(room, userId);
|
||||
const avatarMxc = getMemberAvatarMxc(room, userId);
|
||||
const avatarUrl = (avatarMxc && mxcUrlToHttp(mx, avatarMxc, useAuthentication)) ?? undefined;
|
||||
const displayName = getName();
|
||||
const avatarMxc = getAvatar();
|
||||
const avatarUrl = avatarMxc
|
||||
? mxcToHttp(ct.session.baseUrl, avatarMxc, useAuthentication).url()
|
||||
: undefined;
|
||||
|
||||
const presence = useUserPresence(userId);
|
||||
|
||||
|
|
@ -68,75 +84,78 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
|
|||
};
|
||||
|
||||
return (
|
||||
<Box direction="Column">
|
||||
<UserHero
|
||||
userId={userId}
|
||||
avatarUrl={avatarUrl}
|
||||
presence={presence && presence.lastActiveTs !== 0 ? presence : undefined}
|
||||
/>
|
||||
<Box direction="Column" gap="500" style={{ padding: config.space.S400 }}>
|
||||
<Box direction="Column" gap="400">
|
||||
<Box gap="400" alignItems="Start">
|
||||
<UserHeroName displayName={displayName} userId={userId} />
|
||||
{userId !== myUserId && (
|
||||
<Box shrink="No">
|
||||
<Button
|
||||
size="300"
|
||||
variant="Primary"
|
||||
fill="Solid"
|
||||
radii="300"
|
||||
before={<Icon size="50" src={Icons.Message} filled />}
|
||||
onClick={handleMessage}
|
||||
>
|
||||
<Text size="B300">Message</Text>
|
||||
</Button>
|
||||
<PowerLevelsContextProvider value={powerLevels}>
|
||||
<PowerLevelTagsContextProvider value={powerLevelTags}>
|
||||
<Box direction="Column">
|
||||
<UserHero
|
||||
userId={userId}
|
||||
avatarUrl={avatarUrl}
|
||||
presence={presence && presence.lastActiveTs !== 0 ? presence : undefined}
|
||||
/>
|
||||
<Box direction="Column" gap="500" style={{ padding: config.space.S400 }}>
|
||||
<Box direction="Column" gap="400">
|
||||
<Box gap="400" alignItems="Start">
|
||||
<UserHeroName displayName={displayName} userId={userId} />
|
||||
{userId !== myUserId && (
|
||||
<Box shrink="No">
|
||||
<Button
|
||||
size="300"
|
||||
variant="Primary"
|
||||
fill="Solid"
|
||||
radii="300"
|
||||
before={<Icon size="50" src={Icons.Message} filled />}
|
||||
onClick={handleMessage}
|
||||
>
|
||||
<Text size="B300">Message</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box alignItems="Center" gap="200" wrap="Wrap">
|
||||
{server && <ServerChip server={server} />}
|
||||
<ShareChip userId={userId} />
|
||||
{creator ? <CreatorChip /> : <PowerChip userId={userId} />}
|
||||
{userId !== myUserId && <MutualRoomsChip userId={userId} />}
|
||||
{userId !== myUserId && <OptionsChip userId={userId} />}
|
||||
</Box>
|
||||
</Box>
|
||||
{ignored && <IgnoredUserAlert />}
|
||||
{memberEventResult.value && membership === Membership.Ban && (
|
||||
<UserBanAlert
|
||||
userId={userId}
|
||||
reason={ObjectReader.string(memberEventResult.value.event.content, 'reason')}
|
||||
canUnban={canUnban}
|
||||
bannedBy={memberEventResult.value.event.sender}
|
||||
ts={memberEventResult.value.event.origin_server_ts}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<Box alignItems="Center" gap="200" wrap="Wrap">
|
||||
{server && <ServerChip server={server} />}
|
||||
<ShareChip userId={userId} />
|
||||
{creator ? <CreatorChip /> : <PowerChip userId={userId} />}
|
||||
{userId !== myUserId && <MutualRoomsChip userId={userId} />}
|
||||
{userId !== myUserId && <OptionsChip userId={userId} />}
|
||||
{memberEventResult.value &&
|
||||
membership === Membership.Leave &&
|
||||
memberEventResult.value.event.sender !== userId && (
|
||||
<UserKickAlert
|
||||
reason={ObjectReader.string(memberEventResult.value.event.content, 'reason')}
|
||||
kickedBy={memberEventResult.value.event.sender}
|
||||
ts={memberEventResult.value.event.origin_server_ts}
|
||||
/>
|
||||
)}
|
||||
{memberEventResult.value && membership === Membership.Invite && (
|
||||
<UserInviteAlert
|
||||
userId={userId}
|
||||
reason={ObjectReader.string(memberEventResult.value.event.content, 'reason')}
|
||||
canKick={canKickUser}
|
||||
invitedBy={memberEventResult.value.event.sender}
|
||||
ts={memberEventResult.value.event.origin_server_ts}
|
||||
/>
|
||||
)}
|
||||
<UserModeration
|
||||
userId={userId}
|
||||
canInvite={canInvite && membership === Membership.Leave}
|
||||
canKick={canKickUser && membership === Membership.Join}
|
||||
canBan={canBanUser && membership !== Membership.Ban}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
{ignored && <IgnoredUserAlert />}
|
||||
{member && membership === Membership.Ban && (
|
||||
<UserBanAlert
|
||||
userId={userId}
|
||||
reason={member.events.member?.getContent().reason}
|
||||
canUnban={canUnban}
|
||||
bannedBy={member.events.member?.getSender()}
|
||||
ts={member.events.member?.getTs()}
|
||||
/>
|
||||
)}
|
||||
{member &&
|
||||
membership === Membership.Leave &&
|
||||
member.events.member &&
|
||||
member.events.member.getSender() !== userId && (
|
||||
<UserKickAlert
|
||||
reason={member.events.member?.getContent().reason}
|
||||
kickedBy={member.events.member?.getSender()}
|
||||
ts={member.events.member?.getTs()}
|
||||
/>
|
||||
)}
|
||||
{member && membership === Membership.Invite && (
|
||||
<UserInviteAlert
|
||||
userId={userId}
|
||||
reason={member.events.member?.getContent().reason}
|
||||
canKick={canKickUser}
|
||||
invitedBy={member.events.member?.getSender()}
|
||||
ts={member.events.member?.getTs()}
|
||||
/>
|
||||
)}
|
||||
<UserModeration
|
||||
userId={userId}
|
||||
canInvite={canInvite && membership === Membership.Leave}
|
||||
canKick={canKickUser && membership === Membership.Join}
|
||||
canBan={canBanUser && membership !== Membership.Ban}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</PowerLevelTagsContextProvider>
|
||||
</PowerLevelsContextProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
import { useCallback } from 'react';
|
||||
import { IPowerLevels, readPowerLevel } from './usePowerLevels';
|
||||
|
||||
export const useMemberPowerCompare = (creators: Set<string>, powerLevels: IPowerLevels) => {
|
||||
export const useMemberPowerCompare = (
|
||||
creators: Set<string> | undefined,
|
||||
powerLevels: IPowerLevels
|
||||
) => {
|
||||
/**
|
||||
* returns `true` if `userIdA` has more power than `userIdB`
|
||||
* returns `false` otherwise
|
||||
*/
|
||||
const hasMorePower = useCallback(
|
||||
(userIdA: string, userIdB: string): boolean => {
|
||||
const aIsCreator = creators.has(userIdA);
|
||||
const bIsCreator = creators.has(userIdB);
|
||||
const aIsCreator = creators?.has(userIdA);
|
||||
const bIsCreator = creators?.has(userIdB);
|
||||
if (aIsCreator && bIsCreator) return false;
|
||||
if (aIsCreator) return true;
|
||||
if (bIsCreator) return false;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import { useCallback } from 'react';
|
||||
import { MatrixClient, Method } from 'matrix-js-sdk';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
import { withSearchParams } from 'neurone/lib/api/utils';
|
||||
import { parseMatrixResp, printMatrixError } from 'neurone/lib/matrix-request';
|
||||
import { ClientContext } from '../../client/context';
|
||||
import { AsyncState, useAsyncCallbackValue } from './useAsyncCallback';
|
||||
import { useUnstableFeatureSupport, useSpecVersions } from './useSpecVersions';
|
||||
import { useClient } from './useClient';
|
||||
|
||||
export const useUnstableMutualRoomsSupport = (): boolean => {
|
||||
const checkSupport = useUnstableFeatureSupport();
|
||||
|
||||
const supported =
|
||||
checkSupport('uk.half-shot.msc2666') ||
|
||||
checkSupport('uk.half-shot.msc2666.mutual_rooms') ||
|
||||
checkSupport('uk.half-shot.msc2666.query_mutual_rooms');
|
||||
const supported = checkSupport('uk.half-shot.msc2666.query_mutual_rooms');
|
||||
|
||||
return !!supported;
|
||||
};
|
||||
|
|
@ -31,24 +30,33 @@ type MutualRoomsOK = {
|
|||
count: number;
|
||||
};
|
||||
|
||||
const fetchAllMutualRooms = async (mx: MatrixClient, userId: string): Promise<string[]> => {
|
||||
const fetchAllMutualRooms = async (
|
||||
ct: ClientContext,
|
||||
userId: string,
|
||||
unstable?: boolean
|
||||
): Promise<string[]> => {
|
||||
const mutualRooms: Set<string> = new Set();
|
||||
|
||||
let nextBatch: string | undefined;
|
||||
do {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const result = await mx.http.authedRequest<MutualRoomsOK>(
|
||||
Method.Get,
|
||||
'/mutual_rooms',
|
||||
{
|
||||
user_id: userId,
|
||||
from: nextBatch,
|
||||
},
|
||||
undefined,
|
||||
{
|
||||
prefix: '/_matrix/client/v1',
|
||||
}
|
||||
);
|
||||
const resp = await ct.requestAuthJSON({
|
||||
method: 'GET',
|
||||
path: withSearchParams(
|
||||
unstable
|
||||
? '/_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms'
|
||||
: '/_matrix/client/v1/mutual_rooms',
|
||||
{
|
||||
user_id: userId,
|
||||
from: nextBatch,
|
||||
}
|
||||
),
|
||||
requireAuthentication: true,
|
||||
});
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const [err, result] = await parseMatrixResp<MutualRoomsOK>(resp);
|
||||
if (err) throw new Error(printMatrixError(err));
|
||||
|
||||
result.joined.forEach((r) => mutualRooms.add(r));
|
||||
nextBatch = result.next_batch;
|
||||
} while (typeof nextBatch === 'string');
|
||||
|
|
@ -57,17 +65,19 @@ const fetchAllMutualRooms = async (mx: MatrixClient, userId: string): Promise<st
|
|||
};
|
||||
|
||||
export const useMutualRooms = (userId: string): AsyncState<string[], unknown> => {
|
||||
const mx = useMatrixClient();
|
||||
const ct = useClient();
|
||||
|
||||
const unstableSupport = useUnstableMutualRoomsSupport();
|
||||
const support = useMutualRoomsSupport();
|
||||
|
||||
const [mutualRoomsState] = useAsyncCallbackValue(
|
||||
useCallback(() => {
|
||||
if (support) return fetchAllMutualRooms(mx, userId);
|
||||
if (unstableSupport) return mx._unstable_getSharedRooms(userId);
|
||||
return Promise.resolve([]);
|
||||
}, [mx, userId, unstableSupport, support])
|
||||
useCallback(async () => {
|
||||
if (support) return fetchAllMutualRooms(ct, userId);
|
||||
if (unstableSupport) return fetchAllMutualRooms(ct, userId, true);
|
||||
|
||||
const memberships = await ct.storage.joinRoomMember.read.queryIndex('userId', userId);
|
||||
return memberships.map((membership) => membership.roomId);
|
||||
}, [ct, userId, unstableSupport, support])
|
||||
);
|
||||
|
||||
return mutualRoomsState;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { StateEvent, StateEventType } from '../../types/matrix/room';
|
|||
import { getJoinRoomStateId, JoinRoomStateObject } from '../../client/storage/collections';
|
||||
import { useClient } from './useClient';
|
||||
import { useObservableValuePromise } from './useObservableValue';
|
||||
import { ClientContext } from '../../client/context';
|
||||
|
||||
export type PowerLevelActions = 'invite' | 'redact' | 'kick' | 'ban' | 'historical';
|
||||
export type PowerLevelNotificationsAction = 'room';
|
||||
|
|
@ -243,8 +242,13 @@ export const applyPermissionPower = (
|
|||
if (typeof location.key === 'string') {
|
||||
const users = powerLevels.users ?? {};
|
||||
users[location.key] = power;
|
||||
|
||||
if (powerLevels.users_default === power) {
|
||||
delete users[location.key];
|
||||
}
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
powerLevels.users = users;
|
||||
|
||||
return powerLevels;
|
||||
}
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { User, UserEvent, UserEventHandlerMap } from 'matrix-js-sdk';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
|
||||
export enum Presence {
|
||||
Online = 'online',
|
||||
Unavailable = 'unavailable',
|
||||
Offline = 'offline',
|
||||
}
|
||||
import { useMemo } from 'react';
|
||||
import { Presence } from 'neurone/lib/api/types';
|
||||
import { useClient } from './useClient';
|
||||
import { useObservableValuePromise } from './useObservableValue';
|
||||
import { PresenceObject } from '../../client/storage/collections';
|
||||
|
||||
export type UserPresence = {
|
||||
presence: Presence;
|
||||
|
|
@ -15,44 +11,31 @@ export type UserPresence = {
|
|||
lastActiveTs?: number;
|
||||
};
|
||||
|
||||
const getUserPresence = (user: User): UserPresence => ({
|
||||
presence: user.presence as Presence,
|
||||
status: user.presenceStatusMsg,
|
||||
active: user.currentlyActive,
|
||||
lastActiveTs: user.getLastActiveTs(),
|
||||
const getUserPresence = (user?: PresenceObject): UserPresence => ({
|
||||
presence: user?.presence ?? 'offline',
|
||||
status: user?.content.status_msg ?? undefined,
|
||||
active: user?.content.currently_active ?? false,
|
||||
lastActiveTs: user?.content.last_active_ago,
|
||||
});
|
||||
|
||||
export const useUserPresence = (userId: string): UserPresence | undefined => {
|
||||
const mx = useMatrixClient();
|
||||
const user = mx.getUser(userId);
|
||||
const ct = useClient();
|
||||
const presencePromise = useMemo(
|
||||
() => ct.storage.presence.observableRead.get(userId),
|
||||
[ct, userId]
|
||||
);
|
||||
const presenceResult = useObservableValuePromise(presencePromise);
|
||||
const presenceObj = presenceResult.value;
|
||||
|
||||
const [presence, setPresence] = useState(() => (user ? getUserPresence(user) : undefined));
|
||||
|
||||
useEffect(() => {
|
||||
const updatePresence: UserEventHandlerMap[UserEvent.Presence] = (event, u) => {
|
||||
if (u.userId === user?.userId) {
|
||||
setPresence(getUserPresence(user));
|
||||
}
|
||||
};
|
||||
user?.on(UserEvent.Presence, updatePresence);
|
||||
user?.on(UserEvent.CurrentlyActive, updatePresence);
|
||||
user?.on(UserEvent.LastPresenceTs, updatePresence);
|
||||
return () => {
|
||||
user?.removeListener(UserEvent.Presence, updatePresence);
|
||||
user?.removeListener(UserEvent.CurrentlyActive, updatePresence);
|
||||
user?.removeListener(UserEvent.LastPresenceTs, updatePresence);
|
||||
};
|
||||
}, [user]);
|
||||
|
||||
return presence;
|
||||
return getUserPresence(presenceObj);
|
||||
};
|
||||
|
||||
export const usePresenceLabel = (): Record<Presence, string> =>
|
||||
useMemo(
|
||||
() => ({
|
||||
[Presence.Online]: 'Active',
|
||||
[Presence.Unavailable]: 'Busy',
|
||||
[Presence.Offline]: 'Away',
|
||||
online: 'Active',
|
||||
unavailable: 'Busy',
|
||||
offline: 'Away',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -135,8 +135,8 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
|||
>
|
||||
<Outlet />
|
||||
</ClientLayout>
|
||||
{/*<SearchModalRenderer />
|
||||
<UserRoomProfileRenderer />*/}
|
||||
{/*<SearchModalRenderer />*/}
|
||||
<UserRoomProfileRenderer />
|
||||
<CreateRoomModalRenderer />
|
||||
<CreateSpaceModalRenderer />
|
||||
{/*<RoomSettingsRenderer />
|
||||
|
|
|
|||
Loading…
Reference in a new issue