mirror of
https://github.com/revoltchat/revite.git
synced 2024-12-24 22:52:09 -05:00
feat(mobx): migrate unreads to revolt.js
This commit is contained in:
parent
136238f62e
commit
6e1bcab92b
9 changed files with 117 additions and 147 deletions
|
@ -143,7 +143,7 @@
|
|||
"react-virtuoso": "^1.10.4",
|
||||
"redux": "^4.1.0",
|
||||
"revolt-api": "0.5.3-alpha.10",
|
||||
"revolt.js": "5.1.0-alpha.15",
|
||||
"revolt.js": "5.2.0-patch.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"sass": "^1.35.1",
|
||||
"shade-blend-color": "^1.0.0",
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
Notepad,
|
||||
} from "@styled-icons/boxicons-solid";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Link, Redirect, useLocation, useParams } from "react-router-dom";
|
||||
import { Link, useLocation, useParams } from "react-router-dom";
|
||||
import { RelationshipStatus } from "revolt-api/types/Users";
|
||||
|
||||
import { Text } from "preact-i18n";
|
||||
|
@ -16,48 +16,37 @@ import PaintCounter from "../../../lib/PaintCounter";
|
|||
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
|
||||
|
||||
import { useApplicationState } from "../../../mobx/State";
|
||||
import { dispatch } from "../../../redux";
|
||||
import { connectState } from "../../../redux/connector";
|
||||
import { Unreads } from "../../../redux/reducers/unreads";
|
||||
|
||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
import Category from "../../ui/Category";
|
||||
import placeholderSVG from "../items/placeholder.svg";
|
||||
import { mapChannelWithUnread, useUnreads } from "./common";
|
||||
|
||||
import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase";
|
||||
import ButtonItem, { ChannelButton } from "../items/ButtonItem";
|
||||
import ConnectionStatus from "../items/ConnectionStatus";
|
||||
|
||||
type Props = {
|
||||
unreads: Unreads;
|
||||
};
|
||||
|
||||
const HomeSidebar = observer((props: Props) => {
|
||||
export default observer(() => {
|
||||
const { pathname } = useLocation();
|
||||
const client = useContext(AppContext);
|
||||
const layout = useApplicationState().layout;
|
||||
const { channel } = useParams<{ channel: string }>();
|
||||
const state = useApplicationState();
|
||||
const { channel: currentChannel } = useParams<{ channel: string }>();
|
||||
const { openScreen } = useIntermediate();
|
||||
|
||||
const channels = [...client.channels.values()]
|
||||
.filter(
|
||||
(x) =>
|
||||
x.channel_type === "DirectMessage" ||
|
||||
x.channel_type === "Group",
|
||||
)
|
||||
.map((x) => mapChannelWithUnread(x, props.unreads));
|
||||
const channels = [...client.channels.values()].filter(
|
||||
(x) => x.channel_type === "DirectMessage" || x.channel_type === "Group",
|
||||
);
|
||||
|
||||
const obj = client.channels.get(channel);
|
||||
if (channel && !obj) return <Redirect to="/" />;
|
||||
if (obj) useUnreads({ ...props, channel: obj });
|
||||
const obj = client.channels.get(currentChannel);
|
||||
|
||||
// ! FIXME: move this globally
|
||||
// Track what page the user was last on (in home page).
|
||||
useEffect(() => layout.setLastHomePath(pathname), [pathname]);
|
||||
useEffect(() => state.layout.setLastHomePath(pathname), [pathname]);
|
||||
|
||||
channels.sort((b, a) => a.timestamp.localeCompare(b.timestamp));
|
||||
channels.sort((b, a) =>
|
||||
a.last_message_id_or_past.localeCompare(b.last_message_id_or_past),
|
||||
);
|
||||
|
||||
return (
|
||||
<GenericSidebarBase mobilePadding>
|
||||
|
@ -127,31 +116,37 @@ const HomeSidebar = observer((props: Props) => {
|
|||
{channels.length === 0 && (
|
||||
<img src={placeholderSVG} loading="eager" />
|
||||
)}
|
||||
{channels.map((x) => {
|
||||
{channels.map((channel) => {
|
||||
let user;
|
||||
if (x.channel.channel_type === "DirectMessage") {
|
||||
if (!x.channel.active) return null;
|
||||
user = x.channel.recipient;
|
||||
if (channel.channel_type === "DirectMessage") {
|
||||
if (!channel.active) return null;
|
||||
user = channel.recipient;
|
||||
|
||||
if (!user) {
|
||||
console.warn(
|
||||
`Skipped DM ${x.channel._id} because user was missing.`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
if (!user) return null;
|
||||
}
|
||||
|
||||
const isUnread = channel.isUnread(state.notifications);
|
||||
const mentionCount = channel.getMentions(
|
||||
state.notifications,
|
||||
).length;
|
||||
|
||||
return (
|
||||
<ConditionalLink
|
||||
key={x.channel._id}
|
||||
active={x.channel._id === channel}
|
||||
to={`/channel/${x.channel._id}`}>
|
||||
key={channel._id}
|
||||
active={channel._id === currentChannel}
|
||||
to={`/channel/${channel._id}`}>
|
||||
<ChannelButton
|
||||
user={user}
|
||||
channel={x.channel}
|
||||
alert={x.unread}
|
||||
alertCount={x.alertCount}
|
||||
active={x.channel._id === channel}
|
||||
channel={channel}
|
||||
alert={
|
||||
mentionCount > 0
|
||||
? "mention"
|
||||
: isUnread
|
||||
? "unread"
|
||||
: undefined
|
||||
}
|
||||
alertCount={mentionCount}
|
||||
active={channel._id === currentChannel}
|
||||
/>
|
||||
</ConditionalLink>
|
||||
);
|
||||
|
@ -161,13 +156,3 @@ const HomeSidebar = observer((props: Props) => {
|
|||
</GenericSidebarBase>
|
||||
);
|
||||
});
|
||||
|
||||
export default connectState(
|
||||
HomeSidebar,
|
||||
(state) => {
|
||||
return {
|
||||
unreads: state.unreads,
|
||||
};
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
|
|
@ -13,8 +13,6 @@ import PaintCounter from "../../../lib/PaintCounter";
|
|||
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
|
||||
|
||||
import { useApplicationState } from "../../../mobx/State";
|
||||
import { connectState } from "../../../redux/connector";
|
||||
import { Unreads } from "../../../redux/reducers/unreads";
|
||||
|
||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
||||
|
@ -25,7 +23,6 @@ import UserHover from "../../common/user/UserHover";
|
|||
import UserIcon from "../../common/user/UserIcon";
|
||||
import IconButton from "../../ui/IconButton";
|
||||
import LineDivider from "../../ui/LineDivider";
|
||||
import { mapChannelWithUnread } from "./common";
|
||||
|
||||
import { Children } from "../../../types/Preact";
|
||||
|
||||
|
@ -193,47 +190,14 @@ function Swoosh() {
|
|||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
unreads: Unreads;
|
||||
}
|
||||
|
||||
export const ServerListSidebar = observer(({ unreads }: Props) => {
|
||||
export default observer(() => {
|
||||
const client = useClient();
|
||||
const state = useApplicationState();
|
||||
|
||||
const { server: server_id } = useParams<{ server?: string }>();
|
||||
const server = server_id ? client.servers.get(server_id) : undefined;
|
||||
const activeServers = [...client.servers.values()];
|
||||
const channels = [...client.channels.values()].map((x) =>
|
||||
mapChannelWithUnread(x, unreads),
|
||||
);
|
||||
|
||||
const unreadChannels = channels
|
||||
.filter((x) => x.unread)
|
||||
.filter((x) => !state.notifications.isMuted(x.channel))
|
||||
.map((x) => x.channel?._id);
|
||||
|
||||
const servers = activeServers.map((server) => {
|
||||
let alertCount = 0;
|
||||
for (const id of server.channel_ids) {
|
||||
const channel = channels.find((x) => x.channel?._id === id);
|
||||
if (channel?.alertCount) {
|
||||
alertCount += channel.alertCount;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
server,
|
||||
unread: (typeof server.channel_ids.find((x) =>
|
||||
unreadChannels.includes(x),
|
||||
) !== "undefined"
|
||||
? alertCount > 0
|
||||
? "mention"
|
||||
: "unread"
|
||||
: undefined) as "mention" | "unread" | undefined,
|
||||
alertCount,
|
||||
};
|
||||
});
|
||||
const servers = [...client.servers.values()];
|
||||
const channels = [...client.channels.values()];
|
||||
|
||||
const history = useHistory();
|
||||
const path = useLocation().pathname;
|
||||
|
@ -241,16 +205,16 @@ export const ServerListSidebar = observer(({ unreads }: Props) => {
|
|||
|
||||
let homeUnread: "mention" | "unread" | undefined;
|
||||
let alertCount = 0;
|
||||
for (const x of channels) {
|
||||
if (x.channel?.channel_type === "Group" && x.unread) {
|
||||
for (const channel of channels) {
|
||||
if (channel?.channel_type === "Group" && channel.unread) {
|
||||
homeUnread = "unread";
|
||||
alertCount += x.alertCount ?? 0;
|
||||
alertCount += channel.mentions.length;
|
||||
}
|
||||
|
||||
if (
|
||||
x.channel?.channel_type === "DirectMessage" &&
|
||||
x.channel.active &&
|
||||
x.unread
|
||||
channel.channel_type === "DirectMessage" &&
|
||||
channel.active &&
|
||||
channel.unread
|
||||
) {
|
||||
alertCount++;
|
||||
}
|
||||
|
@ -294,32 +258,40 @@ export const ServerListSidebar = observer(({ unreads }: Props) => {
|
|||
</ServerEntry>
|
||||
</ConditionalLink>
|
||||
<LineDivider />
|
||||
{servers.map((entry) => {
|
||||
const active = entry.server._id === server?._id;
|
||||
{servers.map((server) => {
|
||||
const active = server._id === server_id;
|
||||
|
||||
const isUnread = server.isUnread(state.notifications);
|
||||
const mentionCount = server.getMentions(
|
||||
state.notifications,
|
||||
).length;
|
||||
|
||||
return (
|
||||
<ConditionalLink
|
||||
key={entry.server._id}
|
||||
key={server._id}
|
||||
active={active}
|
||||
to={state.layout.getServerPath(entry.server._id)}>
|
||||
to={state.layout.getServerPath(server._id)}>
|
||||
<ServerEntry
|
||||
active={active}
|
||||
onContextMenu={attachContextMenu("Menu", {
|
||||
server: entry.server._id,
|
||||
unread: entry.unread,
|
||||
server: server._id,
|
||||
unread: isUnread,
|
||||
})}>
|
||||
<Swoosh />
|
||||
<Tooltip
|
||||
content={entry.server.name}
|
||||
content={server.name}
|
||||
placement="right">
|
||||
<Icon
|
||||
size={42}
|
||||
unread={entry.unread}
|
||||
count={entry.alertCount}>
|
||||
<ServerIcon
|
||||
size={32}
|
||||
target={entry.server}
|
||||
/>
|
||||
unread={
|
||||
mentionCount > 0
|
||||
? "mention"
|
||||
: isUnread
|
||||
? "unread"
|
||||
: undefined
|
||||
}
|
||||
count={mentionCount}>
|
||||
<ServerIcon size={32} target={server} />
|
||||
</Icon>
|
||||
</Tooltip>
|
||||
</ServerEntry>
|
||||
|
@ -353,9 +325,3 @@ export const ServerListSidebar = observer(({ unreads }: Props) => {
|
|||
</ServersBase>
|
||||
);
|
||||
});
|
||||
|
||||
export default connectState(ServerListSidebar, (state) => {
|
||||
return {
|
||||
unreads: state.unreads,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -11,25 +11,17 @@ import { internalEmit } from "../../../lib/eventEmitter";
|
|||
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
|
||||
|
||||
import { useApplicationState } from "../../../mobx/State";
|
||||
import { dispatch } from "../../../redux";
|
||||
import { connectState } from "../../../redux/connector";
|
||||
import { Notifications } from "../../../redux/reducers/notifications";
|
||||
import { Unreads } from "../../../redux/reducers/unreads";
|
||||
|
||||
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
import CollapsibleSection from "../../common/CollapsibleSection";
|
||||
import ServerHeader from "../../common/ServerHeader";
|
||||
import Category from "../../ui/Category";
|
||||
import { mapChannelWithUnread, useUnreads } from "./common";
|
||||
|
||||
import { ChannelButton } from "../items/ButtonItem";
|
||||
import ConnectionStatus from "../items/ConnectionStatus";
|
||||
|
||||
interface Props {
|
||||
unreads: Unreads;
|
||||
}
|
||||
|
||||
const ServerBase = styled.div`
|
||||
height: 100%;
|
||||
width: 240px;
|
||||
|
@ -56,7 +48,7 @@ const ServerList = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
const ServerSidebar = observer((props: Props) => {
|
||||
export default observer(() => {
|
||||
const client = useClient();
|
||||
const state = useApplicationState();
|
||||
const { server: server_id, channel: channel_id } =
|
||||
|
@ -76,9 +68,7 @@ const ServerSidebar = observer((props: Props) => {
|
|||
);
|
||||
if (channel_id && !channel) return <Redirect to={`/server/${server_id}`} />;
|
||||
|
||||
// Handle unreads; FIXME: should definitely not be here
|
||||
if (channel) useUnreads({ ...props, channel });
|
||||
|
||||
// ! FIXME: move this globally
|
||||
// Track which channel the user was last on.
|
||||
useEffect(() => {
|
||||
if (!channel_id) return;
|
||||
|
@ -95,6 +85,8 @@ const ServerSidebar = observer((props: Props) => {
|
|||
if (!entry) return;
|
||||
|
||||
const active = channel?._id === entry._id;
|
||||
const isUnread = entry.isUnread(state.notifications);
|
||||
const mentionCount = entry.getMentions(state.notifications);
|
||||
|
||||
return (
|
||||
<ConditionalLink
|
||||
|
@ -115,8 +107,13 @@ const ServerSidebar = observer((props: Props) => {
|
|||
<ChannelButton
|
||||
channel={entry}
|
||||
active={active}
|
||||
// ! FIXME: pull it out directly
|
||||
alert={mapChannelWithUnread(entry, props.unreads).unread}
|
||||
alert={
|
||||
mentionCount.length > 0
|
||||
? "mention"
|
||||
: isUnread
|
||||
? "unread"
|
||||
: undefined
|
||||
}
|
||||
compact
|
||||
muted={state.notifications.isMuted(entry)}
|
||||
/>
|
||||
|
@ -161,10 +158,3 @@ const ServerSidebar = observer((props: Props) => {
|
|||
</ServerBase>
|
||||
);
|
||||
});
|
||||
|
||||
export default connectState(ServerSidebar, (state) => {
|
||||
return {
|
||||
unreads: state.unreads,
|
||||
notifications: state.notifications,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { reaction } from "mobx";
|
||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||
|
||||
import { useLayoutEffect, useRef } from "preact/hooks";
|
||||
import { useLayoutEffect } from "preact/hooks";
|
||||
|
||||
import { dispatch } from "../../../redux";
|
||||
import { Unreads } from "../../../redux/reducers/unreads";
|
||||
|
|
|
@ -195,12 +195,17 @@ export default class NotificationOptions implements Store, Persistent<Data> {
|
|||
* @returns Whether this object is muted
|
||||
*/
|
||||
isMuted(target?: Channel | Server) {
|
||||
var value: NotificationState | undefined;
|
||||
if (target instanceof Channel) {
|
||||
return this.computeForChannel(target) === "muted";
|
||||
value = this.computeForChannel(target);
|
||||
} else if (target instanceof Server) {
|
||||
return this.computeForServer(target._id) === "muted";
|
||||
} else {
|
||||
return false;
|
||||
value = this.computeForServer(target._id);
|
||||
}
|
||||
|
||||
if (value === "muted") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ export default class ServerConfig
|
|||
*/
|
||||
createClient() {
|
||||
const client = new Client({
|
||||
unreads: true,
|
||||
autoReconnect: false,
|
||||
apiURL: import.meta.env.VITE_API_URL,
|
||||
debug: import.meta.env.DEV,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Hash } from "@styled-icons/boxicons-regular";
|
||||
import { Ghost } from "@styled-icons/boxicons-solid";
|
||||
import { reaction } from "mobx";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Channel as ChannelI } from "revolt.js/dist/maps/Channels";
|
||||
|
@ -10,7 +11,6 @@ import { useEffect, useState } from "preact/hooks";
|
|||
|
||||
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
|
||||
|
||||
import { useApplicationState } from "../../mobx/State";
|
||||
import { dispatch, getState } from "../../redux";
|
||||
|
||||
import { useClient } from "../../context/revoltjs/RevoltClient";
|
||||
|
@ -93,6 +93,23 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
|
|||
getState().sectionToggle[CHANNELS_SIDEBAR_KEY] ?? true,
|
||||
);
|
||||
|
||||
// Mark channel as read.
|
||||
useEffect(() => {
|
||||
const checkUnread = () =>
|
||||
channel.unread &&
|
||||
channel.client.unreads!.markRead(
|
||||
channel._id,
|
||||
channel.last_message_id!,
|
||||
true,
|
||||
);
|
||||
|
||||
checkUnread();
|
||||
return reaction(
|
||||
() => channel.last_message_id,
|
||||
() => checkUnread(),
|
||||
);
|
||||
}, [channel]);
|
||||
|
||||
return (
|
||||
<AgeGate
|
||||
type="channel"
|
||||
|
|
14
yarn.lock
14
yarn.lock
|
@ -3088,6 +3088,11 @@ lodash.defaultsdeep@^4.6.1:
|
|||
resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6"
|
||||
integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==
|
||||
|
||||
lodash.flatten@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
||||
|
||||
lodash.isequal@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||
|
@ -3765,16 +3770,17 @@ revolt-api@^0.5.3-alpha.9:
|
|||
resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-alpha.9.tgz#46e75b7d8f9c6702df39039b829dddbb7897f237"
|
||||
integrity sha512-L8K9uPV3ME8bLdtWm8L9iPQvFM0GghA+5LzmWFjd6Gbn56u22ZYub2lABi4iHrWgeA2X41dGSsuSBgHSlts9Og==
|
||||
|
||||
revolt.js@5.1.0-alpha.15:
|
||||
version "5.1.0-alpha.15"
|
||||
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.1.0-alpha.15.tgz#a2be1f29de93f1ec18f0e502ecb65ade55c0070d"
|
||||
integrity sha512-1gGcGDv1+J5NlmnX099XafKugCebACg9ke0NA754I4hLTNMMwkZyphyvYWWWkI394qn2mA3NG7WgEmrIoZUtgw==
|
||||
revolt.js@5.2.0-patch.0:
|
||||
version "5.2.0-patch.0"
|
||||
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.2.0-patch.0.tgz#af6afc402399e5394b50b2e7d1573ff490fd3906"
|
||||
integrity sha512-PnHKqRpEvrBFm1xtLA/lGG5FIsp5kW4eB8sYiejjQCA1DWi7Xg6MNvyOjjha6jKftPXF8roivfZWEnM7sY1bnA==
|
||||
dependencies:
|
||||
axios "^0.21.4"
|
||||
eventemitter3 "^4.0.7"
|
||||
exponential-backoff "^3.1.0"
|
||||
isomorphic-ws "^4.0.1"
|
||||
lodash.defaultsdeep "^4.6.1"
|
||||
lodash.flatten "^4.4.0"
|
||||
lodash.isequal "^4.5.0"
|
||||
mobx "^6.3.2"
|
||||
revolt-api "^0.5.3-alpha.9"
|
||||
|
|
Loading…
Reference in a new issue