diff --git a/src/app/components/JoinRulesSwitcher.tsx b/src/app/components/JoinRulesSwitcher.tsx index bbc0a65d..58cf039b 100644 --- a/src/app/components/JoinRulesSwitcher.tsx +++ b/src/app/components/JoinRulesSwitcher.tsx @@ -13,44 +13,43 @@ import { Button, Spinner, } from 'folds'; -import { JoinRule } from 'matrix-js-sdk'; import FocusTrap from 'focus-trap-react'; import { stopPropagation } from '../utils/keyboard'; import { getRoomIconSrc } from '../utils/room'; +import { JoinRule } from '../../types/matrix/room'; export type ExtraJoinRules = 'knock_restricted'; -export type ExtendedJoinRules = JoinRule | ExtraJoinRules; -type JoinRuleIcons = Record; +type JoinRuleIcons = Record; export const useJoinRuleIcons = (roomType?: string): JoinRuleIcons => useMemo( () => ({ - [JoinRule.Invite]: getRoomIconSrc(Icons, roomType, JoinRule.Invite), - [JoinRule.Knock]: getRoomIconSrc(Icons, roomType, JoinRule.Knock), - knock_restricted: getRoomIconSrc(Icons, roomType, JoinRule.Restricted), - [JoinRule.Restricted]: getRoomIconSrc(Icons, roomType, JoinRule.Restricted), - [JoinRule.Public]: getRoomIconSrc(Icons, roomType, JoinRule.Public), - [JoinRule.Private]: getRoomIconSrc(Icons, roomType, JoinRule.Private), + invite: getRoomIconSrc(Icons, roomType, 'invite'), + knock: getRoomIconSrc(Icons, roomType, 'knock'), + restricted: getRoomIconSrc(Icons, roomType, 'restricted'), + knock_restricted: getRoomIconSrc(Icons, roomType, 'knock_restricted'), + public: getRoomIconSrc(Icons, roomType, 'public'), + private: getRoomIconSrc(Icons, roomType, 'private'), }), [roomType] ); -type JoinRuleLabels = Record; +type JoinRuleLabels = Record; export const useRoomJoinRuleLabel = (): JoinRuleLabels => useMemo( () => ({ - [JoinRule.Invite]: 'Invite Only', - [JoinRule.Knock]: 'Knock & Invite', + invite: 'Invite Only', + knock: 'Knock & Invite', knock_restricted: 'Space Members or Knock', - [JoinRule.Restricted]: 'Space Members', - [JoinRule.Public]: 'Public', - [JoinRule.Private]: 'Invite Only', + restricted: 'Space Members', + public: 'Public', + private: 'Invite Only', }), [] ); -type JoinRulesSwitcherProps = { +type JoinRulesSwitcherProps = { icons: JoinRuleIcons; labels: JoinRuleLabels; rules: T; @@ -59,7 +58,7 @@ type JoinRulesSwitcherProps = { disabled?: boolean; changing?: boolean; }; -export function JoinRulesSwitcher({ +export function JoinRulesSwitcher({ icons, labels, rules, @@ -75,7 +74,7 @@ export function JoinRulesSwitcher({ }; const handleChange = useCallback( - (selectedRule: ExtendedJoinRules) => { + (selectedRule: JoinRule) => { setCords(undefined); onChange(selectedRule); }, @@ -127,7 +126,7 @@ export function JoinRulesSwitcher({ fill="Soft" radii="300" outlined - before={} + before={} after={ changing ? ( diff --git a/src/app/features/common-settings/general/RoomAddress.tsx b/src/app/features/common-settings/general/RoomAddress.tsx index 767a967e..26986eef 100644 --- a/src/app/features/common-settings/general/RoomAddress.tsx +++ b/src/app/features/common-settings/general/RoomAddress.tsx @@ -14,11 +14,9 @@ import { Text, toRem, } from 'folds'; -import { MatrixError } from 'matrix-js-sdk'; import { SettingTile } from '../../../components/setting-tile'; import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCardStyle } from '../../room-settings/styles.css'; -import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useRoom } from '../../../hooks/useRoom'; import { useLocalAliases, @@ -30,25 +28,26 @@ import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { CutoutCard } from '../../../components/cutout-card'; import { replaceSpaceWithDash } from '../../../utils/common'; import { useAlive } from '../../../hooks/useAlive'; -import { StateEvent } from '../../../../types/matrix/room'; +import { StateEventType } from '../../../../types/matrix/room'; import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions'; import { getMxIdServer } from '../../../utils/matrix'; +import { useClient } from '../../../hooks/useClient'; type RoomPublishedAddressesProps = { permissions: RoomPermissionsAPI; }; export function RoomPublishedAddresses({ permissions }: RoomPublishedAddressesProps) { - const mx = useMatrixClient(); - const room = useRoom(); + const ct = useClient(); + const roomId = useRoom(); const canEditCanonical = permissions.stateEvent( - StateEvent.RoomCanonicalAlias, - mx.getSafeUserId() + StateEventType.RoomCanonicalAlias, + ct.session.userId ); - const [canonicalAlias, publishedAliases] = usePublishedAliases(room); - const setMainAlias = useSetMainAlias(room); + const [canonicalAlias, publishedAliases] = usePublishedAliases(roomId); + const setMainAlias = useSetMainAlias(roomId, publishedAliases); const [mainState, setMain] = useAsyncCallback(setMainAlias); const loading = mainState.status === AsyncStatus.Loading; @@ -120,7 +119,7 @@ export function RoomPublishedAddresses({ permissions }: RoomPublishedAddressesPr {mainState.status === AsyncStatus.Error && ( - {(mainState.error as MatrixError).message} + {mainState.error instanceof Error ? mainState.error.message : 'Unknown Error'} )} @@ -131,8 +130,8 @@ export function RoomPublishedAddresses({ permissions }: RoomPublishedAddressesPr } function LocalAddressInput({ addLocalAlias }: { addLocalAlias: (alias: string) => Promise }) { - const mx = useMatrixClient(); - const userId = mx.getSafeUserId(); + const ct = useClient(); + const { userId } = ct.session; const server = getMxIdServer(userId); const alive = useAlive(); @@ -189,9 +188,7 @@ function LocalAddressInput({ addLocalAlias }: { addLocalAlias: (alias: string) = {addState.status === AsyncStatus.Error && ( - {(addState.error as MatrixError).httpStatus === 409 - ? 'Address is already in use!' - : (addState.error as MatrixError).message} + {addState.error instanceof Error ? addState.error.message : 'Unknown Error'} )} @@ -207,11 +204,15 @@ function LocalAddressesList({ removeLocalAlias: (alias: string) => Promise; canEditCanonical?: boolean; }) { - const room = useRoom(); + const roomId = useRoom(); const alive = useAlive(); - const [, publishedAliases] = usePublishedAliases(room); - const { publishAliases, unpublishAliases } = usePublishUnpublishAliases(room); + const [canonicalAlias, publishedAliases] = usePublishedAliases(roomId); + const { publishAliases, unpublishAliases } = usePublishUnpublishAliases( + roomId, + publishedAliases, + canonicalAlias + ); const [selectedAliases, setSelectedAliases] = useState([]); const selectHasPublished = selectedAliases.find((alias) => publishedAliases.includes(alias)); @@ -261,10 +262,10 @@ function LocalAddressesList({ deleteState.status === AsyncStatus.Loading || publishState.status === AsyncStatus.Loading || unpublishState.status === AsyncStatus.Loading; - let error: MatrixError | undefined; - if (deleteState.status === AsyncStatus.Error) error = deleteState.error as MatrixError; - if (publishState.status === AsyncStatus.Error) error = publishState.error as MatrixError; - if (unpublishState.status === AsyncStatus.Error) error = unpublishState.error as MatrixError; + let error: unknown; + if (deleteState.status === AsyncStatus.Error) error = deleteState.error; + if (publishState.status === AsyncStatus.Error) error = publishState.error; + if (unpublishState.status === AsyncStatus.Error) error = unpublishState.error; return ( @@ -352,7 +353,7 @@ function LocalAddressesList({ })} {error && ( - {error.message} + {error instanceof Error ? error.message : 'Unknown Error'} )} @@ -360,17 +361,17 @@ function LocalAddressesList({ } export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissionsAPI }) { - const mx = useMatrixClient(); - const room = useRoom(); + const ct = useClient(); + const roomId = useRoom(); const canEditCanonical = permissions.stateEvent( - StateEvent.RoomCanonicalAlias, - mx.getSafeUserId() + StateEventType.RoomCanonicalAlias, + ct.session.userId ); const [expand, setExpand] = useState(false); - const { localAliasesState, addLocalAlias, removeLocalAlias } = useLocalAliases(room.roomId); + const { localAliasesState, addLocalAlias, removeLocalAlias } = useLocalAliases(roomId); return ( - {localAliasesState.error.message} + {localAliasesState.error instanceof Error + ? localAliasesState.error.message + : 'Unkonw Error'} )} diff --git a/src/app/features/common-settings/general/RoomEncryption.tsx b/src/app/features/common-settings/general/RoomEncryption.tsx index 15b1f156..5e1e8c44 100644 --- a/src/app/features/common-settings/general/RoomEncryption.tsx +++ b/src/app/features/common-settings/general/RoomEncryption.tsx @@ -16,18 +16,19 @@ import { Text, } from 'folds'; import React, { useCallback, useState } from 'react'; -import { MatrixError } from 'matrix-js-sdk'; import FocusTrap from 'focus-trap-react'; +import { putStateEvent } from 'neurone/lib/api/matrix/client/v3/rooms'; +import { parseMatrixResp } from 'neurone/lib/matrix-request'; import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SettingTile } from '../../../components/setting-tile'; -import { useMatrixClient } from '../../../hooks/useMatrixClient'; -import { StateEvent } from '../../../../types/matrix/room'; +import { StateEventType } from '../../../../types/matrix/room'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { useRoom } from '../../../hooks/useRoom'; -import { useStateEvent } from '../../../hooks/useStateEvent'; import { stopPropagation } from '../../../utils/keyboard'; import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions'; +import { useClient } from '../../../hooks/useClient'; +import { useObservableValue } from '../../../hooks/useObservableValue'; const ROOM_ENC_ALGO = 'm.megolm.v1.aes-sha2'; @@ -35,21 +36,26 @@ type RoomEncryptionProps = { permissions: RoomPermissionsAPI; }; export function RoomEncryption({ permissions }: RoomEncryptionProps) { - const mx = useMatrixClient(); - const room = useRoom(); + const ct = useClient(); + const roomId = useRoom(); - const canEnable = permissions.stateEvent(StateEvent.RoomEncryption, mx.getSafeUserId()); - const content = useStateEvent(room, StateEvent.RoomEncryption)?.getContent<{ - algorithm: string; - }>(); - const enabled = content?.algorithm === ROOM_ENC_ALGO; + const canEnable = permissions.stateEvent(StateEventType.RoomEncryption, ct.session.userId); + const encryptedRooms = useObservableValue(ct.encryptedRooms); + const enabled = encryptedRooms.has(roomId); const [enableState, enable] = useAsyncCallback( useCallback(async () => { - await mx.sendStateEvent(room.roomId, StateEvent.RoomEncryption as any, { - algorithm: ROOM_ENC_ALGO, - }); - }, [mx, room.roomId]) + const resp = await ct.requestAuthJSON( + putStateEvent({ + pathParams: { roomId, eventType: StateEventType.RoomEncryption, stateKey: '' }, + body: { + algorithm: ROOM_ENC_ALGO, + }, + }) + ); + const [err] = await parseMatrixResp(resp); + if (err) throw new Error(err.data.error ?? err.data.errcode); + }, [ct, roomId]) ); const enabling = enableState.status === AsyncStatus.Loading; @@ -97,7 +103,7 @@ export function RoomEncryption({ permissions }: RoomEncryptionProps) { > {enableState.status === AsyncStatus.Error && ( - {(enableState.error as MatrixError).message} + {enableState.error instanceof Error ? enableState.error.message : 'Unknown Error'} )} {prompt && ( diff --git a/src/app/features/common-settings/general/RoomHistoryVisibility.tsx b/src/app/features/common-settings/general/RoomHistoryVisibility.tsx index 2e42785f..de13a902 100644 --- a/src/app/features/common-settings/general/RoomHistoryVisibility.tsx +++ b/src/app/features/common-settings/general/RoomHistoryVisibility.tsx @@ -12,55 +12,46 @@ import { Spinner, Text, } from 'folds'; -import { HistoryVisibility, MatrixError } from 'matrix-js-sdk'; -import { RoomHistoryVisibilityEventContent } from 'matrix-js-sdk/lib/types'; import FocusTrap from 'focus-trap-react'; +import { HistoryVisibility } from 'neurone/lib/crypto/machine'; +import { putStateEvent } from 'neurone/lib/api/matrix/client/v3/rooms'; +import { parseMatrixResp } from 'neurone/lib/matrix-request'; import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SettingTile } from '../../../components/setting-tile'; -import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useRoom } from '../../../hooks/useRoom'; -import { StateEvent } from '../../../../types/matrix/room'; +import { RoomHistoryVisibilityContent, StateEventType } from '../../../../types/matrix/room'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; -import { useStateEvent } from '../../../hooks/useStateEvent'; import { stopPropagation } from '../../../utils/keyboard'; import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions'; +import { useClient } from '../../../hooks/useClient'; +import { useObservableValue } from '../../../hooks/useObservableValue'; -const useVisibilityStr = () => +const useVisibilityStr = (): Record => useMemo( () => ({ - [HistoryVisibility.Invited]: 'After Invite', - [HistoryVisibility.Joined]: 'After Join', - [HistoryVisibility.Shared]: 'All Messages', - [HistoryVisibility.WorldReadable]: 'All Messages (Guests)', + invited: 'After Invite', + joined: 'After Join', + shared: 'All Messages', + world_readable: 'All Messages (Guests)', }), [] ); -const useVisibilityMenu = () => - useMemo( - () => [ - HistoryVisibility.Shared, - HistoryVisibility.Invited, - HistoryVisibility.Joined, - HistoryVisibility.WorldReadable, - ], - [] - ); +const useVisibilityMenu = (): HistoryVisibility[] => + useMemo(() => ['invited', 'joined', 'shared', 'world_readable'], []); type RoomHistoryVisibilityProps = { permissions: RoomPermissionsAPI; }; export function RoomHistoryVisibility({ permissions }: RoomHistoryVisibilityProps) { - const mx = useMatrixClient(); - const room = useRoom(); + const ct = useClient(); + const roomId = useRoom(); - const canEdit = permissions.stateEvent(StateEvent.RoomHistoryVisibility, mx.getSafeUserId()); + const canEdit = permissions.stateEvent(StateEventType.RoomHistoryVisibility, ct.session.userId); + const roomToHistoryVisibility = useObservableValue(ct.roomToHistoryVisibility); - const visibilityEvent = useStateEvent(room, StateEvent.RoomHistoryVisibility); - const historyVisibility: HistoryVisibility = - visibilityEvent?.getContent().history_visibility ?? - HistoryVisibility.Shared; + const historyVisibility: HistoryVisibility = roomToHistoryVisibility.get(roomId) ?? 'shared'; const visibilityMenu = useVisibilityMenu(); const visibilityStr = useVisibilityStr(); @@ -73,12 +64,20 @@ export function RoomHistoryVisibility({ permissions }: RoomHistoryVisibilityProp const [submitState, submit] = useAsyncCallback( useCallback( async (visibility: HistoryVisibility) => { - const content: RoomHistoryVisibilityEventContent = { + const content: RoomHistoryVisibilityContent = { history_visibility: visibility, }; - await mx.sendStateEvent(room.roomId, StateEvent.RoomHistoryVisibility as any, content); + + const resp = await ct.requestAuthJSON( + putStateEvent({ + pathParams: { roomId, eventType: StateEventType.RoomHistoryVisibility, stateKey: '' }, + body: content, + }) + ); + const [err] = await parseMatrixResp(resp); + if (err) throw new Error(err.data.error ?? err.data.errcode); }, - [mx, room.roomId] + [ct, roomId] ) ); const submitting = submitState.status === AsyncStatus.Loading; @@ -156,7 +155,7 @@ export function RoomHistoryVisibility({ permissions }: RoomHistoryVisibilityProp > {submitState.status === AsyncStatus.Error && ( - {(submitState.error as MatrixError).message} + {submitState.error instanceof Error ? submitState.error.message : 'Unknown Error'} )} diff --git a/src/app/features/common-settings/general/RoomJoinRules.tsx b/src/app/features/common-settings/general/RoomJoinRules.tsx index 0d352167..c4520379 100644 --- a/src/app/features/common-settings/general/RoomJoinRules.tsx +++ b/src/app/features/common-settings/general/RoomJoinRules.tsx @@ -1,10 +1,9 @@ import React, { useCallback, useMemo } from 'react'; import { color, Text } from 'folds'; -import { JoinRule, MatrixError, RestrictedAllowType } from 'matrix-js-sdk'; -import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types'; import { useAtomValue } from 'jotai'; +import { putStateEvent } from 'neurone/lib/api/matrix/client/v3/rooms'; +import { parseMatrixResp } from 'neurone/lib/matrix-request'; import { - ExtendedJoinRules, JoinRulesSwitcher, useJoinRuleIcons, useRoomJoinRuleLabel, @@ -12,13 +11,15 @@ import { import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SettingTile } from '../../../components/setting-tile'; -import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useRoom } from '../../../hooks/useRoom'; -import { StateEvent } from '../../../../types/matrix/room'; -import { useStateEvent } from '../../../hooks/useStateEvent'; +import { + JoinRule, + StateEventType, + AllowCondition, + JoinRulesContent, +} from '../../../../types/matrix/room'; import { useSpaceOptionally } from '../../../hooks/useSpace'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; -import { getStateEvents } from '../../../utils/room'; import { useRecursiveChildSpaceScopeFactory, useSpaceChildren, @@ -31,89 +32,98 @@ import { restrictedSupported, } from '../../../utils/matrix'; import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions'; - -type RestrictedRoomAllowContent = { - room_id: string; - type: RestrictedAllowType; -}; +import { useClient } from '../../../hooks/useClient'; +import { useObservableValue } from '../../../hooks/useObservableValue'; +import { getRoomStateEventsQuery } from '../../../hooks/storage/useRoomStateEvent'; type RoomJoinRulesProps = { permissions: RoomPermissionsAPI; }; export function RoomJoinRules({ permissions }: RoomJoinRulesProps) { - const mx = useMatrixClient(); - const room = useRoom(); - const allowKnockRestricted = knockRestrictedSupported(room.getVersion()); - const allowRestricted = restrictedSupported(room.getVersion()); - const allowKnock = knockSupported(room.getVersion()); + const ct = useClient(); + const roomId = useRoom(); + + const roomVersion = ct.roomToVersion.read.get(roomId) ?? '1'; + + const allowKnockRestricted = knockRestrictedSupported(roomVersion); + const allowRestricted = restrictedSupported(roomVersion); + const allowKnock = knockSupported(roomVersion); const roomIdToParents = useAtomValue(roomToParentsAtom); - const space = useSpaceOptionally(); - const subspacesScope = useRecursiveChildSpaceScopeFactory(mx, roomIdToParents); - const subspaces = useSpaceChildren(allRoomsAtom, space?.roomId ?? '', subspacesScope); + const spaceId = useSpaceOptionally(); + const subspacesScope = useRecursiveChildSpaceScopeFactory(ct, roomIdToParents); + const subspaces = useSpaceChildren(allRoomsAtom, spaceId ?? '', subspacesScope); - const canEdit = permissions.stateEvent(StateEvent.RoomHistoryVisibility, mx.getSafeUserId()); + const canEdit = permissions.stateEvent(StateEventType.RoomHistoryVisibility, ct.session.userId); - const joinRuleEvent = useStateEvent(room, StateEvent.RoomJoinRules); - const content = joinRuleEvent?.getContent(); - const rule: JoinRule = content?.join_rule ?? JoinRule.Invite; + const roomToJoinRule = useObservableValue(ct.roomToJoinRule); + const rule: JoinRule = roomToJoinRule.get(roomId) ?? 'invite'; - const joinRules: Array = useMemo(() => { - const r: ExtendedJoinRules[] = [JoinRule.Invite]; + const joinRules: Array = useMemo(() => { + const r: JoinRule[] = ['invite']; if (allowKnock) { - r.push(JoinRule.Knock); + r.push('knock'); } - if (allowRestricted && space) { - r.push(JoinRule.Restricted); + if (allowRestricted && spaceId) { + r.push('restricted'); } - if (allowKnockRestricted && space) { + if (allowKnockRestricted && spaceId) { r.push('knock_restricted'); } - r.push(JoinRule.Public); + r.push('public'); return r; - }, [allowKnockRestricted, allowRestricted, allowKnock, space]); + }, [allowKnockRestricted, allowRestricted, allowKnock, spaceId]); - const icons = useJoinRuleIcons(room.getType()); + const icons = useJoinRuleIcons(ct.roomToType.read.get(roomId)); const labels = useRoomJoinRuleLabel(); const [submitState, submit] = useAsyncCallback( useCallback( - async (joinRule: ExtendedJoinRules) => { - const allow: RestrictedRoomAllowContent[] = []; - if (joinRule === JoinRule.Restricted || joinRule === 'knock_restricted') { - const roomParents = roomIdToParents.get(room.roomId); + async (joinRule: JoinRule) => { + const allow: AllowCondition[] = []; + if (joinRule === 'restricted' || joinRule === 'knock_restricted') { + const roomParents = roomIdToParents.get(roomId); - const parents = getStateEvents(room, StateEvent.SpaceParent) - .map((event) => event.getStateKey()) - .filter((parentId) => typeof parentId === 'string') + const parentEvents = await ct.storage.joinRoomState.read.query( + getRoomStateEventsQuery(roomId, StateEventType.SpaceParent) + ); + const parents = parentEvents + .map((event) => event.event.state_key) .filter((parentId) => roomParents?.has(parentId)); - if (parents.length === 0 && space && roomParents) { + if (parents.length === 0 && spaceId && roomParents) { // if no m.space.parent found // find parent in current space const selectedParents = subspaces.filter((rId) => roomParents.has(rId)); - if (roomParents.has(space.roomId)) { - selectedParents.push(space.roomId); + if (roomParents.has(spaceId)) { + selectedParents.push(spaceId); } selectedParents.forEach((pId) => parents.push(pId)); } parents.forEach((parentRoomId) => { if (!parentRoomId) return; allow.push({ - type: RestrictedAllowType.RoomMembership, + type: 'm.room_membership', room_id: parentRoomId, }); }); } - const c: RoomJoinRulesEventContent = { - join_rule: joinRule as JoinRule, + const c: JoinRulesContent = { + join_rule: joinRule, }; if (allow.length > 0) c.allow = allow; - await mx.sendStateEvent(room.roomId, StateEvent.RoomJoinRules as any, c); + const resp = await ct.requestAuthJSON( + putStateEvent({ + pathParams: { roomId, eventType: StateEventType.RoomJoinRules, stateKey: '' }, + body: c, + }) + ); + const [err] = await parseMatrixResp(resp); + if (err) throw new Error(err.data.error ?? err.data.errcode); }, - [mx, room, space, subspaces, roomIdToParents] + [ct, roomId, spaceId, subspaces, roomIdToParents] ) ); @@ -127,9 +137,9 @@ export function RoomJoinRules({ permissions }: RoomJoinRulesProps) { gap="400" > {submitState.status === AsyncStatus.Error && ( - {(submitState.error as MatrixError).message} + {submitState.error instanceof Error ? submitState.error.message : 'Unknown Error'} )} diff --git a/src/app/features/common-settings/general/RoomProfile.tsx b/src/app/features/common-settings/general/RoomProfile.tsx index 0a67f9e0..2367ab0a 100644 --- a/src/app/features/common-settings/general/RoomProfile.tsx +++ b/src/app/features/common-settings/general/RoomProfile.tsx @@ -12,27 +12,18 @@ import { TextArea, } from 'folds'; import React, { FormEventHandler, useCallback, useMemo, useState } from 'react'; -import { useAtomValue } from 'jotai'; import Linkify from 'linkify-react'; import classNames from 'classnames'; -import { JoinRule, MatrixError } from 'matrix-js-sdk'; +import { putStateEvent } from 'neurone/lib/api/matrix/client/v3/rooms'; +import { parseMatrixResp } from 'neurone/lib/matrix-request'; import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCardStyle } from '../../room-settings/styles.css'; import { useRoom } from '../../../hooks/useRoom'; -import { - useRoomAvatar, - useRoomJoinRule, - useRoomName, - useRoomTopic, -} from '../../../hooks/useRoomMeta'; -import { mDirectAtom } from '../../../state/mDirectList'; import { BreakWord, LineClamp3 } from '../../../styles/Text.css'; import { LINKIFY_OPTS } from '../../../plugins/react-custom-html-parser'; import { RoomAvatar, RoomIcon } from '../../../components/room-avatar'; -import { mxcUrlToHttp } from '../../../utils/matrix'; -import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; -import { StateEvent } from '../../../../types/matrix/room'; +import { JoinRule, StateEvent, StateEventType } from '../../../../types/matrix/room'; import { CompactUploadCardRenderer } from '../../../components/upload-card'; import { useObjectURL } from '../../../hooks/useObjectURL'; import { createUploadAtom, UploadSuccess } from '../../../state/upload'; @@ -40,6 +31,9 @@ import { useFilePicker } from '../../../hooks/useFilePicker'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; import { useAlive } from '../../../hooks/useAlive'; import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions'; +import { useClient } from '../../../hooks/useClient'; +import { useObservableValue } from '../../../hooks/useObservableValue'; +import { mxcToHttp } from '../../../../client/mxcToHttp'; type RoomProfileEditProps = { canEditAvatar: boolean; @@ -48,6 +42,7 @@ type RoomProfileEditProps = { avatar?: string; name: string; topic: string; + joinRule?: JoinRule; onClose: () => void; }; export function RoomProfileEdit({ @@ -57,17 +52,22 @@ export function RoomProfileEdit({ avatar, name, topic, + joinRule, onClose, }: RoomProfileEditProps) { - const room = useRoom(); - const mx = useMatrixClient(); - const alive = useAlive(); + const ct = useClient(); const useAuthentication = useMediaAuthentication(); - const joinRule = useRoomJoinRule(room); + const roomId = useRoom(); + const alive = useAlive(); + const [roomAvatar, setRoomAvatar] = useState(avatar); const avatarUrl = roomAvatar - ? mxcUrlToHttp(mx, roomAvatar, useAuthentication) ?? undefined + ? mxcToHttp(ct.session.baseUrl, roomAvatar, useAuthentication).thumbnail({ + width: 96, + height: 96, + method: 'crop', + }) : undefined; const [imageFile, setImageFile] = useState(); @@ -93,18 +93,37 @@ export function RoomProfileEdit({ useCallback( async (roomAvatarMxc?: string | null, roomName?: string, roomTopic?: string) => { if (roomAvatarMxc !== undefined) { - await mx.sendStateEvent(room.roomId, StateEvent.RoomAvatar as any, { - url: roomAvatarMxc, - }); + const resp = await ct.requestAuthJSON( + putStateEvent({ + pathParams: { roomId, eventType: StateEventType.RoomAvatar, stateKey: '' }, + body: { url: roomAvatarMxc }, + }) + ); + const [err] = await parseMatrixResp(resp); + if (err) throw new Error(err.data.error ?? err.data.errcode); } if (roomName !== undefined) { - await mx.sendStateEvent(room.roomId, StateEvent.RoomName as any, { name: roomName }); + const resp = await ct.requestAuthJSON( + putStateEvent({ + pathParams: { roomId, eventType: StateEventType.RoomName, stateKey: '' }, + body: { name: roomName }, + }) + ); + const [err] = await parseMatrixResp(resp); + if (err) throw new Error(err.data.error ?? err.data.errcode); } if (roomTopic !== undefined) { - await mx.sendStateEvent(room.roomId, StateEvent.RoomTopic as any, { topic: roomTopic }); + const resp = await ct.requestAuthJSON( + putStateEvent({ + pathParams: { roomId, eventType: StateEventType.RoomTopic, stateKey: '' }, + body: { topic: roomTopic }, + }) + ); + const [err] = await parseMatrixResp(resp); + if (err) throw new Error(err.data.error ?? err.data.errcode); } }, - [mx, room.roomId] + [ct, roomId] ) ); const submitting = submitState.status === AsyncStatus.Loading; @@ -194,14 +213,14 @@ export function RoomProfileEdit({ ( )} @@ -231,7 +250,7 @@ export function RoomProfileEdit({ {submitState.status === AsyncStatus.Error && ( - {(submitState.error as MatrixError).message} + {submitState.error instanceof Error ? submitState.error.message : 'Unknown Error'} )} @@ -264,23 +283,32 @@ type RoomProfileProps = { permissions: RoomPermissionsAPI; }; export function RoomProfile({ permissions }: RoomProfileProps) { - const mx = useMatrixClient(); + const ct = useClient(); + const myUserId = ct.session.userId; const useAuthentication = useMediaAuthentication(); - const room = useRoom(); - const directs = useAtomValue(mDirectAtom); + const roomId = useRoom(); - const avatar = useRoomAvatar(room, directs.has(room.roomId)); - const name = useRoomName(room); - const topic = useRoomTopic(room); - const joinRule = useRoomJoinRule(room); + const roomToAvatar = useObservableValue(ct.roomToAvatar); + const roomToName = useObservableValue(ct.roomToName); + const roomToTopic = useObservableValue(ct.roomToTopic); + const roomToJoinRule = useObservableValue(ct.roomToJoinRule); - const canEditAvatar = permissions.stateEvent(StateEvent.RoomAvatar, mx.getSafeUserId()); - const canEditName = permissions.stateEvent(StateEvent.RoomName, mx.getSafeUserId()); - const canEditTopic = permissions.stateEvent(StateEvent.RoomTopic, mx.getSafeUserId()); + const avatar = roomToAvatar.get(roomId); + const name = roomToName.get(roomId); + const topic = roomToTopic.get(roomId); + const joinRule = roomToJoinRule.get(roomId); + + const canEditAvatar = permissions.stateEvent(StateEvent.RoomAvatar, myUserId); + const canEditName = permissions.stateEvent(StateEvent.RoomName, myUserId); + const canEditTopic = permissions.stateEvent(StateEvent.RoomTopic, myUserId); const canEdit = canEditAvatar || canEditName || canEditTopic; const avatarUrl = avatar - ? mxcUrlToHttp(mx, avatar, useAuthentication, 96, 96, 'crop') ?? undefined + ? mxcToHttp(ct.session.baseUrl, avatar, useAuthentication).thumbnail({ + width: 96, + height: 96, + method: 'crop', + }) : undefined; const [edit, setEdit] = useState(false); @@ -304,6 +332,7 @@ export function RoomProfile({ permissions }: RoomProfileProps) { avatar={avatar} name={name ?? ''} topic={topic ?? ''} + joinRule={joinRule} onClose={handleCloseEdit} /> ) : ( @@ -337,14 +366,14 @@ export function RoomProfile({ permissions }: RoomProfileProps) { ( )} diff --git a/src/app/features/common-settings/general/RoomPublish.tsx b/src/app/features/common-settings/general/RoomPublish.tsx index ce014210..6be65d8b 100644 --- a/src/app/features/common-settings/general/RoomPublish.tsx +++ b/src/app/features/common-settings/general/RoomPublish.tsx @@ -1,42 +1,37 @@ import React from 'react'; import { Box, color, Spinner, Switch, Text } from 'folds'; -import { JoinRule, MatrixError } from 'matrix-js-sdk'; -import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types'; import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SettingTile } from '../../../components/setting-tile'; import { useRoom } from '../../../hooks/useRoom'; import { useRoomDirectoryVisibility } from '../../../hooks/useRoomDirectoryVisibility'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; -import { StateEvent } from '../../../../types/matrix/room'; -import { useMatrixClient } from '../../../hooks/useMatrixClient'; -import { useStateEvent } from '../../../hooks/useStateEvent'; -import { ExtendedJoinRules } from '../../../components/JoinRulesSwitcher'; +import { JoinRule, StateEventType } from '../../../../types/matrix/room'; import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions'; +import { useClient } from '../../../hooks/useClient'; +import { useObservableValue } from '../../../hooks/useObservableValue'; type RoomPublishProps = { permissions: RoomPermissionsAPI; }; export function RoomPublish({ permissions }: RoomPublishProps) { - const mx = useMatrixClient(); - const room = useRoom(); + const ct = useClient(); + const roomId = useRoom(); const canEditCanonical = permissions.stateEvent( - StateEvent.RoomCanonicalAlias, - mx.getSafeUserId() + StateEventType.RoomCanonicalAlias, + ct.session.userId ); - const joinRuleEvent = useStateEvent(room, StateEvent.RoomJoinRules); - const content = joinRuleEvent?.getContent(); - const rule: ExtendedJoinRules = (content?.join_rule as ExtendedJoinRules) ?? JoinRule.Invite; + const roomToJoinRule = useObservableValue(ct.roomToJoinRule); + const rule: JoinRule = roomToJoinRule.get(roomId) ?? 'invite'; - const { visibilityState, setVisibility } = useRoomDirectoryVisibility(room.roomId); + const { visibilityState, setVisibility } = useRoomDirectoryVisibility(roomId); const [toggleState, toggleVisibility] = useAsyncCallback(setVisibility); const loading = visibilityState.status === AsyncStatus.Loading || toggleState.status === AsyncStatus.Loading; - const validRule = - rule === JoinRule.Public || rule === JoinRule.Knock || rule === 'knock_restricted'; + const validRule = rule === 'public' || rule === 'knock' || rule === 'knock_restricted'; return ( {visibilityState.status === AsyncStatus.Error && ( - {(visibilityState.error as MatrixError).message} + {visibilityState.error instanceof Error + ? visibilityState.error.message + : 'Unknown Error'} )} {toggleState.status === AsyncStatus.Error && ( - {(toggleState.error as MatrixError).message} + {toggleState.error instanceof Error ? toggleState.error.message : 'Unknown Error'} )} diff --git a/src/app/features/common-settings/general/RoomUpgrade.tsx b/src/app/features/common-settings/general/RoomUpgrade.tsx index 45a480aa..58ce5107 100644 --- a/src/app/features/common-settings/general/RoomUpgrade.tsx +++ b/src/app/features/common-settings/general/RoomUpgrade.tsx @@ -16,16 +16,15 @@ import { Icons, } from 'folds'; import FocusTrap from 'focus-trap-react'; -import { MatrixError, Method } from 'matrix-js-sdk'; -import { RoomTombstoneEventContent } from 'matrix-js-sdk/lib/types'; +import { ObjectReader } from 'neurone/lib/utils'; +import { postUpgrade } from 'neurone/lib/api/matrix/client/v3/rooms'; +import { parseMatrixResp } from 'neurone/lib/matrix-request'; import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCardStyle } from '../../room-settings/styles.css'; import { SettingTile } from '../../../components/setting-tile'; import { useRoom } from '../../../hooks/useRoom'; import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback'; -import { IRoomCreateContent, StateEvent } from '../../../../types/matrix/room'; -import { useMatrixClient } from '../../../hooks/useMatrixClient'; -import { useStateEvent } from '../../../hooks/useStateEvent'; +import { StateEventType } from '../../../../types/matrix/room'; import { useRoomNavigate } from '../../../hooks/useRoomNavigate'; import { useCapabilities } from '../../../hooks/useCapabilities'; import { stopPropagation } from '../../../utils/keyboard'; @@ -39,12 +38,14 @@ import { useAlive } from '../../../hooks/useAlive'; import { creatorsSupported } from '../../../utils/matrix'; import { useRoomCreators } from '../../../hooks/useRoomCreators'; import { BreakWord } from '../../../styles/Text.css'; +import { useClient } from '../../../hooks/useClient'; +const EMPTY_CREATORS = new Set(); function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) { - const mx = useMatrixClient(); - const room = useRoom(); + const ct = useClient(); + const roomId = useRoom(); const alive = useAlive(); - const creators = useRoomCreators(room); + const creators = useRoomCreators(roomId) ?? EMPTY_CREATORS; const capabilities = useCapabilities(); const roomVersions = capabilities['m.room_versions']; @@ -61,12 +62,20 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) { const [upgradeState, upgrade] = useAsyncCallback( useCallback( async (version: string, newAdditionalCreators?: string[]) => { - await mx.http.authedRequest(Method.Post, `/rooms/${room.roomId}/upgrade`, undefined, { - new_version: version, - additional_creators: newAdditionalCreators, - }); + const resp = await ct.requestAuthJSON( + postUpgrade({ + pathParams: { roomId }, + body: { + new_version: version, + additional_creators: newAdditionalCreators, + }, + }) + ); + + const [err] = await parseMatrixResp(resp); + if (err) throw new Error(err.data.error ?? err.data.errcode); }, - [mx, room] + [ct, roomId] ) ); @@ -103,7 +112,9 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) { size="500" > - {room.isSpaceRoom() ? 'Space Upgrade' : 'Room Upgrade'} + + {ct.spaceRooms.read.has(roomId) ? 'Space Upgrade' : 'Room Upgrade'} + @@ -139,7 +150,9 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) { {upgradeState.status === AsyncStatus.Error && ( - {(upgradeState.error as MatrixError).message} + {upgradeState.error instanceof Error + ? upgradeState.error.message + : 'Unknown Error'} )} @@ -163,28 +178,30 @@ type RoomUpgradeProps = { requestClose: () => void; }; export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) { - const mx = useMatrixClient(); - const room = useRoom(); + const ct = useClient(); + const roomId = useRoom(); const { navigateRoom, navigateSpace } = useRoomNavigate(); - const createContent = useStateEvent( - room, - StateEvent.RoomCreate - )?.getContent(); - const roomVersion = createContent?.room_version ?? '1'; - const predecessorRoomId = createContent?.predecessor?.room_id; - const tombstoneContent = useStateEvent( - room, - StateEvent.RoomTombstone - )?.getContent(); - const replacementRoom = tombstoneContent?.replacement_room; + const createContent = ct.state.join.create.read.find((event) => event.roomId === roomId)?.event + .content; + const tombstoneContent = ct.state.join.tombstone.read.find((event) => event.roomId === roomId) + ?.event.content; - const canUpgrade = permissions.stateEvent(StateEvent.RoomTombstone, mx.getSafeUserId()); + const roomVersion = ObjectReader.string(createContent, 'room_version') ?? '1'; + const predecessorRoomId = ObjectReader.string( + ObjectReader.object(createContent, 'predecessor'), + 'room_id' + ); + + const replacementRoom = ObjectReader.string(tombstoneContent, 'replacement_room'); + const tombstoneBody = ObjectReader.string(tombstoneContent, 'body'); + + const canUpgrade = permissions.stateEvent(StateEventType.RoomTombstone, ct.session.userId); const handleOpenRoom = () => { if (replacementRoom) { requestClose(); - if (room.isSpaceRoom()) { + if (ct.spaceRooms.read.has(roomId)) { navigateSpace(replacementRoom); } else { navigateRoom(replacementRoom); @@ -195,10 +212,14 @@ export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) { const handleOpenOldRoom = () => { if (predecessorRoomId) { requestClose(); - if (room.isSpaceRoom()) { + if (ct.spaceRooms.read.has(roomId)) { navigateSpace(predecessorRoomId); } else { - navigateRoom(predecessorRoomId, createContent.predecessor?.event_id); + const preEventId = ObjectReader.string( + ObjectReader.object(createContent, 'predecessor'), + 'event_id' + ); + navigateRoom(predecessorRoomId, preEventId); } } }; @@ -213,11 +234,11 @@ export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) { gap="400" > - {room.isSpaceRoom() ? 'Old Space' : 'Old Room'} + {ct.spaceRooms.read.has(roomId) ? 'Old Space' : 'Old Room'} )} {replacementRoom ? ( @@ -242,7 +263,9 @@ export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) { radii="300" onClick={handleOpenRoom} > - {room.isSpaceRoom() ? 'Open New Space' : 'Open New Room'} + + {ct.spaceRooms.read.has(roomId) ? 'Open New Space' : 'Open New Room'} + ) : (