import React, { useCallback, useEffect, useState } from 'react'; import { Button, color, Spinner, Text, Overlay, OverlayBackdrop, OverlayCenter, Dialog, Header, config, Box, IconButton, Icon, 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 { 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 { 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'; import { useModalStyle } from '../../../hooks/useModalStyle'; import { stateToCarry } from '../../../utils/roomUpgradeState'; function RoomUpgradeDialog({ requestClose }: { requestClose: () => void }) { const mx = useMatrixClient(); const room = useRoom(); const alive = useAlive(); const creators = useRoomCreators(room); const modalStyle = useModalStyle(480); 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[]) => { // [Gitea #327] Synapse doesn't copy Lotus room state (voice limit, call // policy, soundboard, retention, role tags, emoji packs) into the // replacement room: read it first, re-create it after the upgrade. const carried = stateToCarry( Array.from(room.currentState.events.values()).flatMap((byKey) => Array.from(byKey.values()), ), ); const res = await mx.http.authedRequest<{ replacement_room?: string }>( Method.Post, `/rooms/${room.roomId}/upgrade`, undefined, { new_version: version, additional_creators: newAdditionalCreators, }, ); const newRoomId = res?.replacement_room; if (!newRoomId || carried.length === 0) return; const results = await Promise.allSettled( carried.map((st) => mx.http.authedRequest( Method.Put, `/rooms/${encodeURIComponent(newRoomId)}/state/${encodeURIComponent(st.type)}/${encodeURIComponent(st.stateKey)}`, undefined, st.content, ), ), ); const failed = results.filter((r) => r.status === 'rejected').length; if (failed > 0) { throw new Error( `Room upgraded, but ${failed} of ${carried.length} Lotus settings (voice limit, call policy, soundboard, retention…) could not be copied to the new room.`, ); } }, [mx, room], ), ); const upgrading = upgradeState.status === AsyncStatus.Loading; const handleUpgradeRoom = () => { const version = selectedRoomVersion; upgrade(version, allowAdditionalCreators ? additionalCreators : undefined).then(() => { if (alive()) { requestClose(); } }); }; return ( }>
{room.isSpaceRoom() ? 'Space Upgrade' : 'Room Upgrade'}
This action is irreversible! Options {allowAdditionalCreators && ( )} {upgradeState.status === AsyncStatus.Error && ( {(upgradeState.error as MatrixError).message} )}
); } type RoomUpgradeProps = { permissions: RoomPermissionsAPI; requestClose: () => void; }; export function RoomUpgrade({ permissions, requestClose }: RoomUpgradeProps) { const mx = useMatrixClient(); const room = 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 canUpgrade = permissions.stateEvent(StateEvent.RoomTombstone, mx.getSafeUserId()); const handleOpenRoom = () => { if (replacementRoom) { requestClose(); if (room.isSpaceRoom()) { navigateSpace(replacementRoom); } else { navigateRoom(replacementRoom); } } }; const handleOpenOldRoom = () => { if (predecessorRoomId) { requestClose(); if (room.isSpaceRoom()) { navigateSpace(predecessorRoomId); } else { navigateRoom(predecessorRoomId, createContent.predecessor?.event_id); } } }; const [prompt, setPrompt] = useState(false); return ( {predecessorRoomId && ( )} {replacementRoom ? ( ) : ( )} } > {prompt && setPrompt(false)} />} ); }