initial commit
This commit is contained in:
199
src/app/organisms/public-channels/PublicChannels.jsx
Normal file
199
src/app/organisms/public-channels/PublicChannels.jsx
Normal file
@@ -0,0 +1,199 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './PublicChannels.scss';
|
||||
|
||||
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';
|
||||
import ChannelTile from '../../molecules/channel-tile/ChannelTile';
|
||||
|
||||
import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
|
||||
import HashSearchIC from '../../../../public/res/ic/outlined/hash-search.svg';
|
||||
|
||||
const SEARCH_LIMIT = 20;
|
||||
|
||||
function PublicChannels({ isOpen, onRequestClose }) {
|
||||
const [isSearching, updateIsSearching] = useState(false);
|
||||
const [isViewMore, updateIsViewMore] = useState(false);
|
||||
const [publicChannels, updatePublicChannels] = useState([]);
|
||||
const [nextBatch, updateNextBatch] = useState(undefined);
|
||||
const [searchQuery, updateSearchQuery] = useState({});
|
||||
const [joiningChannels, updateJoiningChannels] = useState(new Set());
|
||||
|
||||
const channelNameRef = useRef(null);
|
||||
const hsRef = useRef(null);
|
||||
const userId = initMatrix.matrixClient.getUserId();
|
||||
|
||||
async function searchChannels(viewMore) {
|
||||
let inputHs = hsRef?.current?.value;
|
||||
let inputChannelName = channelNameRef?.current?.value;
|
||||
|
||||
if (typeof inputHs !== 'string') inputHs = userId.slice(userId.indexOf(':') + 1);
|
||||
if (typeof inputChannelName !== 'string') inputChannelName = '';
|
||||
|
||||
if (isSearching) return;
|
||||
if (viewMore !== true
|
||||
&& inputChannelName === searchQuery.name
|
||||
&& inputHs === searchQuery.homeserver
|
||||
) return;
|
||||
|
||||
updateSearchQuery({
|
||||
name: inputChannelName,
|
||||
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: {
|
||||
generic_search_term: inputChannelName,
|
||||
},
|
||||
});
|
||||
|
||||
const totalChannels = viewMore ? publicChannels.concat(result.chunk) : result.chunk;
|
||||
updatePublicChannels(totalChannels);
|
||||
updateNextBatch(result.next_batch);
|
||||
updateIsSearching(false);
|
||||
updateIsViewMore(false);
|
||||
} catch (e) {
|
||||
updatePublicChannels([]);
|
||||
updateSearchQuery({ error: 'Something went wrong!' });
|
||||
updateIsSearching(false);
|
||||
updateNextBatch(undefined);
|
||||
updateIsViewMore(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) searchChannels();
|
||||
}, [isOpen]);
|
||||
|
||||
function handleOnRoomAdded(roomId) {
|
||||
if (joiningChannels.has(roomId)) {
|
||||
joiningChannels.delete(roomId);
|
||||
updateJoiningChannels(new Set(Array.from(joiningChannels)));
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
initMatrix.roomList.on(cons.events.roomList.ROOM_JOINED, handleOnRoomAdded);
|
||||
return () => {
|
||||
initMatrix.roomList.removeListener(cons.events.roomList.ROOM_JOINED, handleOnRoomAdded);
|
||||
};
|
||||
}, [joiningChannels]);
|
||||
|
||||
function handleViewChannel(roomId) {
|
||||
selectRoom(roomId);
|
||||
onRequestClose();
|
||||
}
|
||||
|
||||
function joinChannel(roomId) {
|
||||
joiningChannels.add(roomId);
|
||||
updateJoiningChannels(new Set(Array.from(joiningChannels)));
|
||||
roomActions.join(roomId, false);
|
||||
}
|
||||
|
||||
function renderChannelList(channels) {
|
||||
return channels.map((channel) => {
|
||||
const alias = typeof channel.canonical_alias === 'string' ? channel.canonical_alias : channel.room_id;
|
||||
const name = typeof channel.name === 'string' ? channel.name : alias;
|
||||
const isJoined = initMatrix.roomList.rooms.has(channel.room_id);
|
||||
return (
|
||||
<ChannelTile
|
||||
key={channel.room_id}
|
||||
avatarSrc={typeof channel.avatar_url === 'string' ? initMatrix.matrixClient.mxcUrlToHttp(channel.avatar_url, 42, 42, 'crop') : null}
|
||||
name={name}
|
||||
id={alias}
|
||||
memberCount={channel.num_joined_members}
|
||||
desc={typeof channel.topic === 'string' ? channel.topic : null}
|
||||
options={(
|
||||
<>
|
||||
{isJoined && <Button onClick={() => handleViewChannel(channel.room_id)}>Open</Button>}
|
||||
{!isJoined && (joiningChannels.has(channel.room_id) ? <Spinner size="small" /> : <Button onClick={() => joinChannel(channel.room_id)} variant="primary">Join</Button>)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<PopupWindow
|
||||
isOpen={isOpen}
|
||||
title="Public channels"
|
||||
contentOptions={<IconButton src={CrossIC} onClick={onRequestClose} tooltip="Close" />}
|
||||
onRequestClose={onRequestClose}
|
||||
>
|
||||
<div className="public-channels">
|
||||
<form className="public-channels__form" onSubmit={(e) => { e.preventDefault(); searchChannels(); }}>
|
||||
<div className="public-channels__input-wrapper">
|
||||
<Input forwardRef={channelNameRef} label="Channel name" />
|
||||
<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>
|
||||
<div className="public-channels__search-status">
|
||||
{
|
||||
typeof searchQuery.name !== 'undefined' && isSearching && (
|
||||
searchQuery.name === ''
|
||||
? (
|
||||
<div className="flex--center">
|
||||
<Spinner size="small" />
|
||||
<Text variant="b2">{`Loading public channels from ${searchQuery.homeserver}...`}</Text>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className="flex--center">
|
||||
<Spinner size="small" />
|
||||
<Text variant="b2">{`Searching for "${searchQuery.name}" on ${searchQuery.homeserver}...`}</Text>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
{
|
||||
typeof searchQuery.name !== 'undefined' && !isSearching && (
|
||||
searchQuery.name === ''
|
||||
? <Text variant="b2">{`Public channels on ${searchQuery.homeserver}.`}</Text>
|
||||
: <Text variant="b2">{`Search result for "${searchQuery.name}" on ${searchQuery.homeserver}.`}</Text>
|
||||
)
|
||||
}
|
||||
{
|
||||
searchQuery.error && <Text className="public-channels__search-error" variant="b2">{searchQuery.error}</Text>
|
||||
}
|
||||
</div>
|
||||
{ publicChannels.length !== 0 && (
|
||||
<div className="public-channels__content">
|
||||
{ renderChannelList(publicChannels) }
|
||||
</div>
|
||||
)}
|
||||
{ publicChannels.length !== 0 && publicChannels.length % SEARCH_LIMIT === 0 && (
|
||||
<div className="public-channels__view-more">
|
||||
{ isViewMore !== true && (
|
||||
<Button onClick={() => searchChannels(true)}>View more</Button>
|
||||
)}
|
||||
{ isViewMore && <Spinner /> }
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PopupWindow>
|
||||
);
|
||||
}
|
||||
|
||||
PublicChannels.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onRequestClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default PublicChannels;
|
||||
Reference in New Issue
Block a user