feat(mobx): server notification options + data store

This commit is contained in:
Paul
2021-12-11 23:34:46 +00:00
parent 763ac4eb98
commit 6aa542d2a9
10 changed files with 237 additions and 102 deletions

View File

@@ -48,6 +48,7 @@ import {
StatusContext,
} from "../context/revoltjs/RevoltClient";
import { takeError } from "../context/revoltjs/util";
import CMNotifications from "./contextmenu/CMNotifications";
import Tooltip from "../components/common/Tooltip";
import UserStatus from "../components/common/user/UserStatus";
@@ -117,7 +118,11 @@ type Action =
| { action: "leave_server"; target: Server }
| { action: "delete_server"; target: Server }
| { action: "edit_identity"; target: Server }
| { action: "open_notification_options"; channel: Channel }
| {
action: "open_notification_options";
channel?: Channel;
server?: Server;
}
| { action: "open_settings" }
| { action: "open_channel_settings"; id: string }
| { action: "open_server_settings"; id: string }
@@ -128,13 +133,9 @@ type Action =
state?: NotificationState;
};
type Props = {
notifications: Notifications;
};
// ! FIXME: I dare someone to re-write this
// Tip: This should just be split into separate context menus per logical area.
function ContextMenus(props: Props) {
export default function ContextMenus() {
const { openScreen, writeClipboard } = useIntermediate();
const client = useContext(AppContext);
const userId = client.user!._id;
@@ -427,6 +428,7 @@ function ContextMenus(props: Props) {
case "open_notification_options": {
openContextMenu("NotificationOptions", {
channel: data.channel,
server: data.server,
});
break;
}
@@ -921,6 +923,16 @@ function ContextMenus(props: Props) {
}
if (sid && server) {
generateAction(
{
action: "open_notification_options",
server,
},
undefined,
undefined,
<ChevronRight size={24} />,
);
if (server.channels[0] !== undefined)
generateAction(
{
@@ -1085,76 +1097,7 @@ function ContextMenus(props: Props) {
);
}}
</ContextMenuWithData>
<ContextMenuWithData
id="NotificationOptions"
onClose={contextClick}>
{({ channel }: { channel: Channel }) => {
const state = props.notifications[channel._id];
const actual = getNotificationState(
props.notifications,
channel,
);
const elements: Children[] = [
<MenuItem
key="notif"
data={{
action: "set_notification_state",
key: channel._id,
}}>
<Text
id={`app.main.channel.notifications.default`}
/>
<div className="tip">
{state !== undefined && <Square size={20} />}
{state === undefined && (
<CheckSquare size={20} />
)}
</div>
</MenuItem>,
];
function generate(key: string, icon: Children) {
elements.push(
<MenuItem
key={key}
data={{
action: "set_notification_state",
key: channel._id,
state: key,
}}>
{icon}
<Text
id={`app.main.channel.notifications.${key}`}
/>
{state === undefined && actual === key && (
<div className="tip">
<LeftArrowAlt size={20} />
</div>
)}
{state === key && (
<div className="tip">
<Check size={20} />
</div>
)}
</MenuItem>,
);
}
generate("all", <Bell size={24} />);
generate("mention", <At size={24} />);
generate("muted", <BellOff size={24} />);
generate("none", <Block size={24} />);
return elements;
}}
</ContextMenuWithData>
<CMNotifications />
</>
);
}
export default connectState(ContextMenus, (state) => {
return {
notifications: state.notifications,
};
});

View File

@@ -0,0 +1,119 @@
import {
At,
Bell,
BellOff,
Check,
CheckSquare,
Block,
Square,
LeftArrowAlt,
} from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Server } from "revolt.js/dist/maps/Servers";
import { ContextMenuWithData, MenuItem } from "preact-context-menu";
import { Text } from "preact-i18n";
import { useApplicationState } from "../../mobx/State";
import { NotificationState } from "../../mobx/stores/NotificationOptions";
import LineDivider from "../../components/ui/LineDivider";
import { Children } from "../../types/Preact";
interface Action {
key: string;
type: "channel" | "server";
state?: NotificationState;
}
/**
* Provides a context menu for controlling notification options.
*/
export default observer(() => {
const notifications = useApplicationState().notifications;
const contextClick = (data?: Action) =>
data &&
(data.type === "channel"
? notifications.setChannelState(data.key, data.state)
: notifications.setServerState(data.key, data.state));
return (
<ContextMenuWithData id="NotificationOptions" onClose={contextClick}>
{({ channel, server }: { channel?: Channel; server?: Server }) => {
// Find the computed and actual state values for channel / server.
const state = channel
? notifications.getChannelState(channel._id)
: notifications.computeForServer(server!._id);
const actual = channel
? notifications.computeForChannel(channel)
: undefined;
// If we're editing channel, show a default option too.
const elements: Children[] = channel
? [
<MenuItem
key="notif"
data={{
key: channel._id,
type: "channel",
}}>
<Text
id={`app.main.channel.notifications.default`}
/>
<div className="tip">
{state !== undefined && <Square size={20} />}
{state === undefined && (
<CheckSquare size={20} />
)}
</div>
</MenuItem>,
<LineDivider />,
]
: [];
/**
* Generate a new entry we can select.
* @param key Notification state
* @param icon Icon for this state
*/
function generate(key: string, icon: Children) {
elements.push(
<MenuItem
key={key}
data={{
key: channel ? channel._id : server!._id,
type: channel ? "channel" : "server",
state: key,
}}>
{icon}
<Text
id={`app.main.channel.notifications.${key}`}
/>
{state === undefined && actual === key && (
<div className="tip">
<LeftArrowAlt size={20} />
</div>
)}
{state === key && (
<div className="tip">
<Check size={20} />
</div>
)}
</MenuItem>,
);
}
generate("all", <Bell size={24} />);
generate("mention", <At size={24} />);
generate("none", <BellOff size={24} />);
generate("muted", <Block size={24} />);
return elements;
}}
</ContextMenuWithData>
);
});