2021-07-28 16:15:52 +03:00
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
|
|
|
import PropTypes from 'prop-types';
|
2021-08-31 16:13:31 +03:00
|
|
|
import './PublicRooms.scss';
|
2021-07-28 16:15:52 +03:00
|
|
|
|
|
|
|
import initMatrix from '../../../client/initMatrix';
|
|
|
|
import cons from '../../../client/state/cons';
|
|
|
|
import { selectRoom } from '../../../client/action/navigation';
|
|
|
|
import * as roomActions from '../../../client/action/room';
|
|
|
|
|
|
|
|
import Text from '../../atoms/text/Text';
|
|
|
|
import Button from '../../atoms/button/Button';
|
|
|
|
import IconButton from '../../atoms/button/IconButton';
|
|
|
|
import Spinner from '../../atoms/spinner/Spinner';
|
|
|
|
import Input from '../../atoms/input/Input';
|
|
|
|
import PopupWindow from '../../molecules/popup-window/PopupWindow';
|
2021-08-31 16:13:31 +03:00
|
|
|
import RoomTile from '../../molecules/room-tile/RoomTile';
|
2021-07-28 16:15:52 +03:00
|
|
|
|
|
|
|
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
|
|
|
import HashSearchIC from '../../../../public/res/ic/outlined/hash-search.svg';
|
|
|
|
|
|
|
|
const SEARCH_LIMIT = 20;
|
|
|
|
|
2021-08-08 11:58:22 +03:00
|
|
|
function TryJoinWithAlias({ alias, onRequestClose }) {
|
|
|
|
const [status, setStatus] = useState({
|
|
|
|
isJoining: false,
|
|
|
|
error: null,
|
|
|
|
roomId: null,
|
|
|
|
tempRoomId: null,
|
|
|
|
});
|
|
|
|
function handleOnRoomAdded(roomId) {
|
|
|
|
if (status.tempRoomId !== null && status.tempRoomId !== roomId) return;
|
|
|
|
setStatus({
|
|
|
|
isJoining: false, error: null, roomId, tempRoomId: null,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
initMatrix.roomList.on(cons.events.roomList.ROOM_JOINED, handleOnRoomAdded);
|
|
|
|
return () => {
|
|
|
|
initMatrix.roomList.removeListener(cons.events.roomList.ROOM_JOINED, handleOnRoomAdded);
|
|
|
|
};
|
|
|
|
}, [status]);
|
|
|
|
|
|
|
|
async function joinWithAlias() {
|
|
|
|
setStatus({
|
|
|
|
isJoining: true, error: null, roomId: null, tempRoomId: null,
|
|
|
|
});
|
|
|
|
try {
|
|
|
|
const roomId = await roomActions.join(alias, false);
|
|
|
|
setStatus({
|
|
|
|
isJoining: true, error: null, roomId: null, tempRoomId: roomId,
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
setStatus({
|
|
|
|
isJoining: false,
|
2021-08-31 16:13:31 +03:00
|
|
|
error: `Unable to join ${alias}. Either room is private or doesn't exist.`,
|
2021-08-08 11:58:22 +03:00
|
|
|
roomId: null,
|
|
|
|
tempRoomId: null,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="try-join-with-alias">
|
|
|
|
{status.roomId === null && !status.isJoining && status.error === null && (
|
|
|
|
<Button onClick={() => joinWithAlias()}>{`Try joining ${alias}`}</Button>
|
|
|
|
)}
|
|
|
|
{status.isJoining && (
|
|
|
|
<>
|
|
|
|
<Spinner size="small" />
|
|
|
|
<Text>{`Joining ${alias}...`}</Text>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
{status.roomId !== null && (
|
|
|
|
<Button onClick={() => { onRequestClose(); selectRoom(status.roomId); }}>Open</Button>
|
|
|
|
)}
|
|
|
|
{status.error !== null && <Text variant="b2"><span style={{ color: 'var(--bg-danger)' }}>{status.error}</span></Text>}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
TryJoinWithAlias.propTypes = {
|
|
|
|
alias: PropTypes.string.isRequired,
|
|
|
|
onRequestClose: PropTypes.func.isRequired,
|
|
|
|
};
|
|
|
|
|
2021-08-31 16:13:31 +03:00
|
|
|
function PublicRooms({ isOpen, searchTerm, onRequestClose }) {
|
2021-07-28 16:15:52 +03:00
|
|
|
const [isSearching, updateIsSearching] = useState(false);
|
|
|
|
const [isViewMore, updateIsViewMore] = useState(false);
|
2021-08-31 16:13:31 +03:00
|
|
|
const [publicRooms, updatePublicRooms] = useState([]);
|
2021-07-28 16:15:52 +03:00
|
|
|
const [nextBatch, updateNextBatch] = useState(undefined);
|
|
|
|
const [searchQuery, updateSearchQuery] = useState({});
|
2021-08-31 16:13:31 +03:00
|
|
|
const [joiningRooms, updateJoiningRooms] = useState(new Set());
|
2021-07-28 16:15:52 +03:00
|
|
|
|
2021-08-31 16:13:31 +03:00
|
|
|
const roomNameRef = useRef(null);
|
2021-07-28 16:15:52 +03:00
|
|
|
const hsRef = useRef(null);
|
|
|
|
const userId = initMatrix.matrixClient.getUserId();
|
|
|
|
|
2021-08-31 16:13:31 +03:00
|
|
|
async function searchRooms(viewMore) {
|
|
|
|
let inputRoomName = roomNameRef?.current?.value || searchTerm;
|
2021-08-08 11:58:22 +03:00
|
|
|
let isInputAlias = false;
|
2021-08-31 16:13:31 +03:00
|
|
|
if (typeof inputRoomName === 'string') {
|
|
|
|
isInputAlias = inputRoomName[0] === '#' && inputRoomName.indexOf(':') > 1;
|
2021-08-08 11:58:22 +03:00
|
|
|
}
|
2021-08-31 16:13:31 +03:00
|
|
|
const hsFromAlias = (isInputAlias) ? inputRoomName.slice(inputRoomName.indexOf(':') + 1) : null;
|
2021-08-08 11:58:22 +03:00
|
|
|
let inputHs = hsFromAlias || hsRef?.current?.value;
|
2021-07-28 16:15:52 +03:00
|
|
|
|
|
|
|
if (typeof inputHs !== 'string') inputHs = userId.slice(userId.indexOf(':') + 1);
|
2021-08-31 16:13:31 +03:00
|
|
|
if (typeof inputRoomName !== 'string') inputRoomName = '';
|
2021-07-28 16:15:52 +03:00
|
|
|
|
|
|
|
if (isSearching) return;
|
|
|
|
if (viewMore !== true
|
2021-08-31 16:13:31 +03:00
|
|
|
&& inputRoomName === searchQuery.name
|
2021-07-28 16:15:52 +03:00
|
|
|
&& inputHs === searchQuery.homeserver
|
|
|
|
) return;
|
|
|
|
|
|
|
|
updateSearchQuery({
|
2021-08-31 16:13:31 +03:00
|
|
|
name: inputRoomName,
|
2021-07-28 16:15:52 +03:00
|
|
|
homeserver: inputHs,
|
|
|
|
});
|
|
|
|
if (isViewMore !== viewMore) updateIsViewMore(viewMore);
|
|
|
|
updateIsSearching(true);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await initMatrix.matrixClient.publicRooms({
|
|
|
|
server: inputHs,
|
|
|
|
limit: SEARCH_LIMIT,
|
|
|
|
since: viewMore ? nextBatch : undefined,
|
|
|
|
include_all_networks: true,
|
|
|
|
filter: {
|
2021-08-31 16:13:31 +03:00
|
|
|
generic_search_term: inputRoomName,
|
2021-07-28 16:15:52 +03:00
|
|
|
},
|
|
|
|
});
|
|
|
|
|
2021-08-31 16:13:31 +03:00
|
|
|
const totalRooms = viewMore ? publicRooms.concat(result.chunk) : result.chunk;
|
|
|
|
updatePublicRooms(totalRooms);
|
2021-07-28 16:15:52 +03:00
|
|
|
updateNextBatch(result.next_batch);
|
|
|
|
updateIsSearching(false);
|
|
|
|
updateIsViewMore(false);
|
2021-10-03 07:23:54 +03:00
|
|
|
if (totalRooms.length === 0) {
|
2021-08-08 11:58:22 +03:00
|
|
|
updateSearchQuery({
|
2021-10-03 07:23:54 +03:00
|
|
|
error: inputRoomName === ''
|
|
|
|
? `No public rooms on ${inputHs}`
|
|
|
|
: `No result found for "${inputRoomName}" on ${inputHs}`,
|
2021-08-31 16:13:31 +03:00
|
|
|
alias: isInputAlias ? inputRoomName : null,
|
2021-08-08 11:58:22 +03:00
|
|
|
});
|
|
|
|
}
|
2021-07-28 16:15:52 +03:00
|
|
|
} catch (e) {
|
2021-08-31 16:13:31 +03:00
|
|
|
updatePublicRooms([]);
|
2021-10-03 07:23:54 +03:00
|
|
|
let err = 'Something went wrong!';
|
|
|
|
if (e?.httpStatus >= 400 && e?.httpStatus < 500) {
|
|
|
|
err = e.message;
|
|
|
|
}
|
2021-08-11 14:18:39 +03:00
|
|
|
updateSearchQuery({
|
2021-10-03 07:23:54 +03:00
|
|
|
error: err,
|
2021-08-31 16:13:31 +03:00
|
|
|
alias: isInputAlias ? inputRoomName : null,
|
2021-08-11 14:18:39 +03:00
|
|
|
});
|
2021-07-28 16:15:52 +03:00
|
|
|
updateIsSearching(false);
|
|
|
|
updateNextBatch(undefined);
|
|
|
|
updateIsViewMore(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
2021-08-31 16:13:31 +03:00
|
|
|
if (isOpen) searchRooms();
|
2021-07-28 16:15:52 +03:00
|
|
|
}, [isOpen]);
|
|
|
|
|
|
|
|
function handleOnRoomAdded(roomId) {
|
2021-08-31 16:13:31 +03:00
|
|
|
if (joiningRooms.has(roomId)) {
|
|
|
|
joiningRooms.delete(roomId);
|
|
|
|
updateJoiningRooms(new Set(Array.from(joiningRooms)));
|
2021-07-28 16:15:52 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
useEffect(() => {
|
|
|
|
initMatrix.roomList.on(cons.events.roomList.ROOM_JOINED, handleOnRoomAdded);
|
|
|
|
return () => {
|
|
|
|
initMatrix.roomList.removeListener(cons.events.roomList.ROOM_JOINED, handleOnRoomAdded);
|
|
|
|
};
|
2021-08-31 16:13:31 +03:00
|
|
|
}, [joiningRooms]);
|
2021-07-28 16:15:52 +03:00
|
|
|
|
2021-08-31 16:13:31 +03:00
|
|
|
function handleViewRoom(roomId) {
|
2021-07-28 16:15:52 +03:00
|
|
|
selectRoom(roomId);
|
|
|
|
onRequestClose();
|
|
|
|
}
|
|
|
|
|
2021-08-31 16:13:31 +03:00
|
|
|
function joinRoom(roomIdOrAlias) {
|
|
|
|
joiningRooms.add(roomIdOrAlias);
|
|
|
|
updateJoiningRooms(new Set(Array.from(joiningRooms)));
|
2021-08-15 19:55:07 +03:00
|
|
|
roomActions.join(roomIdOrAlias, false);
|
2021-07-28 16:15:52 +03:00
|
|
|
}
|
|
|
|
|
2021-08-31 16:13:31 +03:00
|
|
|
function renderRoomList(rooms) {
|
|
|
|
return rooms.map((room) => {
|
|
|
|
const alias = typeof room.canonical_alias === 'string' ? room.canonical_alias : room.room_id;
|
|
|
|
const name = typeof room.name === 'string' ? room.name : alias;
|
|
|
|
const isJoined = initMatrix.roomList.rooms.has(room.room_id);
|
2021-07-28 16:15:52 +03:00
|
|
|
return (
|
2021-08-31 16:13:31 +03:00
|
|
|
<RoomTile
|
|
|
|
key={room.room_id}
|
|
|
|
avatarSrc={typeof room.avatar_url === 'string' ? initMatrix.matrixClient.mxcUrlToHttp(room.avatar_url, 42, 42, 'crop') : null}
|
2021-07-28 16:15:52 +03:00
|
|
|
name={name}
|
|
|
|
id={alias}
|
2021-08-31 16:13:31 +03:00
|
|
|
memberCount={room.num_joined_members}
|
|
|
|
desc={typeof room.topic === 'string' ? room.topic : null}
|
2021-07-28 16:15:52 +03:00
|
|
|
options={(
|
|
|
|
<>
|
2021-08-31 16:13:31 +03:00
|
|
|
{isJoined && <Button onClick={() => handleViewRoom(room.room_id)}>Open</Button>}
|
|
|
|
{!isJoined && (joiningRooms.has(room.room_id) ? <Spinner size="small" /> : <Button onClick={() => joinRoom(room.aliases?.[0] || room.room_id)} variant="primary">Join</Button>)}
|
2021-07-28 16:15:52 +03:00
|
|
|
</>
|
|
|
|
)}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<PopupWindow
|
|
|
|
isOpen={isOpen}
|
2021-08-31 16:13:31 +03:00
|
|
|
title="Public rooms"
|
2021-07-28 16:15:52 +03:00
|
|
|
contentOptions={<IconButton src={CrossIC} onClick={onRequestClose} tooltip="Close" />}
|
|
|
|
onRequestClose={onRequestClose}
|
|
|
|
>
|
2021-08-31 16:13:31 +03:00
|
|
|
<div className="public-rooms">
|
|
|
|
<form className="public-rooms__form" onSubmit={(e) => { e.preventDefault(); searchRooms(); }}>
|
|
|
|
<div className="public-rooms__input-wrapper">
|
|
|
|
<Input value={searchTerm} forwardRef={roomNameRef} label="Room name or alias" />
|
2021-07-28 16:15:52 +03:00
|
|
|
<Input forwardRef={hsRef} value={userId.slice(userId.indexOf(':') + 1)} label="Homeserver" required />
|
|
|
|
</div>
|
|
|
|
<Button disabled={isSearching} iconSrc={HashSearchIC} variant="primary" type="submit">Search</Button>
|
|
|
|
</form>
|
2021-08-31 16:13:31 +03:00
|
|
|
<div className="public-rooms__search-status">
|
2021-07-28 16:15:52 +03:00
|
|
|
{
|
|
|
|
typeof searchQuery.name !== 'undefined' && isSearching && (
|
|
|
|
searchQuery.name === ''
|
|
|
|
? (
|
|
|
|
<div className="flex--center">
|
|
|
|
<Spinner size="small" />
|
2021-08-31 16:13:31 +03:00
|
|
|
<Text variant="b2">{`Loading public rooms from ${searchQuery.homeserver}...`}</Text>
|
2021-07-28 16:15:52 +03:00
|
|
|
</div>
|
|
|
|
)
|
|
|
|
: (
|
|
|
|
<div className="flex--center">
|
|
|
|
<Spinner size="small" />
|
|
|
|
<Text variant="b2">{`Searching for "${searchQuery.name}" on ${searchQuery.homeserver}...`}</Text>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
{
|
2021-10-03 07:23:54 +03:00
|
|
|
typeof searchQuery.name !== 'undefined' && !isSearching && (
|
2021-07-28 16:15:52 +03:00
|
|
|
searchQuery.name === ''
|
2021-08-31 16:13:31 +03:00
|
|
|
? <Text variant="b2">{`Public rooms on ${searchQuery.homeserver}.`}</Text>
|
2021-07-28 16:15:52 +03:00
|
|
|
: <Text variant="b2">{`Search result for "${searchQuery.name}" on ${searchQuery.homeserver}.`}</Text>
|
|
|
|
)
|
|
|
|
}
|
2021-08-08 11:58:22 +03:00
|
|
|
{ searchQuery.error && (
|
|
|
|
<>
|
2021-08-31 16:13:31 +03:00
|
|
|
<Text className="public-rooms__search-error" variant="b2">{searchQuery.error}</Text>
|
2021-08-11 14:18:39 +03:00
|
|
|
{typeof searchQuery.alias === 'string' && (
|
2021-08-08 11:58:22 +03:00
|
|
|
<TryJoinWithAlias onRequestClose={onRequestClose} alias={searchQuery.alias} />
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
)}
|
2021-07-28 16:15:52 +03:00
|
|
|
</div>
|
2021-08-31 16:13:31 +03:00
|
|
|
{ publicRooms.length !== 0 && (
|
|
|
|
<div className="public-rooms__content">
|
|
|
|
{ renderRoomList(publicRooms) }
|
2021-07-28 16:15:52 +03:00
|
|
|
</div>
|
|
|
|
)}
|
2021-08-31 16:13:31 +03:00
|
|
|
{ publicRooms.length !== 0 && publicRooms.length % SEARCH_LIMIT === 0 && (
|
|
|
|
<div className="public-rooms__view-more">
|
2021-07-28 16:15:52 +03:00
|
|
|
{ isViewMore !== true && (
|
2021-08-31 16:13:31 +03:00
|
|
|
<Button onClick={() => searchRooms(true)}>View more</Button>
|
2021-07-28 16:15:52 +03:00
|
|
|
)}
|
|
|
|
{ isViewMore && <Spinner /> }
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</PopupWindow>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-08-31 16:13:31 +03:00
|
|
|
PublicRooms.defaultProps = {
|
2021-08-08 12:15:21 +03:00
|
|
|
searchTerm: undefined,
|
|
|
|
};
|
|
|
|
|
2021-08-31 16:13:31 +03:00
|
|
|
PublicRooms.propTypes = {
|
2021-07-28 16:15:52 +03:00
|
|
|
isOpen: PropTypes.bool.isRequired,
|
2021-08-08 12:15:21 +03:00
|
|
|
searchTerm: PropTypes.string,
|
2021-07-28 16:15:52 +03:00
|
|
|
onRequestClose: PropTypes.func.isRequired,
|
|
|
|
};
|
|
|
|
|
2021-08-31 16:13:31 +03:00
|
|
|
export default PublicRooms;
|