add new sdk in general room settings
This commit is contained in:
parent
919fe50ac0
commit
e56eeace64
18 changed files with 580 additions and 446 deletions
|
|
@ -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<ExtendedJoinRules, IconSrc>;
|
||||
type JoinRuleIcons = Record<JoinRule, IconSrc>;
|
||||
|
||||
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<ExtendedJoinRules, string>;
|
||||
type JoinRuleLabels = Record<JoinRule, string>;
|
||||
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<T extends ExtendedJoinRules[]> = {
|
||||
type JoinRulesSwitcherProps<T extends JoinRule[]> = {
|
||||
icons: JoinRuleIcons;
|
||||
labels: JoinRuleLabels;
|
||||
rules: T;
|
||||
|
|
@ -59,7 +58,7 @@ type JoinRulesSwitcherProps<T extends ExtendedJoinRules[]> = {
|
|||
disabled?: boolean;
|
||||
changing?: boolean;
|
||||
};
|
||||
export function JoinRulesSwitcher<T extends ExtendedJoinRules[]>({
|
||||
export function JoinRulesSwitcher<T extends JoinRule[]>({
|
||||
icons,
|
||||
labels,
|
||||
rules,
|
||||
|
|
@ -75,7 +74,7 @@ export function JoinRulesSwitcher<T extends ExtendedJoinRules[]>({
|
|||
};
|
||||
|
||||
const handleChange = useCallback(
|
||||
(selectedRule: ExtendedJoinRules) => {
|
||||
(selectedRule: JoinRule) => {
|
||||
setCords(undefined);
|
||||
onChange(selectedRule);
|
||||
},
|
||||
|
|
@ -127,7 +126,7 @@ export function JoinRulesSwitcher<T extends ExtendedJoinRules[]>({
|
|||
fill="Soft"
|
||||
radii="300"
|
||||
outlined
|
||||
before={<Icon size="100" src={icons[value] ?? icons[JoinRule.Restricted]} />}
|
||||
before={<Icon size="100" src={icons[value] ?? 'restricted'} />}
|
||||
after={
|
||||
changing ? (
|
||||
<Spinner size="100" variant="Secondary" fill="Soft" />
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<Text size="T200" style={{ color: color.Critical.Main }}>
|
||||
{(mainState.error as MatrixError).message}
|
||||
{mainState.error instanceof Error ? mainState.error.message : 'Unknown Error'}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
|
@ -131,8 +130,8 @@ export function RoomPublishedAddresses({ permissions }: RoomPublishedAddressesPr
|
|||
}
|
||||
|
||||
function LocalAddressInput({ addLocalAlias }: { addLocalAlias: (alias: string) => Promise<void> }) {
|
||||
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) =
|
|||
</Box>
|
||||
{addState.status === AsyncStatus.Error && (
|
||||
<Text style={{ color: color.Critical.Main }} size="T200">
|
||||
{(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'}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
|
@ -207,11 +204,15 @@ function LocalAddressesList({
|
|||
removeLocalAlias: (alias: string) => Promise<void>;
|
||||
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<string[]>([]);
|
||||
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 (
|
||||
<Box direction="Column" gap="300">
|
||||
|
|
@ -352,7 +353,7 @@ function LocalAddressesList({
|
|||
})}
|
||||
{error && (
|
||||
<Text size="T200" style={{ color: color.Critical.Main }}>
|
||||
{error.message}
|
||||
{error instanceof Error ? error.message : 'Unknown Error'}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
|
@ -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 (
|
||||
<SequenceCard
|
||||
|
|
@ -424,7 +425,9 @@ export function RoomLocalAddresses({ permissions }: { permissions: RoomPermissio
|
|||
{localAliasesState.status === AsyncStatus.Error && (
|
||||
<Box gap="100">
|
||||
<Text size="T200" style={{ color: color.Critical.Main }}>
|
||||
{localAliasesState.error.message}
|
||||
{localAliasesState.error instanceof Error
|
||||
? localAliasesState.error.message
|
||||
: 'Unkonw Error'}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<Text style={{ color: color.Critical.Main }} size="T200">
|
||||
{(enableState.error as MatrixError).message}
|
||||
{enableState.error instanceof Error ? enableState.error.message : 'Unknown Error'}
|
||||
</Text>
|
||||
)}
|
||||
{prompt && (
|
||||
|
|
|
|||
|
|
@ -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<HistoryVisibility, string> =>
|
||||
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<RoomHistoryVisibilityEventContent>().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 && (
|
||||
<Text style={{ color: color.Critical.Main }} size="T200">
|
||||
{(submitState.error as MatrixError).message}
|
||||
{submitState.error instanceof Error ? submitState.error.message : 'Unknown Error'}
|
||||
</Text>
|
||||
)}
|
||||
</SettingTile>
|
||||
|
|
|
|||
|
|
@ -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<RoomJoinRulesEventContent>();
|
||||
const rule: JoinRule = content?.join_rule ?? JoinRule.Invite;
|
||||
const roomToJoinRule = useObservableValue(ct.roomToJoinRule);
|
||||
const rule: JoinRule = roomToJoinRule.get(roomId) ?? 'invite';
|
||||
|
||||
const joinRules: Array<ExtendedJoinRules> = useMemo(() => {
|
||||
const r: ExtendedJoinRules[] = [JoinRule.Invite];
|
||||
const joinRules: Array<JoinRule> = 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"
|
||||
>
|
||||
<SettingTile
|
||||
title={room.isSpaceRoom() ? 'Space Access' : 'Room Access'}
|
||||
title={ct.spaceRooms.read.has(roomId) ? 'Space Access' : 'Room Access'}
|
||||
description={
|
||||
room.isSpaceRoom()
|
||||
ct.spaceRooms.read.has(roomId)
|
||||
? 'Change how people can join the space.'
|
||||
: 'Change how people can join the room.'
|
||||
}
|
||||
|
|
@ -147,7 +157,7 @@ export function RoomJoinRules({ permissions }: RoomJoinRulesProps) {
|
|||
>
|
||||
{submitState.status === AsyncStatus.Error && (
|
||||
<Text style={{ color: color.Critical.Main }} size="T200">
|
||||
{(submitState.error as MatrixError).message}
|
||||
{submitState.error instanceof Error ? submitState.error.message : 'Unknown Error'}
|
||||
</Text>
|
||||
)}
|
||||
</SettingTile>
|
||||
|
|
|
|||
|
|
@ -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<File>();
|
||||
|
|
@ -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({
|
|||
<Box shrink="No">
|
||||
<Avatar size="500" radii="300">
|
||||
<RoomAvatar
|
||||
roomId={room.roomId}
|
||||
roomId={roomId}
|
||||
src={avatarUrl}
|
||||
alt={name}
|
||||
renderFallback={() => (
|
||||
<RoomIcon
|
||||
roomType={room.getType()}
|
||||
roomType={ct.roomToType.read.get(roomId)}
|
||||
size="400"
|
||||
joinRule={joinRule?.join_rule ?? JoinRule.Invite}
|
||||
joinRule={joinRule}
|
||||
filled
|
||||
/>
|
||||
)}
|
||||
|
|
@ -231,7 +250,7 @@ export function RoomProfileEdit({
|
|||
</Box>
|
||||
{submitState.status === AsyncStatus.Error && (
|
||||
<Text size="T200" style={{ color: color.Critical.Main }}>
|
||||
{(submitState.error as MatrixError).message}
|
||||
{submitState.error instanceof Error ? submitState.error.message : 'Unknown Error'}
|
||||
</Text>
|
||||
)}
|
||||
<Box gap="300">
|
||||
|
|
@ -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) {
|
|||
<Box shrink="No">
|
||||
<Avatar size="500" radii="300">
|
||||
<RoomAvatar
|
||||
roomId={room.roomId}
|
||||
roomId={roomId}
|
||||
src={avatarUrl}
|
||||
alt={name}
|
||||
renderFallback={() => (
|
||||
<RoomIcon
|
||||
roomType={room.getType()}
|
||||
roomType={ct.roomToType.read.get(roomId)}
|
||||
size="400"
|
||||
joinRule={joinRule?.join_rule ?? JoinRule.Invite}
|
||||
joinRule={joinRule}
|
||||
filled
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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<RoomJoinRulesEventContent>();
|
||||
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 (
|
||||
<SequenceCard
|
||||
|
|
@ -48,7 +43,7 @@ export function RoomPublish({ permissions }: RoomPublishProps) {
|
|||
<SettingTile
|
||||
title="Publish to Directory"
|
||||
description={
|
||||
room.isSpaceRoom()
|
||||
ct.spaceRooms.read.has(roomId)
|
||||
? 'List the space in the public directory to make it discoverable by others.'
|
||||
: 'List the room in the public directory to make it discoverable by others.'
|
||||
}
|
||||
|
|
@ -67,13 +62,15 @@ export function RoomPublish({ permissions }: RoomPublishProps) {
|
|||
>
|
||||
{visibilityState.status === AsyncStatus.Error && (
|
||||
<Text style={{ color: color.Critical.Main }} size="T200">
|
||||
{(visibilityState.error as MatrixError).message}
|
||||
{visibilityState.error instanceof Error
|
||||
? visibilityState.error.message
|
||||
: 'Unknown Error'}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{toggleState.status === AsyncStatus.Error && (
|
||||
<Text style={{ color: color.Critical.Main }} size="T200">
|
||||
{(toggleState.error as MatrixError).message}
|
||||
{toggleState.error instanceof Error ? toggleState.error.message : 'Unknown Error'}
|
||||
</Text>
|
||||
)}
|
||||
</SettingTile>
|
||||
|
|
|
|||
|
|
@ -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<string>();
|
||||
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"
|
||||
>
|
||||
<Box grow="Yes">
|
||||
<Text size="H4">{room.isSpaceRoom() ? 'Space Upgrade' : 'Room Upgrade'}</Text>
|
||||
<Text size="H4">
|
||||
{ct.spaceRooms.read.has(roomId) ? 'Space Upgrade' : 'Room Upgrade'}
|
||||
</Text>
|
||||
</Box>
|
||||
<IconButton size="300" onClick={requestClose} radii="300">
|
||||
<Icon src={Icons.Cross} />
|
||||
|
|
@ -139,7 +150,9 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
|
|||
</Box>
|
||||
{upgradeState.status === AsyncStatus.Error && (
|
||||
<Text className={BreakWord} style={{ color: color.Critical.Main }} size="T200">
|
||||
{(upgradeState.error as MatrixError).message}
|
||||
{upgradeState.error instanceof Error
|
||||
? upgradeState.error.message
|
||||
: 'Unknown Error'}
|
||||
</Text>
|
||||
)}
|
||||
<Button
|
||||
|
|
@ -148,7 +161,9 @@ function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
|
|||
disabled={upgrading}
|
||||
before={upgrading && <Spinner size="200" variant="Secondary" fill="Solid" />}
|
||||
>
|
||||
<Text size="B400">{room.isSpaceRoom() ? 'Upgrade Space' : 'Upgrade Room'}</Text>
|
||||
<Text size="B400">
|
||||
{ct.spaceRooms.read.has(roomId) ? 'Upgrade Space' : 'Upgrade Room'}
|
||||
</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Dialog>
|
||||
|
|
@ -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<IRoomCreateContent>();
|
||||
const roomVersion = createContent?.room_version ?? '1';
|
||||
const predecessorRoomId = createContent?.predecessor?.room_id;
|
||||
|
||||
const tombstoneContent = useStateEvent(
|
||||
room,
|
||||
StateEvent.RoomTombstone
|
||||
)?.getContent<RoomTombstoneEventContent>();
|
||||
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"
|
||||
>
|
||||
<SettingTile
|
||||
title={room.isSpaceRoom() ? 'Upgrade Space' : 'Upgrade Room'}
|
||||
title={ct.spaceRooms.read.has(roomId) ? 'Upgrade Space' : 'Upgrade Room'}
|
||||
description={
|
||||
replacementRoom
|
||||
? tombstoneContent.body ||
|
||||
`This ${room.isSpaceRoom() ? 'space' : 'room'} has been replaced!`
|
||||
? tombstoneBody ||
|
||||
`This ${ct.spaceRooms.read.has(roomId) ? 'space' : 'room'} has been replaced!`
|
||||
: `Current version: ${roomVersion}.`
|
||||
}
|
||||
after={
|
||||
|
|
@ -231,7 +252,7 @@ export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) {
|
|||
radii="300"
|
||||
onClick={handleOpenOldRoom}
|
||||
>
|
||||
<Text size="B300">{room.isSpaceRoom() ? 'Old Space' : 'Old Room'}</Text>
|
||||
<Text size="B300">{ct.spaceRooms.read.has(roomId) ? 'Old Space' : 'Old Room'}</Text>
|
||||
</Button>
|
||||
)}
|
||||
{replacementRoom ? (
|
||||
|
|
@ -242,7 +263,9 @@ export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) {
|
|||
radii="300"
|
||||
onClick={handleOpenRoom}
|
||||
>
|
||||
<Text size="B300">{room.isSpaceRoom() ? 'Open New Space' : 'Open New Room'}</Text>
|
||||
<Text size="B300">
|
||||
{ct.spaceRooms.read.has(roomId) ? 'Open New Space' : 'Open New Room'}
|
||||
</Text>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -1,14 +1,8 @@
|
|||
import React, { useMemo, useState } from 'react';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { Avatar, Box, config, Icon, IconButton, Icons, IconSrc, MenuItem, Text } from 'folds';
|
||||
import { JoinRule } from 'matrix-js-sdk';
|
||||
import { PageNav, PageNavContent, PageNavHeader, PageRoot } from '../../components/page';
|
||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { mxcUrlToHttp } from '../../utils/matrix';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { useRoomAvatar, useRoomJoinRule, useRoomName } from '../../hooks/useRoomMeta';
|
||||
import { mDirectAtom } from '../../state/mDirectList';
|
||||
import { RoomAvatar, RoomIcon } from '../../components/room-avatar';
|
||||
import { General } from './general';
|
||||
import { Members } from '../common-settings/members';
|
||||
|
|
@ -17,6 +11,22 @@ import { Permissions } from './permissions';
|
|||
import { RoomSettingsPage } from '../../state/roomSettings';
|
||||
import { useRoom } from '../../hooks/useRoom';
|
||||
import { DeveloperTools } from '../common-settings/developer-tools';
|
||||
import { useClient } from '../../hooks/useClient';
|
||||
import { useRoomHeroes } from '../../hooks/storage/useRoomHeros';
|
||||
import { useGetRoomName } from '../../hooks/storage/useGetRoomName';
|
||||
import { useGetRoomAvatar } from '../../hooks/storage/useGetRoomAvatar';
|
||||
import { mxcToHttp } from '../../../client/mxcToHttp';
|
||||
import { useObservableValue } from '../../hooks/useObservableValue';
|
||||
import {
|
||||
PowerLevelsContextProvider,
|
||||
usePowerLevels,
|
||||
usePowerLevelsState,
|
||||
} from '../../hooks/usePowerLevels';
|
||||
import {
|
||||
PowerLevelTagsContextProvider,
|
||||
usePowerLevelTags,
|
||||
usePowerLevelTagsState,
|
||||
} from '../../hooks/usePowerLevelTags';
|
||||
|
||||
type RoomSettingsMenuItem = {
|
||||
page: RoomSettingsPage;
|
||||
|
|
@ -61,17 +71,29 @@ type RoomSettingsProps = {
|
|||
requestClose: () => void;
|
||||
};
|
||||
export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) {
|
||||
const room = useRoom();
|
||||
const mx = useMatrixClient();
|
||||
const roomId = useRoom();
|
||||
const ct = useClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const mDirects = useAtomValue(mDirectAtom);
|
||||
|
||||
const roomAvatar = useRoomAvatar(room, mDirects.has(room.roomId));
|
||||
const roomName = useRoomName(room);
|
||||
const joinRuleContent = useRoomJoinRule(room);
|
||||
const [getRoomHeroes, roomToHeroMembersResult] = useRoomHeroes(roomId);
|
||||
const getRoomName = useGetRoomName(getRoomHeroes, roomToHeroMembersResult.value);
|
||||
const getRoomAvatar = useGetRoomAvatar(getRoomHeroes, roomToHeroMembersResult.value);
|
||||
const roomAvatar = getRoomAvatar(roomId);
|
||||
const roomName = getRoomName(roomId);
|
||||
|
||||
const stateObjResult = usePowerLevelsState(roomId);
|
||||
const powerLevels = usePowerLevels(stateObjResult.value);
|
||||
const powerLevelTagsResult = usePowerLevelTagsState(roomId);
|
||||
const powerLevelTags = usePowerLevelTags(powerLevels, powerLevelTagsResult.value);
|
||||
|
||||
const roomToJoinRule = useObservableValue(ct.roomToJoinRule);
|
||||
|
||||
const avatarUrl = roomAvatar
|
||||
? mxcUrlToHttp(mx, roomAvatar, useAuthentication, 96, 96, 'crop') ?? undefined
|
||||
? mxcToHttp(ct.session.baseUrl, roomAvatar, useAuthentication).thumbnail({
|
||||
width: 96,
|
||||
height: 96,
|
||||
method: 'crop',
|
||||
})
|
||||
: undefined;
|
||||
|
||||
const screenSize = useScreenSizeContext();
|
||||
|
|
@ -90,84 +112,91 @@ export function RoomSettings({ initialPage, requestClose }: RoomSettingsProps) {
|
|||
};
|
||||
|
||||
return (
|
||||
<PageRoot
|
||||
nav={
|
||||
screenSize === ScreenSize.Mobile && activePage !== undefined ? undefined : (
|
||||
<PageNav size="300">
|
||||
<PageNavHeader outlined={false}>
|
||||
<Box grow="Yes" gap="200">
|
||||
<Avatar size="200" radii="300">
|
||||
<RoomAvatar
|
||||
roomId={room.roomId}
|
||||
src={avatarUrl}
|
||||
alt={roomName}
|
||||
renderFallback={() => (
|
||||
<RoomIcon
|
||||
size="50"
|
||||
roomType={room.getType()}
|
||||
joinRule={joinRuleContent?.join_rule ?? JoinRule.Invite}
|
||||
filled
|
||||
<PowerLevelsContextProvider value={powerLevels}>
|
||||
<PowerLevelTagsContextProvider value={powerLevelTags}>
|
||||
<PageRoot
|
||||
nav={
|
||||
screenSize === ScreenSize.Mobile && activePage !== undefined ? undefined : (
|
||||
<PageNav size="300">
|
||||
<PageNavHeader outlined={false}>
|
||||
<Box grow="Yes" gap="200">
|
||||
<Avatar size="200" radii="300">
|
||||
<RoomAvatar
|
||||
roomId={roomId}
|
||||
src={avatarUrl}
|
||||
alt={roomName}
|
||||
renderFallback={() => (
|
||||
<RoomIcon
|
||||
size="50"
|
||||
roomType={ct.roomToType.read.get(roomId)}
|
||||
joinRule={roomToJoinRule.get(roomId)}
|
||||
filled
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Avatar>
|
||||
<Text size="H4" truncate>
|
||||
{roomName}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No">
|
||||
{screenSize === ScreenSize.Mobile && (
|
||||
<IconButton onClick={requestClose} variant="Background">
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
)}
|
||||
/>
|
||||
</Avatar>
|
||||
<Text size="H4" truncate>
|
||||
{roomName}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box shrink="No">
|
||||
{screenSize === ScreenSize.Mobile && (
|
||||
<IconButton onClick={requestClose} variant="Background">
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
</PageNavHeader>
|
||||
<Box grow="Yes" direction="Column">
|
||||
<PageNavContent>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
{menuItems.map((item) => (
|
||||
<MenuItem
|
||||
key={item.name}
|
||||
variant="Background"
|
||||
radii="400"
|
||||
aria-pressed={activePage === item.page}
|
||||
before={<Icon src={item.icon} size="100" filled={activePage === item.page} />}
|
||||
onClick={() => setActivePage(item.page)}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: activePage === item.page ? config.fontWeight.W600 : undefined,
|
||||
}}
|
||||
size="T300"
|
||||
truncate
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
</MenuItem>
|
||||
))}
|
||||
</div>
|
||||
</PageNavContent>
|
||||
</Box>
|
||||
</PageNav>
|
||||
)
|
||||
}
|
||||
>
|
||||
{activePage === RoomSettingsPage.GeneralPage && (
|
||||
<General requestClose={handlePageRequestClose} />
|
||||
)}
|
||||
{activePage === RoomSettingsPage.MembersPage && (
|
||||
<Members requestClose={handlePageRequestClose} />
|
||||
)}
|
||||
{activePage === RoomSettingsPage.PermissionsPage && (
|
||||
<Permissions requestClose={handlePageRequestClose} />
|
||||
)}
|
||||
{activePage === RoomSettingsPage.EmojisStickersPage && (
|
||||
<EmojisStickers requestClose={handlePageRequestClose} />
|
||||
)}
|
||||
{activePage === RoomSettingsPage.DeveloperToolsPage && (
|
||||
<DeveloperTools requestClose={handlePageRequestClose} />
|
||||
)}
|
||||
</PageRoot>
|
||||
</Box>
|
||||
</PageNavHeader>
|
||||
<Box grow="Yes" direction="Column">
|
||||
<PageNavContent>
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
{menuItems.map((item) => (
|
||||
<MenuItem
|
||||
key={item.name}
|
||||
variant="Background"
|
||||
radii="400"
|
||||
aria-pressed={activePage === item.page}
|
||||
before={
|
||||
<Icon src={item.icon} size="100" filled={activePage === item.page} />
|
||||
}
|
||||
onClick={() => setActivePage(item.page)}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight:
|
||||
activePage === item.page ? config.fontWeight.W600 : undefined,
|
||||
}}
|
||||
size="T300"
|
||||
truncate
|
||||
>
|
||||
{item.name}
|
||||
</Text>
|
||||
</MenuItem>
|
||||
))}
|
||||
</div>
|
||||
</PageNavContent>
|
||||
</Box>
|
||||
</PageNav>
|
||||
)
|
||||
}
|
||||
>
|
||||
{activePage === RoomSettingsPage.GeneralPage && (
|
||||
<General requestClose={handlePageRequestClose} />
|
||||
)}
|
||||
{activePage === RoomSettingsPage.MembersPage && (
|
||||
<Members requestClose={handlePageRequestClose} />
|
||||
)}
|
||||
{activePage === RoomSettingsPage.PermissionsPage && (
|
||||
<Permissions requestClose={handlePageRequestClose} />
|
||||
)}
|
||||
{activePage === RoomSettingsPage.EmojisStickersPage && (
|
||||
<EmojisStickers requestClose={handlePageRequestClose} />
|
||||
)}
|
||||
{activePage === RoomSettingsPage.DeveloperToolsPage && (
|
||||
<DeveloperTools requestClose={handlePageRequestClose} />
|
||||
)}
|
||||
</PageRoot>
|
||||
</PowerLevelTagsContextProvider>
|
||||
</PowerLevelsContextProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ import React from 'react';
|
|||
import { RoomSettings } from './RoomSettings';
|
||||
import { Modal500 } from '../../components/Modal500';
|
||||
import { useCloseRoomSettings, useRoomSettingsState } from '../../state/hooks/roomSettings';
|
||||
import { useAllJoinedRoomsSet, useGetRoom } from '../../hooks/useGetRoom';
|
||||
import { RoomSettingsState } from '../../state/roomSettings';
|
||||
import { RoomProvider } from '../../hooks/useRoom';
|
||||
import { SpaceProvider } from '../../hooks/useSpace';
|
||||
import { useClient } from '../../hooks/useClient';
|
||||
import { useObservableValue } from '../../hooks/useObservableValue';
|
||||
|
||||
type RenderSettingsProps = {
|
||||
state: RoomSettingsState;
|
||||
|
|
@ -13,17 +14,15 @@ type RenderSettingsProps = {
|
|||
function RenderSettings({ state }: RenderSettingsProps) {
|
||||
const { roomId, spaceId, page } = state;
|
||||
const closeSettings = useCloseRoomSettings();
|
||||
const allJoinedRooms = useAllJoinedRoomsSet();
|
||||
const getRoom = useGetRoom(allJoinedRooms);
|
||||
const room = getRoom(roomId);
|
||||
const space = spaceId ? getRoom(spaceId) : undefined;
|
||||
const ct = useClient();
|
||||
const rooms = useObservableValue(ct.rooms.join);
|
||||
|
||||
if (!room) return null;
|
||||
if (!rooms.includes(roomId)) return null;
|
||||
|
||||
return (
|
||||
<Modal500 requestClose={closeSettings}>
|
||||
<SpaceProvider value={space ?? null}>
|
||||
<RoomProvider value={room}>
|
||||
<SpaceProvider value={spaceId ?? null}>
|
||||
<RoomProvider value={roomId}>
|
||||
<RoomSettings initialPage={page} requestClose={closeSettings} />
|
||||
</RoomProvider>
|
||||
</SpaceProvider>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds';
|
||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||
import { usePowerLevels } from '../../../hooks/usePowerLevels';
|
||||
import { usePowerLevelsContext } from '../../../hooks/usePowerLevels';
|
||||
import { useRoom } from '../../../hooks/useRoom';
|
||||
import {
|
||||
RoomProfile,
|
||||
|
|
@ -21,8 +21,8 @@ type GeneralProps = {
|
|||
};
|
||||
export function General({ requestClose }: GeneralProps) {
|
||||
const room = useRoom();
|
||||
const powerLevels = usePowerLevels(room);
|
||||
const creators = useRoomCreators(room);
|
||||
const powerLevels = usePowerLevelsContext();
|
||||
const permissions = useRoomPermissions(creators, powerLevels);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -23,19 +23,19 @@ export const useRoomStateEvent = (
|
|||
return stateEventResult;
|
||||
};
|
||||
|
||||
export const getRoomStateEventsQuery = (roomId: string, eventType: string) =>
|
||||
new QueryRange(
|
||||
getJoinRoomStateId(roomId, eventType, ''),
|
||||
getJoinRoomStateId(roomId, eventType, MAX_UNICODE)
|
||||
);
|
||||
|
||||
export const useRoomStateEvents = (
|
||||
roomId: string,
|
||||
eventType: string
|
||||
): PendingObservableResult<JoinRoomStateObject[]> => {
|
||||
const ct = useClient();
|
||||
const stateEventPromise = useMemo(
|
||||
() =>
|
||||
ct.storage.joinRoomState.observableRead.query(
|
||||
new QueryRange(
|
||||
getJoinRoomStateId(roomId, eventType, ''),
|
||||
getJoinRoomStateId(roomId, eventType, MAX_UNICODE)
|
||||
)
|
||||
),
|
||||
() => ct.storage.joinRoomState.observableRead.query(getRoomStateEventsQuery(roomId, eventType)),
|
||||
[ct, roomId, eventType]
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,83 +1,75 @@
|
|||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { MatrixError, Room } from 'matrix-js-sdk';
|
||||
import { RoomCanonicalAliasEventContent } from 'matrix-js-sdk/lib/types';
|
||||
import { ObjectReader } from 'neurone/lib/utils';
|
||||
import { parseMatrixResp } from 'neurone/lib/matrix-request';
|
||||
import { getAliases, putStateEvent, GetAliasesOK } from 'neurone/lib/api/matrix/client/v3/rooms';
|
||||
import { deleteRoom, putRoom } from 'neurone/lib/api/matrix/client/v3/directory';
|
||||
import { AsyncState, useAsyncCallback } from './useAsyncCallback';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
import { useAlive } from './useAlive';
|
||||
import { useStateEvent } from './useStateEvent';
|
||||
import { StateEvent } from '../../types/matrix/room';
|
||||
import { getStateEvent } from '../utils/room';
|
||||
import { useClient } from './useClient';
|
||||
import { useObservableValue } from './useObservableValue';
|
||||
import { RoomCanonicalAliasContent, StateEventType } from '../../types/matrix/room';
|
||||
|
||||
export const usePublishedAliases = (room: Room): [string | undefined, string[]] => {
|
||||
const aliasContent = useStateEvent(
|
||||
room,
|
||||
StateEvent.RoomCanonicalAlias
|
||||
)?.getContent<RoomCanonicalAliasEventContent>();
|
||||
export const usePublishedAliases = (roomId: string): [string | undefined, string[]] => {
|
||||
const ct = useClient();
|
||||
const canonicalAliasEvents = useObservableValue(ct.state.join.canonicalAlias);
|
||||
const content = canonicalAliasEvents.find((state) => state.roomId === roomId)?.event.content;
|
||||
|
||||
const canonicalAlias = aliasContent?.alias;
|
||||
const alias = ObjectReader.string(content, 'alias');
|
||||
const altAliases = ObjectReader.array(content, 'alt_aliases')?.filter(
|
||||
(a): a is string => typeof a === 'string'
|
||||
);
|
||||
|
||||
const publishedAliases = useMemo(() => {
|
||||
const aliases: string[] = [];
|
||||
if (typeof aliasContent?.alias === 'string') {
|
||||
aliases.push(aliasContent.alias);
|
||||
}
|
||||
aliasContent?.alt_aliases?.forEach((alias) => {
|
||||
if (typeof alias === 'string') {
|
||||
aliases.push(alias);
|
||||
}
|
||||
});
|
||||
if (alias) aliases.push(alias);
|
||||
if (altAliases) aliases.push(...altAliases);
|
||||
return aliases;
|
||||
}, [aliasContent]);
|
||||
}, [alias, altAliases]);
|
||||
|
||||
return [canonicalAlias, publishedAliases];
|
||||
return [alias, publishedAliases];
|
||||
};
|
||||
|
||||
export const useSetMainAlias = (room: Room): ((alias: string | undefined) => Promise<void>) => {
|
||||
const mx = useMatrixClient();
|
||||
export const useSetMainAlias = (
|
||||
roomId: string,
|
||||
publishedAliases: string[]
|
||||
): ((alias: string | undefined) => Promise<void>) => {
|
||||
const ct = useClient();
|
||||
|
||||
const mainAlias = useCallback(
|
||||
async (alias: string | undefined) => {
|
||||
const content = getStateEvent(
|
||||
room,
|
||||
StateEvent.RoomCanonicalAlias
|
||||
)?.getContent<RoomCanonicalAliasEventContent>();
|
||||
|
||||
const altAliases: string[] = [];
|
||||
if (content?.alias && content.alias !== alias) {
|
||||
altAliases.push(content.alias);
|
||||
}
|
||||
content?.alt_aliases?.forEach((a) => {
|
||||
if (a !== alias) {
|
||||
altAliases.push(a);
|
||||
}
|
||||
});
|
||||
|
||||
const newContent: RoomCanonicalAliasEventContent = {
|
||||
const content: RoomCanonicalAliasContent = {
|
||||
alias,
|
||||
alt_aliases: altAliases,
|
||||
alt_aliases: publishedAliases.filter((a) => a !== alias),
|
||||
};
|
||||
|
||||
await mx.sendStateEvent(room.roomId, StateEvent.RoomCanonicalAlias as any, newContent);
|
||||
const resp = await ct.requestAuthJSON(
|
||||
putStateEvent({
|
||||
pathParams: { roomId, eventType: StateEventType.RoomCanonicalAlias, stateKey: '' },
|
||||
body: content,
|
||||
})
|
||||
);
|
||||
const [err] = await parseMatrixResp(resp);
|
||||
if (err) throw new Error(err.data.error ?? err.data.errcode);
|
||||
},
|
||||
[mx, room]
|
||||
[ct, roomId, publishedAliases]
|
||||
);
|
||||
|
||||
return mainAlias;
|
||||
};
|
||||
|
||||
export const usePublishUnpublishAliases = (
|
||||
room: Room
|
||||
roomId: string,
|
||||
publishedAliases: string[],
|
||||
canonicalAlias?: string
|
||||
): {
|
||||
publishAliases: (aliases: string[]) => Promise<void>;
|
||||
unpublishAliases: (aliases: string[]) => Promise<void>;
|
||||
} => {
|
||||
const mx = useMatrixClient();
|
||||
const ct = useClient();
|
||||
|
||||
const publishAliases = useCallback(
|
||||
async (aliases: string[]) => {
|
||||
const content = getStateEvent(
|
||||
room,
|
||||
StateEvent.RoomCanonicalAlias
|
||||
)?.getContent<RoomCanonicalAliasEventContent>();
|
||||
const altAliases = content?.alt_aliases ?? [];
|
||||
const altAliases = publishedAliases.filter((a) => a !== canonicalAlias);
|
||||
|
||||
aliases.forEach((alias) => {
|
||||
if (!altAliases.includes(alias)) {
|
||||
|
|
@ -85,38 +77,42 @@ export const usePublishUnpublishAliases = (
|
|||
}
|
||||
});
|
||||
|
||||
const newContent: RoomCanonicalAliasEventContent = {
|
||||
alias: content?.alias,
|
||||
const content: RoomCanonicalAliasContent = {
|
||||
alias: canonicalAlias,
|
||||
alt_aliases: altAliases,
|
||||
};
|
||||
|
||||
await mx.sendStateEvent(room.roomId, StateEvent.RoomCanonicalAlias as any, newContent);
|
||||
const resp = await ct.requestAuthJSON(
|
||||
putStateEvent({
|
||||
pathParams: { roomId, eventType: StateEventType.RoomCanonicalAlias, stateKey: '' },
|
||||
body: content,
|
||||
})
|
||||
);
|
||||
const [err] = await parseMatrixResp(resp);
|
||||
if (err) throw new Error(err.data.error ?? err.data.errcode);
|
||||
},
|
||||
[mx, room]
|
||||
[ct, roomId, publishedAliases, canonicalAlias]
|
||||
);
|
||||
|
||||
const unpublishAliases = useCallback(
|
||||
async (aliases: string[]) => {
|
||||
const content = getStateEvent(
|
||||
room,
|
||||
StateEvent.RoomCanonicalAlias
|
||||
)?.getContent<RoomCanonicalAliasEventContent>();
|
||||
const altAliases: string[] = [];
|
||||
const altAliases: string[] = publishedAliases.filter((alias) => !aliases.includes(alias));
|
||||
|
||||
content?.alt_aliases?.forEach((alias) => {
|
||||
if (!aliases.includes(alias)) {
|
||||
altAliases.push(alias);
|
||||
}
|
||||
});
|
||||
|
||||
const newContent: RoomCanonicalAliasEventContent = {
|
||||
alias: content?.alias,
|
||||
const content: RoomCanonicalAliasContent = {
|
||||
alias: canonicalAlias,
|
||||
alt_aliases: altAliases,
|
||||
};
|
||||
|
||||
await mx.sendStateEvent(room.roomId, StateEvent.RoomCanonicalAlias as any, newContent);
|
||||
const resp = await ct.requestAuthJSON(
|
||||
putStateEvent({
|
||||
pathParams: { roomId, eventType: StateEventType.RoomCanonicalAlias, stateKey: '' },
|
||||
body: content,
|
||||
})
|
||||
);
|
||||
const [err] = await parseMatrixResp(resp);
|
||||
if (err) throw new Error(err.data.error ?? err.data.errcode);
|
||||
},
|
||||
[mx, room]
|
||||
[ct, roomId, publishedAliases, canonicalAlias]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
@ -128,18 +124,20 @@ export const usePublishUnpublishAliases = (
|
|||
export const useLocalAliases = (
|
||||
roomId: string
|
||||
): {
|
||||
localAliasesState: AsyncState<string[], MatrixError>;
|
||||
localAliasesState: AsyncState<string[], unknown>;
|
||||
addLocalAlias: (alias: string) => Promise<void>;
|
||||
removeLocalAlias: (alias: string) => Promise<void>;
|
||||
} => {
|
||||
const mx = useMatrixClient();
|
||||
const ct = useClient();
|
||||
const alive = useAlive();
|
||||
|
||||
const [aliasesState, loadAliases] = useAsyncCallback<string[], MatrixError, []>(
|
||||
const [aliasesState, loadAliases] = useAsyncCallback<string[], unknown, []>(
|
||||
useCallback(async () => {
|
||||
const content = await mx.getLocalAliases(roomId);
|
||||
return content.aliases;
|
||||
}, [mx, roomId])
|
||||
const resp = await ct.requestAuthJSON(getAliases({ pathParams: { roomId } }));
|
||||
const [err, result] = await parseMatrixResp<GetAliasesOK>(resp);
|
||||
if (err) throw new Error(err.data.error ?? err.data.errcode);
|
||||
return result.aliases;
|
||||
}, [ct, roomId])
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -148,18 +146,36 @@ export const useLocalAliases = (
|
|||
|
||||
const addLocalAlias = useCallback(
|
||||
async (alias: string) => {
|
||||
await mx.createAlias(alias, roomId);
|
||||
const resp = await ct.requestAuthJSON(
|
||||
putRoom({
|
||||
pathParams: { roomAlias: alias },
|
||||
body: { room_id: roomId },
|
||||
})
|
||||
);
|
||||
|
||||
const [err] = await parseMatrixResp(resp);
|
||||
if (err) {
|
||||
if (err.status === 409) {
|
||||
throw new Error('Address is already in use!');
|
||||
}
|
||||
throw new Error(err.data.error ?? err.data.errcode);
|
||||
}
|
||||
|
||||
if (alive()) await loadAliases();
|
||||
},
|
||||
[mx, roomId, loadAliases, alive]
|
||||
[ct, roomId, loadAliases, alive]
|
||||
);
|
||||
|
||||
const removeLocalAlias = useCallback(
|
||||
async (alias: string) => {
|
||||
await mx.deleteAlias(alias);
|
||||
const resp = await ct.requestAuthJSON(deleteRoom({ pathParams: { roomAlias: alias } }));
|
||||
|
||||
const [err] = await parseMatrixResp(resp);
|
||||
if (err) throw new Error(err.data.error ?? err.data.errcode);
|
||||
|
||||
if (alive()) await loadAliases();
|
||||
},
|
||||
[mx, loadAliases, alive]
|
||||
[ct, loadAliases, alive]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
import {
|
||||
getListRoom,
|
||||
GetListRoomOK,
|
||||
putListRoom,
|
||||
} from 'neurone/lib/api/matrix/client/v3/directory';
|
||||
import { parseMatrixResp } from 'neurone/lib/matrix-request';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { Visibility } from 'matrix-js-sdk';
|
||||
import { useAsyncCallback } from './useAsyncCallback';
|
||||
import { useMatrixClient } from './useMatrixClient';
|
||||
import { useClient } from './useClient';
|
||||
|
||||
export const useRoomDirectoryVisibility = (roomId: string) => {
|
||||
const mx = useMatrixClient();
|
||||
const ct = useClient();
|
||||
|
||||
const [visibilityState, loadVisibility] = useAsyncCallback(
|
||||
useCallback(async () => {
|
||||
const v = await mx.getRoomDirectoryVisibility(roomId);
|
||||
return v.visibility === Visibility.Public;
|
||||
}, [mx, roomId])
|
||||
const resp = await ct.requestAuthJSON(getListRoom({ pathParams: { roomId } }));
|
||||
const [err, result] = await parseMatrixResp<GetListRoomOK>(resp);
|
||||
if (err) throw new Error(err.data.error ?? err.data.errcode);
|
||||
return result?.visibility === 'public';
|
||||
}, [ct, roomId])
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -18,14 +25,18 @@ export const useRoomDirectoryVisibility = (roomId: string) => {
|
|||
}, [loadVisibility]);
|
||||
|
||||
const setVisibility = useCallback(
|
||||
async (visibility: boolean) => {
|
||||
await mx.setRoomDirectoryVisibility(
|
||||
roomId,
|
||||
visibility ? Visibility.Public : Visibility.Private
|
||||
async (visible: boolean) => {
|
||||
const resp = await ct.requestAuthJSON(
|
||||
putListRoom({
|
||||
pathParams: { roomId },
|
||||
body: { visibility: visible ? 'public' : 'private' },
|
||||
})
|
||||
);
|
||||
const [err] = await parseMatrixResp(resp);
|
||||
if (err) throw new Error(err.data.error ?? err.data.errcode);
|
||||
await loadVisibility();
|
||||
},
|
||||
[mx, roomId, loadVisibility]
|
||||
[ct, roomId, loadVisibility]
|
||||
);
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -139,8 +139,8 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize)
|
|||
<UserRoomProfileRenderer />
|
||||
<CreateRoomModalRenderer />
|
||||
<CreateSpaceModalRenderer />
|
||||
{/*<RoomSettingsRenderer />
|
||||
<SpaceSettingsRenderer />
|
||||
<RoomSettingsRenderer />
|
||||
{/*<SpaceSettingsRenderer />
|
||||
<ReceiveSelfDeviceVerification />
|
||||
<AutoRestoreBackupOnVerification /> */}
|
||||
</ClientRoomsNotificationPreferences>
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export const createUploadAtom = (file: UploadContent) => {
|
|||
file,
|
||||
status: UploadStatus.Idle,
|
||||
});
|
||||
return atom<Upload, [UploadAtomAction], undefined>(
|
||||
return atom<Upload, [UploadAtomAction], void>(
|
||||
(get) => get(baseUploadAtom),
|
||||
(get, set, update) => {
|
||||
const uploadState = get(baseUploadAtom);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ export const BreakWord = style({
|
|||
wordBreak: 'break-word',
|
||||
});
|
||||
|
||||
export const PreWrap = style({
|
||||
whiteSpace: 'pre-wrap',
|
||||
});
|
||||
|
||||
export const LineClamp2 = style({
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { RoomMembership } from 'neurone/lib/api/types';
|
||||
import { HistoryVisibility } from 'neurone/lib/crypto/machine';
|
||||
import { IImageInfo } from './common';
|
||||
|
||||
export enum Membership {
|
||||
|
|
@ -180,3 +181,11 @@ export type RoomServerAclContent = {
|
|||
allow_ip_literals?: boolean;
|
||||
deny?: string[];
|
||||
};
|
||||
|
||||
export type RoomHistoryVisibilityContent = {
|
||||
history_visibility: HistoryVisibility;
|
||||
};
|
||||
export type RoomCanonicalAliasContent = {
|
||||
alias?: string;
|
||||
alt_aliases?: string[];
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue