revite/src/components/navigation/right/MemberSidebar.tsx

383 lines
14 KiB
TypeScript
Raw Normal View History

2021-07-29 10:11:21 -04:00
import { observer } from "mobx-react-lite";
2021-07-05 06:23:23 -04:00
import { useParams } from "react-router";
import { Link } from "react-router-dom";
2021-07-05 06:23:23 -04:00
import { User } from "revolt.js";
2021-07-09 09:34:36 -04:00
import { Channels, Message, Servers, Users } from "revolt.js/dist/api/objects";
2021-07-05 06:23:23 -04:00
import { ClientboundNotification } from "revolt.js/dist/websocket/notifications";
import { Text } from "preact-i18n";
import { useContext, useEffect, useState } from "preact/hooks";
2021-07-29 10:11:21 -04:00
import { useData } from "../../../mobx/State";
import { getState } from "../../../redux";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
2021-07-05 06:23:23 -04:00
import {
2021-07-05 06:25:20 -04:00
AppContext,
ClientStatus,
StatusContext,
2021-07-05 06:23:23 -04:00
} from "../../../context/revoltjs/RevoltClient";
import {
2021-07-05 06:25:20 -04:00
HookContext,
useChannel,
useForceUpdate,
2021-07-05 06:23:23 -04:00
} from "../../../context/revoltjs/hooks";
import CollapsibleSection from "../../common/CollapsibleSection";
2021-07-21 13:27:05 -04:00
import Button from "../../ui/Button";
2021-07-05 06:23:23 -04:00
import Category from "../../ui/Category";
import InputBox from "../../ui/InputBox";
import Preloader from "../../ui/Preloader";
2021-07-05 06:23:23 -04:00
import placeholderSVG from "../items/placeholder.svg";
import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase";
import { UserButton } from "../items/ButtonItem";
import { ChannelDebugInfo } from "./ChannelDebugInfo";
interface Props {
2021-07-05 06:25:20 -04:00
ctx: HookContext;
}
export default function MemberSidebar(props: { channel?: Channels.Channel }) {
2021-07-05 06:25:20 -04:00
const ctx = useForceUpdate();
const { channel: cid } = useParams<{ channel: string }>();
const channel = props.channel ?? useChannel(cid, ctx);
2021-07-05 06:23:23 -04:00
2021-07-05 06:25:20 -04:00
switch (channel?.channel_type) {
case "Group":
return <GroupMemberSidebar channel={channel} ctx={ctx} />;
case "TextChannel":
return <ServerMemberSidebar channel={channel} ctx={ctx} />;
default:
return null;
}
}
2021-07-29 10:11:21 -04:00
export const GroupMemberSidebar = observer(
({ channel }: Props & { channel: Channels.GroupChannel }) => {
const { openScreen } = useIntermediate();
const store = useData();
const members = channel.recipients
?.map((member) => store.users.get(member)!)
.filter((x) => typeof x !== "undefined");
/*const voice = useContext(VoiceContext);
const voiceActive = voice.roomId === channel._id;
let voiceParticipants: User[] = [];
if (voiceActive) {
const idArray = Array.from(voice.participants.keys());
voiceParticipants = idArray
.map(x => users.find(y => y?._id === x))
.filter(x => typeof x !== "undefined") as User[];
members = members.filter(member => idArray.indexOf(member._id) === -1);
voiceParticipants.sort((a, b) => a.username.localeCompare(b.username));
}*/
2021-07-29 10:11:21 -04:00
members.sort((a, b) => {
// ! FIXME: should probably rewrite all this code
const l =
+(
(a.online &&
a.status?.presence !== Users.Presence.Invisible) ??
false
) | 0;
const r =
+(
(b.online &&
b.status?.presence !== Users.Presence.Invisible) ??
false
) | 0;
2021-07-29 10:11:21 -04:00
const n = r - l;
if (n !== 0) {
return n;
}
2021-07-09 09:34:36 -04:00
2021-07-29 10:11:21 -04:00
return a.username.localeCompare(b.username);
});
return (
<GenericSidebarBase>
<GenericSidebarList>
<ChannelDebugInfo id={channel._id} />
<Search channel={channel._id} />
{/*voiceActive && voiceParticipants.length !== 0 && (
<Fragment>
<Category
type="members"
text={
<span>
<Text id="app.main.categories.participants" />{" "}
{voiceParticipants.length}
</span>
}
/>
{voiceParticipants.map(
user =>
user && (
<LinkProfile user_id={user._id}>
<UserButton
key={user._id}
user={user}
context={channel}
/>
</LinkProfile>
)
)}
</Fragment>
)*/}
<CollapsibleSection
2021-07-29 10:11:21 -04:00
sticky
id="members"
defaultValue
summary={
2021-07-29 10:11:21 -04:00
<Category
variant="uniform"
text={
<span>
<Text id="app.main.categories.members" />{" "}
{channel.recipients.length}
</span>
}
/>
}>
2021-07-29 10:11:21 -04:00
{members.length === 0 && (
<img src={placeholderSVG} loading="eager" />
)}
2021-07-29 10:11:21 -04:00
{members.map(
(user) =>
user && (
<UserButton
key={user._id}
user={user}
context={channel}
onClick={() =>
openScreen({
id: "profile",
user_id: user._id,
})
}
/>
),
)}
</CollapsibleSection>
2021-07-29 10:11:21 -04:00
</GenericSidebarList>
</GenericSidebarBase>
);
},
);
export const ServerMemberSidebar = observer(
({ channel }: Props & { channel: Channels.TextChannel }) => {
const [members, setMembers] = useState<Servers.Member[] | undefined>(
undefined,
);
const store = useData();
const users = members
?.map((member) => store.users.get(member._id.user)!)
.filter((x) => typeof x !== "undefined");
const { openScreen } = useIntermediate();
const status = useContext(StatusContext);
const client = useContext(AppContext);
useEffect(() => {
if (
status === ClientStatus.ONLINE &&
typeof members === "undefined"
) {
client.members
.fetchMembers(channel.server)
.then((members) => setMembers(members));
}
}, [status]);
// ! FIXME: temporary code
useEffect(() => {
function onPacket(packet: ClientboundNotification) {
if (!members) return;
if (packet.type === "ServerMemberJoin") {
if (packet.id !== channel.server) return;
setMembers([
...members,
{ _id: { server: packet.id, user: packet.user } },
]);
} else if (packet.type === "ServerMemberLeave") {
if (packet.id !== channel.server) return;
setMembers(
members.filter(
(x) =>
!(
x._id.user === packet.user &&
x._id.server === packet.id
),
),
);
}
}
client.addListener("packet", onPacket);
return () => client.removeListener("packet", onPacket);
}, [members]);
// copy paste from above
users?.sort((a, b) => {
// ! FIXME: should probably rewrite all this code
const l =
+(
(a.online &&
a.status?.presence !== Users.Presence.Invisible) ??
false
) | 0;
const r =
+(
(b.online &&
b.status?.presence !== Users.Presence.Invisible) ??
false
) | 0;
const n = r - l;
if (n !== 0) {
return n;
}
return a.username.localeCompare(b.username);
});
return (
<GenericSidebarBase>
<GenericSidebarList>
<ChannelDebugInfo id={channel._id} />
<Search channel={channel._id} />
<div>{!members && <Preloader type="ring" />}</div>
{members && (
<CollapsibleSection
//sticky //will re-add later, need to fix css
id="members"
defaultValue
summary={
<span>
<Text id="app.main.categories.members" /> {" "}
{users?.length ?? 0}
</span>
}>
{(users?.length ?? 0) === 0 && (
<img src={placeholderSVG} loading="eager" />
)}
{users?.map(
(user) =>
user && (
<UserButton
key={user._id}
user={user}
context={channel}
onClick={() =>
openScreen({
id: "profile",
user_id: user._id,
})
}
/>
),
)}
</CollapsibleSection>
)}
</GenericSidebarList>
</GenericSidebarBase>
);
},
);
2021-07-09 09:34:36 -04:00
function Search({ channel }: { channel: string }) {
if (!getState().experiments.enabled?.includes("search")) return null;
2021-07-09 09:34:36 -04:00
const client = useContext(AppContext);
2021-07-21 13:27:05 -04:00
type Sort = "Relevance" | "Latest" | "Oldest";
const [sort, setSort] = useState<Sort>("Relevance");
const [query, setV] = useState("");
const [results, setResults] = useState<Message[]>([]);
2021-07-09 09:34:36 -04:00
async function search() {
const data = await client.channels.searchWithUsers(
channel,
2021-07-21 13:27:05 -04:00
{ query, sort },
true,
);
2021-07-09 09:34:36 -04:00
setResults(data.messages);
}
return (
<CollapsibleSection
sticky
id="search"
defaultValue={false}
2021-07-21 13:27:05 -04:00
summary={
<>
<Text id="app.main.channel.search.title" /> (BETA)
</>
}>
<div style={{ display: "flex" }}>
{["Relevance", "Latest", "Oldest"].map((key) => (
<Button
style={{ flex: 1, minWidth: 0 }}
compact
error={sort === key}
onClick={() => setSort(key as Sort)}>
<Text
id={`app.main.channel.search.sort.${key.toLowerCase()}`}
/>
</Button>
))}
</div>
<InputBox
style={{ width: "100%" }}
onKeyDown={(e) => e.key === "Enter" && search()}
value={query}
onChange={(e) => setV(e.currentTarget.value)}
/>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "4px",
marginTop: "8px",
}}>
{results.map((message) => {
let href = "";
const channel = client.channels.get(message.channel);
if (channel?.channel_type === "TextChannel") {
2021-07-09 09:34:36 -04:00
href += `/server/${channel.server}`;
}
href += `/channel/${message.channel}/${message._id}`;
return (
<Link to={href}>
<div
style={{
margin: "2px",
padding: "6px",
background: "var(--primary-background)",
}}>
<b>
@
{client.users.get(message.author)?.username}
</b>
<br />
{message.content}
</div>
</Link>
);
})}
2021-07-09 09:34:36 -04:00
</div>
</CollapsibleSection>
);
2021-07-09 09:34:36 -04:00
}