import React, { useState, useEffect, useRef } from 'react'; import PropTypes from 'prop-types'; import './CreateRoom.scss'; import { twemojify } from '../../../util/twemojify'; import initMatrix from '../../../client/initMatrix'; import cons from '../../../client/state/cons'; import navigation from '../../../client/state/navigation'; import { selectRoom, openReusableContextMenu } from '../../../client/action/navigation'; import * as roomActions from '../../../client/action/room'; import { isRoomAliasAvailable, getIdServer } from '../../../util/matrixUtil'; import { getEventCords } from '../../../util/common'; import Text from '../../atoms/text/Text'; import Button from '../../atoms/button/Button'; import Toggle from '../../atoms/button/Toggle'; import IconButton from '../../atoms/button/IconButton'; import { MenuHeader, MenuItem } from '../../atoms/context-menu/ContextMenu'; import Input from '../../atoms/input/Input'; import Spinner from '../../atoms/spinner/Spinner'; import SegmentControl from '../../atoms/segmented-controls/SegmentedControls'; import Dialog from '../../molecules/dialog/Dialog'; import SettingTile from '../../molecules/setting-tile/SettingTile'; import HashPlusIC from '../../../../public/res/ic/outlined/hash-plus.svg'; import SpacePlusIC from '../../../../public/res/ic/outlined/space-plus.svg'; import HashIC from '../../../../public/res/ic/outlined/hash.svg'; import HashLockIC from '../../../../public/res/ic/outlined/hash-lock.svg'; import HashGlobeIC from '../../../../public/res/ic/outlined/hash-globe.svg'; import SpaceIC from '../../../../public/res/ic/outlined/space.svg'; import SpaceLockIC from '../../../../public/res/ic/outlined/space-lock.svg'; import SpaceGlobeIC from '../../../../public/res/ic/outlined/space-globe.svg'; import ChevronBottomIC from '../../../../public/res/ic/outlined/chevron-bottom.svg'; import CrossIC from '../../../../public/res/ic/outlined/cross.svg'; function CreateRoomContent({ isSpace, parentId, onRequestClose }) { const [joinRule, setJoinRule] = useState(parentId ? 'restricted' : 'invite'); const [isEncrypted, setIsEncrypted] = useState(true); const [isCreatingRoom, setIsCreatingRoom] = useState(false); const [creatingError, setCreatingError] = useState(null); const [isValidAddress, setIsValidAddress] = useState(null); const [addressValue, setAddressValue] = useState(undefined); const [roleIndex, setRoleIndex] = useState(0); const addressRef = useRef(null); const mx = initMatrix.matrixClient; const userHs = getIdServer(mx.getUserId()); useEffect(() => { const { roomList } = initMatrix; const onCreated = (roomId) => { setIsCreatingRoom(false); setCreatingError(null); setIsValidAddress(null); setAddressValue(undefined); if (!mx.getRoom(roomId)?.isSpaceRoom()) { selectRoom(roomId); } onRequestClose(); }; roomList.on(cons.events.roomList.ROOM_CREATED, onCreated); return () => { roomList.removeListener(cons.events.roomList.ROOM_CREATED, onCreated); }; }, []); const handleSubmit = async (evt) => { evt.preventDefault(); const { target } = evt; if (isCreatingRoom) return; setIsCreatingRoom(true); setCreatingError(null); const name = target.name.value; let topic = target.topic.value; if (topic.trim() === '') topic = undefined; let roomAlias; if (joinRule === 'public') { roomAlias = addressRef?.current?.value; if (roomAlias.trim() === '') roomAlias = undefined; } const powerLevel = roleIndex === 1 ? 101 : undefined; try { await roomActions.createRoom({ name, topic, joinRule, alias: roomAlias, isEncrypted: (isSpace || joinRule === 'public') ? false : isEncrypted, powerLevel, isSpace, parentId, }); } catch (e) { if (e.message === 'M_UNKNOWN: Invalid characters in room alias') { setCreatingError('ERROR: Invalid characters in address'); setIsValidAddress(false); } else if (e.message === 'M_ROOM_IN_USE: Room alias already taken') { setCreatingError('ERROR: This address is already in use'); setIsValidAddress(false); } else setCreatingError(e.message); setIsCreatingRoom(false); } }; const validateAddress = (e) => { const myAddress = e.target.value; setIsValidAddress(null); setAddressValue(e.target.value); setCreatingError(null); setTimeout(async () => { if (myAddress !== addressRef.current.value) return; const roomAlias = addressRef.current.value; if (roomAlias === '') return; const roomAddress = `#${roomAlias}:${userHs}`; if (await isRoomAliasAvailable(roomAddress)) { setIsValidAddress(true); } else { setIsValidAddress(false); } }, 1000); }; const joinRules = ['invite', 'restricted', 'public']; const joinRuleShortText = ['Private', 'Restricted', 'Public']; const joinRuleText = ['Private (invite only)', 'Restricted (space member can join)', 'Public (anyone can join)']; const jrRoomIC = [HashLockIC, HashIC, HashGlobeIC]; const jrSpaceIC = [SpaceLockIC, SpaceIC, SpaceGlobeIC]; const handleJoinRule = (evt) => { openReusableContextMenu( 'bottom', getEventCords(evt, '.btn-surface'), (closeMenu) => ( <> Visibility (who can join) { joinRules.map((rule) => ( { closeMenu(); setJoinRule(rule); }} disabled={!parentId && rule === 'restricted'} > { joinRuleText[joinRules.indexOf(rule)] } )) } ), ); }; return (
{joinRuleShortText[joinRules.indexOf(joinRule)]} )} content={{`Select who can join this ${isSpace ? 'space' : 'room'}.`}} /> {joinRule === 'public' && (
{isSpace ? 'Space address' : 'Room address'}
# {`:${userHs}`}
{isValidAddress === false && {`#${addressValue}:${userHs} is already in use`}}
)} {!isSpace && joinRule !== 'public' && ( } content={You can’t disable this later. Bridges & most bots won’t work yet.} /> )} )} content={( Selecting Admin sets 100 power level whereas Founder sets 101. )} />
{isCreatingRoom && (
{`Creating ${isSpace ? 'space' : 'room'}...`}
)} {typeof creatingError === 'string' && {creatingError}}
); } CreateRoomContent.defaultProps = { parentId: null, }; CreateRoomContent.propTypes = { isSpace: PropTypes.bool.isRequired, parentId: PropTypes.string, onRequestClose: PropTypes.func.isRequired, }; function useWindowToggle() { const [create, setCreate] = useState(null); useEffect(() => { const handleOpen = (isSpace, parentId) => { setCreate({ isSpace, parentId, }); }; navigation.on(cons.events.navigation.CREATE_ROOM_OPENED, handleOpen); return () => { navigation.removeListener(cons.events.navigation.CREATE_ROOM_OPENED, handleOpen); }; }, []); const onRequestClose = () => setCreate(null); return [create, onRequestClose]; } function CreateRoom() { const [create, onRequestClose] = useWindowToggle(); const { isSpace, parentId } = create ?? {}; const mx = initMatrix.matrixClient; const room = mx.getRoom(parentId); return ( {parentId ? twemojify(room.name) : 'Home'} {` — create ${isSpace ? 'space' : 'room'}`} )} contentOptions={} onRequestClose={onRequestClose} > { create ? ( ) :
}
); } export default CreateRoom;