Support room version 12 (#2399)
* WIP - support room version 12 * add room creators hook * revert changes from powerlevels * improve use room creators hook * add hook to get dm users * add options to add creators in create room/space * add member item component in member drawer * remove unused import * extract member drawer header component * get room creators as set only if room version support them * add room permissions hook * support room v12 creators power * make predecessor event id optional * add info about founders in permissions * allow to create infinite powers to room creators * allow everyone with permission to create infinite power * handle additional creators in room upgrade * add option to follow space tombstone
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { FormEventHandler, useCallback, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
color,
|
||||
@@ -14,54 +14,172 @@ import {
|
||||
IconButton,
|
||||
Icon,
|
||||
Icons,
|
||||
Input,
|
||||
} from 'folds';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import { MatrixError } from 'matrix-js-sdk';
|
||||
import { RoomCreateEventContent, RoomTombstoneEventContent } from 'matrix-js-sdk/lib/types';
|
||||
import { MatrixError, Method } from 'matrix-js-sdk';
|
||||
import { RoomTombstoneEventContent } 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 { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
|
||||
import { IPowerLevels, powerLevelAPI } from '../../../hooks/usePowerLevels';
|
||||
import { StateEvent } from '../../../../types/matrix/room';
|
||||
import { IRoomCreateContent, StateEvent } from '../../../../types/matrix/room';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { useStateEvent } from '../../../hooks/useStateEvent';
|
||||
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
|
||||
import { useCapabilities } from '../../../hooks/useCapabilities';
|
||||
import { stopPropagation } from '../../../utils/keyboard';
|
||||
import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions';
|
||||
import {
|
||||
AdditionalCreatorInput,
|
||||
RoomVersionSelector,
|
||||
useAdditionalCreators,
|
||||
} from '../../../components/create-room';
|
||||
import { useAlive } from '../../../hooks/useAlive';
|
||||
import { creatorsSupported } from '../../../utils/matrix';
|
||||
import { useRoomCreators } from '../../../hooks/useRoomCreators';
|
||||
import { BreakWord } from '../../../styles/Text.css';
|
||||
|
||||
function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) {
|
||||
const mx = useMatrixClient();
|
||||
const room = useRoom();
|
||||
const alive = useAlive();
|
||||
const creators = useRoomCreators(room);
|
||||
|
||||
const capabilities = useCapabilities();
|
||||
const roomVersions = capabilities['m.room_versions'];
|
||||
const [selectedRoomVersion, selectRoomVersion] = useState(roomVersions?.default ?? '1');
|
||||
useEffect(() => {
|
||||
// capabilities load async
|
||||
selectRoomVersion(roomVersions?.default ?? '1');
|
||||
}, [roomVersions?.default]);
|
||||
|
||||
const allowAdditionalCreators = creatorsSupported(selectedRoomVersion);
|
||||
const { additionalCreators, addAdditionalCreator, removeAdditionalCreator } =
|
||||
useAdditionalCreators(Array.from(creators));
|
||||
|
||||
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,
|
||||
});
|
||||
},
|
||||
[mx, room]
|
||||
)
|
||||
);
|
||||
|
||||
const upgrading = upgradeState.status === AsyncStatus.Loading;
|
||||
|
||||
const handleUpgradeRoom = () => {
|
||||
const version = selectedRoomVersion;
|
||||
|
||||
upgrade(version, allowAdditionalCreators ? additionalCreators : undefined).then(() => {
|
||||
if (alive()) {
|
||||
requestClose();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
onDeactivate: requestClose,
|
||||
clickOutsideDeactivates: true,
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Dialog variant="Surface">
|
||||
<Header
|
||||
style={{
|
||||
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
|
||||
borderBottomWidth: config.borderWidth.B300,
|
||||
}}
|
||||
variant="Surface"
|
||||
size="500"
|
||||
>
|
||||
<Box grow="Yes">
|
||||
<Text size="H4">{room.isSpaceRoom() ? 'Space Upgrade' : 'Room Upgrade'}</Text>
|
||||
</Box>
|
||||
<IconButton size="300" onClick={requestClose} radii="300">
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
</Header>
|
||||
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
|
||||
<Text priority="400" style={{ color: color.Critical.Main }}>
|
||||
<b>This action is irreversible!</b>
|
||||
</Text>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Options</Text>
|
||||
<RoomVersionSelector
|
||||
versions={roomVersions?.available ? Object.keys(roomVersions.available) : ['1']}
|
||||
value={selectedRoomVersion}
|
||||
onChange={selectRoomVersion}
|
||||
disabled={upgrading}
|
||||
/>
|
||||
{allowAdditionalCreators && (
|
||||
<SequenceCard
|
||||
style={{ padding: config.space.S300 }}
|
||||
variant="SurfaceVariant"
|
||||
direction="Column"
|
||||
gap="500"
|
||||
>
|
||||
<AdditionalCreatorInput
|
||||
additionalCreators={additionalCreators}
|
||||
onSelect={addAdditionalCreator}
|
||||
onRemove={removeAdditionalCreator}
|
||||
disabled={upgrading}
|
||||
/>
|
||||
</SequenceCard>
|
||||
)}
|
||||
</Box>
|
||||
{upgradeState.status === AsyncStatus.Error && (
|
||||
<Text className={BreakWord} style={{ color: color.Critical.Main }} size="T200">
|
||||
{(upgradeState.error as MatrixError).message}
|
||||
</Text>
|
||||
)}
|
||||
<Button
|
||||
onClick={handleUpgradeRoom}
|
||||
variant="Secondary"
|
||||
disabled={upgrading}
|
||||
before={upgrading && <Spinner size="200" variant="Secondary" fill="Solid" />}
|
||||
>
|
||||
<Text size="B400">{room.isSpaceRoom() ? 'Upgrade Space' : 'Upgrade Room'}</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Dialog>
|
||||
</FocusTrap>
|
||||
</OverlayCenter>
|
||||
</Overlay>
|
||||
);
|
||||
}
|
||||
|
||||
type RoomUpgradeProps = {
|
||||
powerLevels: IPowerLevels;
|
||||
permissions: RoomPermissionsAPI;
|
||||
requestClose: () => void;
|
||||
};
|
||||
export function RoomUpgrade({ powerLevels, requestClose }: RoomUpgradeProps) {
|
||||
export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) {
|
||||
const mx = useMatrixClient();
|
||||
const room = useRoom();
|
||||
const { navigateRoom, navigateSpace } = useRoomNavigate();
|
||||
const createContent = useStateEvent(
|
||||
room,
|
||||
StateEvent.RoomCreate
|
||||
)?.getContent<RoomCreateEventContent>();
|
||||
const roomVersion = createContent?.room_version ?? 1;
|
||||
)?.getContent<IRoomCreateContent>();
|
||||
const roomVersion = createContent?.room_version ?? '1';
|
||||
const predecessorRoomId = createContent?.predecessor?.room_id;
|
||||
|
||||
const capabilities = useCapabilities();
|
||||
const defaultRoomVersion = capabilities['m.room_versions']?.default;
|
||||
|
||||
const tombstoneContent = useStateEvent(
|
||||
room,
|
||||
StateEvent.RoomTombstone
|
||||
)?.getContent<RoomTombstoneEventContent>();
|
||||
const replacementRoom = tombstoneContent?.replacement_room;
|
||||
|
||||
const userPowerLevel = powerLevelAPI.getPowerLevel(powerLevels, mx.getSafeUserId());
|
||||
const canUpgrade = powerLevelAPI.canSendStateEvent(
|
||||
powerLevels,
|
||||
StateEvent.RoomTombstone,
|
||||
userPowerLevel
|
||||
);
|
||||
const canUpgrade = permissions.stateEvent(StateEvent.RoomTombstone, mx.getSafeUserId());
|
||||
|
||||
const handleOpenRoom = () => {
|
||||
if (replacementRoom) {
|
||||
@@ -85,31 +203,8 @@ export function RoomUpgrade({ powerLevels, requestClose }: RoomUpgradeProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const [upgradeState, upgrade] = useAsyncCallback(
|
||||
useCallback(
|
||||
async (version: string) => {
|
||||
await mx.upgradeRoom(room.roomId, version);
|
||||
},
|
||||
[mx, room]
|
||||
)
|
||||
);
|
||||
|
||||
const upgrading = upgradeState.status === AsyncStatus.Loading;
|
||||
|
||||
const [prompt, setPrompt] = useState(false);
|
||||
|
||||
const handleSubmitUpgrade: FormEventHandler<HTMLFormElement> = (evt) => {
|
||||
evt.preventDefault();
|
||||
|
||||
const target = evt.target as HTMLFormElement | undefined;
|
||||
const versionInput = target?.versionInput as HTMLInputElement | undefined;
|
||||
const version = versionInput?.value.trim();
|
||||
if (!version) return;
|
||||
|
||||
upgrade(version);
|
||||
setPrompt(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<SequenceCard
|
||||
className={SequenceCardStyle}
|
||||
@@ -123,7 +218,7 @@ export function RoomUpgrade({ powerLevels, requestClose }: RoomUpgradeProps) {
|
||||
replacementRoom
|
||||
? tombstoneContent.body ||
|
||||
`This ${room.isSpaceRoom() ? 'space' : 'room'} has been replaced!`
|
||||
: `Current room version: ${roomVersion}.`
|
||||
: `Current version: ${roomVersion}.`
|
||||
}
|
||||
after={
|
||||
<Box alignItems="Center" gap="200">
|
||||
@@ -155,8 +250,7 @@ export function RoomUpgrade({ powerLevels, requestClose }: RoomUpgradeProps) {
|
||||
variant="Secondary"
|
||||
fill="Solid"
|
||||
radii="300"
|
||||
disabled={upgrading || !canUpgrade}
|
||||
before={upgrading && <Spinner size="100" variant="Secondary" fill="Solid" />}
|
||||
disabled={!canUpgrade}
|
||||
onClick={() => setPrompt(true)}
|
||||
>
|
||||
<Text size="B300">Upgrade</Text>
|
||||
@@ -165,63 +259,7 @@ export function RoomUpgrade({ powerLevels, requestClose }: RoomUpgradeProps) {
|
||||
</Box>
|
||||
}
|
||||
>
|
||||
{upgradeState.status === AsyncStatus.Error && (
|
||||
<Text style={{ color: color.Critical.Main }} size="T200">
|
||||
{(upgradeState.error as MatrixError).message}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{prompt && (
|
||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||
<OverlayCenter>
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
onDeactivate: () => setPrompt(false),
|
||||
clickOutsideDeactivates: true,
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Dialog variant="Surface" as="form" onSubmit={handleSubmitUpgrade}>
|
||||
<Header
|
||||
style={{
|
||||
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
|
||||
borderBottomWidth: config.borderWidth.B300,
|
||||
}}
|
||||
variant="Surface"
|
||||
size="500"
|
||||
>
|
||||
<Box grow="Yes">
|
||||
<Text size="H4">{room.isSpaceRoom() ? 'Space Upgrade' : 'Room Upgrade'}</Text>
|
||||
</Box>
|
||||
<IconButton size="300" onClick={() => setPrompt(false)} radii="300">
|
||||
<Icon src={Icons.Cross} />
|
||||
</IconButton>
|
||||
</Header>
|
||||
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
|
||||
<Text priority="400" style={{ color: color.Critical.Main }}>
|
||||
<b>This action is irreversible!</b>
|
||||
</Text>
|
||||
<Box direction="Column" gap="100">
|
||||
<Text size="L400">Version</Text>
|
||||
<Input
|
||||
defaultValue={defaultRoomVersion}
|
||||
name="versionInput"
|
||||
variant="Background"
|
||||
required
|
||||
/>
|
||||
</Box>
|
||||
<Button type="submit" variant="Secondary">
|
||||
<Text size="B400">
|
||||
{room.isSpaceRoom() ? 'Upgrade Space' : 'Upgrade Room'}
|
||||
</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Dialog>
|
||||
</FocusTrap>
|
||||
</OverlayCenter>
|
||||
</Overlay>
|
||||
)}
|
||||
{prompt && <RoomUpgradeDialog requestClose={() => setPrompt(false)} />}
|
||||
</SettingTile>
|
||||
</SequenceCard>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user