diff --git a/.gitignore b/.gitignore
index 321920d0..bb4a98c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,5 @@ dist-ssr
public/assets
public/assets_*
!public/assets_default
+
+.vscode/vscode-chrome-debug-userdatadir
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..e80b447e
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,17 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "pwa-chrome",
+ "request": "launch",
+ "name": "Launch Chrome against localhost",
+ "url": "http://local.revolt.chat:3000",
+ "webRoot": "${workspaceFolder}",
+ "runtimeExecutable": "/usr/bin/chromium",
+ "userDataDir": "${workspaceFolder}/.vscode/vscode-chrome-debug-userdatadir"
+ }
+ ]
+}
diff --git a/package.json b/package.json
index 279af451..57e2a352 100644
--- a/package.json
+++ b/package.json
@@ -136,14 +136,12 @@
"react-helmet": "^6.1.0",
"react-hook-form": "6.3.0",
"react-overlapping-panels": "1.2.2",
- "react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-scroll": "^1.8.2",
"react-virtualized-auto-sizer": "^1.0.5",
"react-virtuoso": "^1.10.4",
- "redux": "^4.1.0",
"revolt-api": "0.5.3-alpha.10",
- "revolt.js": "5.2.0-patch.0",
+ "revolt.js": "5.2.1-patch.1",
"rimraf": "^3.0.2",
"sass": "^1.35.1",
"shade-blend-color": "^1.0.0",
diff --git a/src/components/common/AgeGate.tsx b/src/components/common/AgeGate.tsx
index 03071212..11c2b56c 100644
--- a/src/components/common/AgeGate.tsx
+++ b/src/components/common/AgeGate.tsx
@@ -6,7 +6,8 @@ import styled from "styled-components";
import { Text } from "preact-i18n";
import { useState } from "preact/hooks";
-import { dispatch, getState } from "../../redux";
+import { useApplicationState } from "../../mobx/State";
+import { SECTION_NSFW } from "../../mobx/stores/Layout";
import Button from "../ui/Button";
import Checkbox from "../ui/Checkbox";
@@ -49,9 +50,7 @@ type Props = {
export default observer((props: Props) => {
const history = useHistory();
- const [consent, setConsent] = useState(
- getState().sectionToggle["nsfw"] ?? false,
- );
+ const layout = useApplicationState().layout;
const [ageGate, setAgeGate] = useState(false);
if (ageGate || !props.gated) {
@@ -81,26 +80,19 @@ export default observer((props: Props) => {
{
- setConsent(v);
- if (v) {
- dispatch({
- type: "SECTION_TOGGLE_SET",
- id: "nsfw",
- state: true,
- });
- } else {
- dispatch({ type: "SECTION_TOGGLE_UNSET", id: "nsfw" });
- }
- }}>
+ checked={layout.getSectionState(SECTION_NSFW, false)}
+ onChange={() => layout.toggleSectionState(SECTION_NSFW, false)}>
-
diff --git a/src/components/common/messaging/Message.tsx b/src/components/common/messaging/Message.tsx
index 5eae1ed9..1765fb5f 100644
--- a/src/components/common/messaging/Message.tsx
+++ b/src/components/common/messaging/Message.tsx
@@ -7,7 +7,7 @@ import { useState } from "preact/hooks";
import { internalEmit } from "../../../lib/eventEmitter";
-import { QueuedMessage } from "../../../redux/reducers/queue";
+import { QueuedMessage } from "../../../mobx/stores/MessageQueue";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import { useClient } from "../../../context/revoltjs/RevoltClient";
diff --git a/src/components/common/messaging/MessageBox.tsx b/src/components/common/messaging/MessageBox.tsx
index 513e6280..330db243 100644
--- a/src/components/common/messaging/MessageBox.tsx
+++ b/src/components/common/messaging/MessageBox.tsx
@@ -21,7 +21,7 @@ import {
} from "../../../lib/renderer/Singleton";
import { useApplicationState } from "../../../mobx/State";
-import { Reply } from "../../../redux/reducers/queue";
+import { Reply } from "../../../mobx/stores/MessageQueue";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import {
@@ -111,7 +111,7 @@ const Action = styled.div`
const RE_SED = new RegExp("^s/([^])*/([^])*$");
// ! FIXME: add to app config and load from app config
-export const CAN_UPLOAD_AT_ONCE = 4;
+export const CAN_UPLOAD_AT_ONCE = 5;
export default observer(({ channel }: Props) => {
const state = useApplicationState();
diff --git a/src/components/common/messaging/bars/ReplyBar.tsx b/src/components/common/messaging/bars/ReplyBar.tsx
index a2d10452..0e853e25 100644
--- a/src/components/common/messaging/bars/ReplyBar.tsx
+++ b/src/components/common/messaging/bars/ReplyBar.tsx
@@ -10,8 +10,9 @@ import { StateUpdater, useEffect } from "preact/hooks";
import { internalSubscribe } from "../../../../lib/eventEmitter";
-import { dispatch, getState } from "../../../../redux";
-import { Reply } from "../../../../redux/reducers/queue";
+import { useApplicationState } from "../../../../mobx/State";
+import { SECTION_MENTION } from "../../../../mobx/stores/Layout";
+import { Reply } from "../../../../mobx/stores/MessageQueue";
import IconButton from "../../../ui/IconButton";
@@ -81,6 +82,7 @@ const Base = styled.div`
const MAX_REPLIES = 5;
export default observer(({ channel, replies, setReplies }: Props) => {
const client = channel.client;
+ const layout = useApplicationState().layout;
// Event listener for adding new messages to reply bar.
useEffect(() => {
@@ -99,7 +101,7 @@ export default observer(({ channel, replies, setReplies }: Props) => {
mention:
message.author_id === client.user!._id
? false
- : getState().sectionToggle.mention ?? false,
+ : layout.getSectionState("SECTION_MENTION", false),
},
]);
});
@@ -181,11 +183,11 @@ export default observer(({ channel, replies, setReplies }: Props) => {
}),
);
- dispatch({
- type: "SECTION_TOGGLE_SET",
- id: "mention",
+ layout.setSectionState(
+ SECTION_MENTION,
state,
- });
+ false,
+ );
}}>
diff --git a/src/components/common/messaging/embed/EmbedInvite.tsx b/src/components/common/messaging/embed/EmbedInvite.tsx
index 830c5695..165a6f7b 100644
--- a/src/components/common/messaging/embed/EmbedInvite.tsx
+++ b/src/components/common/messaging/embed/EmbedInvite.tsx
@@ -10,8 +10,6 @@ import { useContext, useEffect, useState } from "preact/hooks";
import { defer } from "../../../../lib/defer";
import { isTouchscreenDevice } from "../../../../lib/isTouchscreenDevice";
-import { dispatch } from "../../../../redux";
-
import {
AppContext,
ClientStatus,
@@ -33,7 +31,7 @@ const EmbedInviteBase = styled.div`
align-items: center;
padding: 0 12px;
margin-top: 2px;
- ${() =>
+ ${() =>
isTouchscreenDevice &&
css`
flex-wrap: wrap;
@@ -44,19 +42,17 @@ const EmbedInviteBase = styled.div`
> button {
width: 100%;
}
- `
- }
+ `}
`;
const EmbedInviteDetails = styled.div`
flex-grow: 1;
padding-left: 12px;
- ${() =>
+ ${() =>
isTouchscreenDevice &&
css`
width: calc(100% - 55px);
- `
- }
+ `}
`;
const EmbedInviteName = styled.div`
@@ -74,11 +70,10 @@ type Props = {
code: string;
};
-export function EmbedInvite(props: Props) {
+export function EmbedInvite({ code }: Props) {
const history = useHistory();
const client = useContext(AppContext);
const status = useContext(StatusContext);
- const code = props.code;
const [processing, setProcessing] = useState(false);
const [error, setError] = useState(undefined);
const [joinError, setJoinError] = useState(undefined);
@@ -124,7 +119,8 @@ export function EmbedInvite(props: Props) {
{invite.server_name}
- {invite.member_count.toLocaleString()} {invite.member_count === 1 ? "member" : "members"}
+ {invite.member_count.toLocaleString()}{" "}
+ {invite.member_count === 1 ? "member" : "members"}
{processing ? (
@@ -151,10 +147,9 @@ export function EmbedInvite(props: Props) {
defer(() => {
if (server) {
- dispatch({
- type: "UNREADS_MARK_MULTIPLE_READ",
- channels: server.channel_ids,
- });
+ client.unreads!.markMultipleRead(
+ server.channel_ids,
+ );
history.push(
`/server/${server._id}/channel/${invite.channel_id}`,
@@ -172,7 +167,9 @@ export function EmbedInvite(props: Props) {
setProcessing(false);
}
}}>
- {client.servers.get(invite.server_id) ? "Joined" : "Join"}
+ {client.servers.get(invite.server_id)
+ ? "Joined"
+ : "Join"}
)}
diff --git a/src/components/navigation/LeftSidebar.tsx b/src/components/navigation/LeftSidebar.tsx
index d9787a2e..309cb360 100644
--- a/src/components/navigation/LeftSidebar.tsx
+++ b/src/components/navigation/LeftSidebar.tsx
@@ -1,14 +1,16 @@
import { Route, Switch } from "react-router";
+import { useApplicationState } from "../../mobx/State";
+import { SIDEBAR_CHANNELS } from "../../mobx/stores/Layout";
+
import SidebarBase from "./SidebarBase";
import HomeSidebar from "./left/HomeSidebar";
import ServerListSidebar from "./left/ServerListSidebar";
import ServerSidebar from "./left/ServerSidebar";
-import { useSelector } from "react-redux";
-import { State } from "../../redux";
export default function LeftSidebar() {
- const isOpen = useSelector((state: State) => state.sectionToggle['sidebar_channels'] ?? true)
+ const layout = useApplicationState().layout;
+ const isOpen = layout.getSectionState(SIDEBAR_CHANNELS, true);
return (
diff --git a/src/components/navigation/left/ServerSidebar.tsx b/src/components/navigation/left/ServerSidebar.tsx
index 7ed142ef..b08e5597 100644
--- a/src/components/navigation/left/ServerSidebar.tsx
+++ b/src/components/navigation/left/ServerSidebar.tsx
@@ -11,7 +11,6 @@ import { internalEmit } from "../../../lib/eventEmitter";
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
import { useApplicationState } from "../../../mobx/State";
-import { connectState } from "../../../redux/connector";
import { useClient } from "../../../context/revoltjs/RevoltClient";
diff --git a/src/components/navigation/left/common.ts b/src/components/navigation/left/common.ts
deleted file mode 100644
index ee5e5b66..00000000
--- a/src/components/navigation/left/common.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { reaction } from "mobx";
-import { Channel } from "revolt.js/dist/maps/Channels";
-
-import { useLayoutEffect } from "preact/hooks";
-
-import { dispatch } from "../../../redux";
-import { Unreads } from "../../../redux/reducers/unreads";
-
-type UnreadProps = {
- channel: Channel;
- unreads: Unreads;
-};
-
-export function useUnreads({ channel, unreads }: UnreadProps) {
- // const firstLoad = useRef(true);
- useLayoutEffect(() => {
- function checkUnread(target: Channel) {
- if (!target) return;
- if (target._id !== channel._id) return;
- if (
- target.channel_type === "SavedMessages" ||
- target.channel_type === "VoiceChannel"
- )
- return;
-
- const unread = unreads[channel._id]?.last_id;
- if (target.last_message_id) {
- if (
- !unread ||
- (unread && target.last_message_id.localeCompare(unread) > 0)
- ) {
- dispatch({
- type: "UNREADS_MARK_READ",
- channel: channel._id,
- message: target.last_message_id,
- });
-
- channel.ack(target.last_message_id);
- }
- }
- }
-
- checkUnread(channel);
- return reaction(
- () => channel.last_message,
- () => checkUnread(channel),
- );
- }, [channel, unreads]);
-}
-
-export function mapChannelWithUnread(channel: Channel, unreads: Unreads) {
- const last_message_id = channel.last_message_id;
-
- let unread: "mention" | "unread" | undefined;
- let alertCount: undefined | number;
- if (last_message_id && unreads) {
- const u = unreads[channel._id];
- if (u) {
- if (u.mentions && u.mentions.length > 0) {
- alertCount = u.mentions.length;
- unread = "mention";
- } else if (
- u.last_id &&
- (last_message_id as string).localeCompare(u.last_id) > 0
- ) {
- unread = "unread";
- }
- } else {
- unread = "unread";
- }
- }
-
- return {
- channel,
- timestamp: last_message_id ?? channel._id,
- unread,
- alertCount,
- };
-}
diff --git a/src/components/ui/Modal.tsx b/src/components/ui/Modal.tsx
index 905dbd85..e25d6b38 100644
--- a/src/components/ui/Modal.tsx
+++ b/src/components/ui/Modal.tsx
@@ -167,7 +167,7 @@ export default function Modal(props: Props) {
isModalClosing = animateClose;
const onClose = useCallback(() => {
setAnimateClose(true);
- setTimeout(() => props.onClose?.(), 2e2);
+ setTimeout(() => props.onClose!(), 2e2);
}, [setAnimateClose, props]);
useEffect(() => internalSubscribe("Modal", "close", onClose), [onClose]);
diff --git a/src/context/Theme.tsx b/src/context/Theme.tsx
index 55370c44..2c69ac9f 100644
--- a/src/context/Theme.tsx
+++ b/src/context/Theme.tsx
@@ -5,7 +5,6 @@ import { createGlobalStyle } from "styled-components";
import { useEffect } from "preact/hooks";
import { useApplicationState } from "../mobx/State";
-import { getState } from "../redux";
export type Variables =
| "accent"
@@ -280,28 +279,6 @@ export const PRESETS: Record = {
},
};
-// todo: store used themes locally
-export function getBaseTheme(name: string): Theme {
- if (name in PRESETS) {
- return PRESETS[name];
- }
-
- // TODO: properly initialize `themes` in state instead of letting it be undefined
- const themes = getState().themes ?? {};
-
- if (name in themes) {
- const { theme } = themes[name];
-
- return {
- ...PRESETS[theme.light ? "light" : "dark"],
- ...theme,
- };
- }
-
- // how did we get here
- return PRESETS["dark"];
-}
-
const keys = Object.keys(PRESETS.dark);
const GlobalTheme = createGlobalStyle<{ theme: Theme }>`
:root {
diff --git a/src/context/index.tsx b/src/context/index.tsx
index 1b6ee4ec..3f768da0 100644
--- a/src/context/index.tsx
+++ b/src/context/index.tsx
@@ -1,7 +1,5 @@
import { BrowserRouter as Router } from "react-router-dom";
-import State from "../redux/State";
-
import { Children } from "../types/Preact";
import Locale from "./Locale";
import Theme from "./Theme";
@@ -15,14 +13,12 @@ import Client from "./revoltjs/RevoltClient";
export default function Context({ children }: { children: Children }) {
return (
-
-
-
- {children}
-
-
-
-
+
+
+ {children}
+
+
+
);
}
diff --git a/src/context/intermediate/modals/ExternalLinkPrompt.tsx b/src/context/intermediate/modals/ExternalLinkPrompt.tsx
index 42f73f28..09efd941 100644
--- a/src/context/intermediate/modals/ExternalLinkPrompt.tsx
+++ b/src/context/intermediate/modals/ExternalLinkPrompt.tsx
@@ -1,7 +1,6 @@
import { Text } from "preact-i18n";
import { useApplicationState } from "../../../mobx/State";
-import { dispatch } from "../../../redux";
import Modal from "../../../components/ui/Modal";
diff --git a/src/context/revoltjs/StateMonitor.tsx b/src/context/revoltjs/StateMonitor.tsx
index 2f05b74e..061a7168 100644
--- a/src/context/revoltjs/StateMonitor.tsx
+++ b/src/context/revoltjs/StateMonitor.tsx
@@ -6,31 +6,28 @@ import { Message } from "revolt.js/dist/maps/Messages";
import { useContext, useEffect } from "preact/hooks";
import { useApplicationState } from "../../mobx/State";
-import { connectState } from "../../redux/connector";
-import { QueuedMessage } from "../../redux/reducers/queue";
import { setGlobalEmojiPack } from "../../components/common/Emoji";
import { AppContext } from "./RevoltClient";
-type Props = {
- messages: QueuedMessage[];
-};
-
-function StateMonitor(props: Props) {
+export default function StateMonitor() {
const client = useContext(AppContext);
const state = useApplicationState();
useEffect(() => {
function add(msg: Message) {
if (!msg.nonce) return;
- if (!props.messages.find((x) => x.id === msg.nonce)) return;
+ if (
+ !state.queue.get(msg.channel_id).find((x) => x.id === msg.nonce)
+ )
+ return;
state.queue.remove(msg.nonce);
}
client.addListener("message", add);
return () => client.removeListener("message", add);
- }, [client, props.messages]);
+ }, [client]);
// Set global emoji pack.
useEffect(() => {
@@ -40,9 +37,3 @@ function StateMonitor(props: Props) {
return null;
}
-
-export default connectState(StateMonitor, (state) => {
- return {
- messages: [...state.queue],
- };
-});
diff --git a/src/context/revoltjs/SyncManager.tsx b/src/context/revoltjs/SyncManager.tsx
index ec272ea5..4dd5b43f 100644
--- a/src/context/revoltjs/SyncManager.tsx
+++ b/src/context/revoltjs/SyncManager.tsx
@@ -1,25 +1,6 @@
/**
* This file monitors changes to settings and syncs them to the server.
*/
-import isEqual from "lodash.isequal";
-import { UserSettings } from "revolt-api/types/Sync";
-import { ClientboundNotification } from "revolt.js/dist/websocket/notifications";
-
-import { useCallback, useContext, useEffect, useMemo } from "preact/hooks";
-
-import { dispatch } from "../../redux";
-import { connectState } from "../../redux/connector";
-import { Notifications } from "../../redux/reducers/notifications";
-import { Settings } from "../../redux/reducers/settings";
-import {
- DEFAULT_ENABLED_SYNC,
- SyncData,
- SyncKeys,
- SyncOptions,
-} from "../../redux/reducers/sync";
-
-import { Language } from "../Locale";
-import { AppContext, ClientStatus, StatusContext } from "./RevoltClient";
/*type Props = {
settings: Settings;
diff --git a/src/context/revoltjs/events.ts b/src/context/revoltjs/events.ts
index 2556e76f..c39ed9fb 100644
--- a/src/context/revoltjs/events.ts
+++ b/src/context/revoltjs/events.ts
@@ -5,7 +5,6 @@ import { ClientboundNotification } from "revolt.js/dist/websocket/notifications"
import { StateUpdater } from "preact/hooks";
import Auth from "../../mobx/stores/Auth";
-import { dispatch } from "../../redux";
import { ClientStatus } from "./RevoltClient";
@@ -46,29 +45,6 @@ export function registerEvents(
attemptReconnect();
},
- packet: (packet: ClientboundNotification) => {
- switch (packet.type) {
- case "ChannelAck": {
- dispatch({
- type: "UNREADS_MARK_READ",
- channel: packet.id,
- message: packet.message_id,
- });
- break;
- }
- }
- },
-
- message: (message: Message) => {
- if (message.mention_ids?.includes(client.user!._id)) {
- dispatch({
- type: "UNREADS_MENTION",
- channel: message.channel_id,
- message: message._id,
- });
- }
- },
-
ready: () => setStatus(ClientStatus.ONLINE),
logout: () => {
diff --git a/src/lib/ContextMenus.tsx b/src/lib/ContextMenus.tsx
index 0b48eab7..cb657c20 100644
--- a/src/lib/ContextMenus.tsx
+++ b/src/lib/ContextMenus.tsx
@@ -33,14 +33,8 @@ import { Text } from "preact-i18n";
import { useContext } from "preact/hooks";
import { useApplicationState } from "../mobx/State";
-import { dispatch } from "../redux";
-import { connectState } from "../redux/connector";
-import {
- getNotificationState,
- Notifications,
- NotificationState,
-} from "../redux/reducers/notifications";
-import { QueuedMessage } from "../redux/reducers/queue";
+import { QueuedMessage } from "../mobx/stores/MessageQueue";
+import { NotificationState } from "../mobx/stores/NotificationOptions";
import { Screen, useIntermediate } from "../context/intermediate/Intermediate";
import {
@@ -174,21 +168,19 @@ export default function ContextMenus() {
)
return;
- dispatch({
- type: "UNREADS_MARK_READ",
- channel: data.channel._id,
- message: data.channel.last_message_id!,
- });
-
- data.channel.ack(undefined, true);
+ client.unreads!.markRead(
+ data.channel._id,
+ data.channel.last_message_id!,
+ true,
+ true,
+ );
}
break;
case "mark_server_as_read":
{
- dispatch({
- type: "UNREADS_MARK_MULTIPLE_READ",
- channels: data.server.channel_ids,
- });
+ client.unreads!.markMultipleRead(
+ data.server.channel_ids,
+ );
data.server.ack();
}
@@ -439,16 +431,6 @@ export default function ContextMenus() {
case "open_server_settings":
history.push(`/server/${data.id}/settings`);
break;
-
- case "set_notification_state": {
- const { key, state } = data;
- if (state) {
- dispatch({ type: "NOTIFICATIONS_SET", key, state });
- } else {
- dispatch({ type: "NOTIFICATIONS_REMOVE", key });
- }
- break;
- }
}
})().catch((err) => {
openScreen({ id: "error", error: takeError(err) });
diff --git a/src/mobx/State.ts b/src/mobx/State.ts
index 9c94cee3..bd69020f 100644
--- a/src/mobx/State.ts
+++ b/src/mobx/State.ts
@@ -14,6 +14,7 @@ import MessageQueue from "./stores/MessageQueue";
import NotificationOptions from "./stores/NotificationOptions";
import ServerConfig from "./stores/ServerConfig";
import Settings from "./stores/Settings";
+import Sync from "./stores/Sync";
/**
* Handles global application state.
@@ -28,6 +29,7 @@ export default class State {
notifications: NotificationOptions;
queue: MessageQueue;
settings: Settings;
+ sync: Sync;
private persistent: [string, Persistent][] = [];
@@ -44,6 +46,7 @@ export default class State {
this.notifications = new NotificationOptions();
this.queue = new MessageQueue();
this.settings = new Settings();
+ this.sync = new Sync();
makeAutoObservable(this);
this.registerListeners = this.registerListeners.bind(this);
@@ -116,14 +119,25 @@ export default class State {
}
}
-const StateContext = createContext(null!);
-
-export const StateContextProvider = StateContext.Provider;
+var state: State;
/**
* Get the application state
* @returns Application state
*/
export function useApplicationState() {
- return useContext(StateContext);
+ if (!state) state = new State();
+ return state;
}
+
+/**
+ *
+ * Redux hydration:
+ * localForage.getItem("state").then((s) => {
+ if (s !== null) {
+ dispatch({ type: "__INIT", state: s as State });
+ }
+
+ state.hydrate().then(() => setLoaded(true));
+ });
+ */
diff --git a/src/mobx/implementation notes b/src/mobx/implementation notes
deleted file mode 100644
index 36cefd3b..00000000
--- a/src/mobx/implementation notes
+++ /dev/null
@@ -1,25 +0,0 @@
-split settings per account(?)
-multiple accounts need to be supported
-
-redux -> mobx migration (wipe existing redux data post-migration)
-
-> look into talking with other tabs to detect multiple instances
-> (also use this to tell the user to close all tabs before updating)
-
-write new settings data structures for server-side
----- (deprecate existing API and replace with new endpoints?)
-alternatively: keep using current system and eventually migrate
-or: handle both incoming types of data and keep newer version (v1_prefix)
-need to document these data structures
-
-provide state globally? perform all authentication from inside mobx
-mobx parent holds client information and prepares us for first render
-
-reasoning for global:
-
-- we can't and won't have more than one of the application running in a single tab
-- interactions become simpler
-- all accounts will be managed from one place anyways
-
-things such as unreads can pass through this data store providing a host of
-information, such as whether there are any alerts on channels, etc
diff --git a/src/mobx/legacy/redux.ts b/src/mobx/legacy/redux.ts
index 73154743..e9edfa5f 100644
--- a/src/mobx/legacy/redux.ts
+++ b/src/mobx/legacy/redux.ts
@@ -1,4 +1,4 @@
-import { AuthState } from "../../redux/reducers/auth";
+import { Session } from "revolt-api/types/Auth";
import { Language } from "../../context/Locale";
import { Fonts, MonospaceFonts, Overrides } from "../../context/Theme";
@@ -7,6 +7,7 @@ import { Data as DataAuth } from "../stores/Auth";
import { Data as DataLocaleOptions } from "../stores/LocaleOptions";
import { Data as DataNotificationOptions } from "../stores/NotificationOptions";
import { ISettings } from "../stores/Settings";
+import { Data as DataSync } from "../stores/Sync";
export type LegacyTheme = Overrides & {
light?: boolean;
@@ -39,7 +40,29 @@ export interface LegacySyncData {
notifications?: LegacyNotifications;
}
-function legacyMigrateAuth(auth: AuthState): DataAuth {
+export type LegacySyncKeys =
+ | "theme"
+ | "appearance"
+ | "locale"
+ | "notifications";
+
+export interface LegacySyncOptions {
+ disabled?: LegacySyncKeys[];
+ revision?: {
+ [key: string]: number;
+ };
+}
+
+export interface LegacyAuthState {
+ accounts: {
+ [key: string]: {
+ session: Session;
+ };
+ };
+ active?: string;
+}
+
+function legacyMigrateAuth(auth: LegacyAuthState): DataAuth {
return {
current: auth.active,
sessions: auth.accounts,
@@ -82,3 +105,12 @@ function legacyMigrateNotification(
channel,
};
}
+
+function legacyMigrateSync(sync: LegacySyncOptions): DataSync {
+ return {
+ disabled: sync.disabled ?? [],
+ revision: {
+ ...sync.revision,
+ },
+ };
+}
diff --git a/src/mobx/stores/Layout.ts b/src/mobx/stores/Layout.ts
index cd17a4ac..80d03109 100644
--- a/src/mobx/stores/Layout.ts
+++ b/src/mobx/stores/Layout.ts
@@ -12,6 +12,11 @@ export interface Data {
openSections?: Record;
}
+export const SIDEBAR_MEMBERS = "sidebar_members";
+export const SIDEBAR_CHANNELS = "sidebar_channels";
+export const SECTION_MENTION = "mention";
+export const SECTION_NSFW = "nsfw";
+
/**
* Keeps track of the last open channels, tabs, etc.
* Handles providing good UX experience on navigating
@@ -165,4 +170,13 @@ export default class Layout implements Store, Persistent {
this.openSections.set(id, value);
}
}
+
+ /**
+ * Toggle state of a section.
+ * @param id Section ID
+ * @param def Default state value
+ */
+ @action toggleSectionState(id: string, def?: boolean) {
+ this.setSectionState(id, !this.getSectionState(id, def));
+ }
}
diff --git a/src/mobx/stores/Settings.ts b/src/mobx/stores/Settings.ts
index 80a75253..b64e4b9f 100644
--- a/src/mobx/stores/Settings.ts
+++ b/src/mobx/stores/Settings.ts
@@ -78,10 +78,14 @@ export default class Settings implements Store, Persistent {
/**
* Get a settings key.
* @param key Colon-divided key
+ * @param defaultValue Default value if not present
* @returns Value at key
*/
- @computed get(key: T) {
- return this.data.get(key) as ISettings[T] | undefined;
+ @computed get(
+ key: T,
+ defaultValue?: ISettings[T],
+ ) {
+ return (this.data.get(key) as ISettings[T] | undefined) ?? defaultValue;
}
@action remove(key: T) {
diff --git a/src/mobx/stores/Sync.ts b/src/mobx/stores/Sync.ts
index 42d76edc..b0b47065 100644
--- a/src/mobx/stores/Sync.ts
+++ b/src/mobx/stores/Sync.ts
@@ -23,6 +23,9 @@ export const SYNC_KEYS: SyncKeys[] = [
export interface Data {
disabled: SyncKeys[];
+ revision: {
+ [key: string]: number;
+ };
}
/**
@@ -30,12 +33,14 @@ export interface Data {
*/
export default class Sync implements Store, Persistent {
private disabled: ObservableSet;
+ private revision: ObservableMap;
/**
* Construct new Sync store.
*/
constructor() {
this.disabled = new ObservableSet();
+ this.revision = new ObservableMap();
makeAutoObservable(this);
this.isEnabled = this.isEnabled.bind(this);
}
@@ -47,6 +52,7 @@ export default class Sync implements Store, Persistent {
toJSON() {
return {
enabled: [...this.disabled],
+ revision: mapToRecord(this.revision),
};
}
@@ -58,6 +64,22 @@ export default class Sync implements Store, Persistent {
}
}
+ @action enable(key: SyncKeys) {
+ this.disabled.delete(key);
+ }
+
+ @action disable(key: SyncKeys) {
+ this.disabled.add(key);
+ }
+
+ @action toggle(key: SyncKeys) {
+ if (this.isEnabled(key)) {
+ this.disable(key);
+ } else {
+ this.enable(key);
+ }
+ }
+
@computed isEnabled(key: SyncKeys) {
return !this.disabled.has(key);
}
diff --git a/src/pages/channels/Channel.tsx b/src/pages/channels/Channel.tsx
index cf162c6d..8695f5c3 100644
--- a/src/pages/channels/Channel.tsx
+++ b/src/pages/channels/Channel.tsx
@@ -7,11 +7,12 @@ import { Channel as ChannelI } from "revolt.js/dist/maps/Channels";
import styled from "styled-components";
import { Text } from "preact-i18n";
-import { useEffect, useState } from "preact/hooks";
+import { useEffect } from "preact/hooks";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
-import { dispatch, getState } from "../../redux";
+import { useApplicationState } from "../../mobx/State";
+import { SIDEBAR_MEMBERS } from "../../mobx/stores/Layout";
import { useClient } from "../../context/revoltjs/RevoltClient";
@@ -83,15 +84,8 @@ export function Channel({ id }: { id: string }) {
return ;
}
-const MEMBERS_SIDEBAR_KEY = "sidebar_members";
-const CHANNELS_SIDEBAR_KEY = "sidebar_channels";
const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
- const [showMembers, setMembers] = useState(
- getState().sectionToggle[MEMBERS_SIDEBAR_KEY] ?? true,
- );
- const [showChannels, setChannels] = useState(
- getState().sectionToggle[CHANNELS_SIDEBAR_KEY] ?? true,
- );
+ const layout = useApplicationState().layout;
// Mark channel as read.
useEffect(() => {
@@ -121,45 +115,7 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
channel.nsfw
)
}>
- {
- setMembers(!showMembers);
-
- if (showMembers) {
- dispatch({
- type: "SECTION_TOGGLE_SET",
- id: MEMBERS_SIDEBAR_KEY,
- state: false,
- });
- } else {
- dispatch({
- type: "SECTION_TOGGLE_UNSET",
- id: MEMBERS_SIDEBAR_KEY,
- });
- }
- }}
- toggleChannelSidebar={() => {
- if (isTouchscreenDevice) {
- return;
- }
-
- setChannels(!showChannels);
-
- if (showChannels) {
- dispatch({
- type: "SECTION_TOGGLE_SET",
- id: CHANNELS_SIDEBAR_KEY,
- state: false,
- });
- } else {
- dispatch({
- type: "SECTION_TOGGLE_UNSET",
- id: CHANNELS_SIDEBAR_KEY,
- });
- }
- }}
- />
+
@@ -168,7 +124,10 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
- {!isTouchscreenDevice && showMembers && }
+ {!isTouchscreenDevice &&
+ layout.getSectionState(SIDEBAR_MEMBERS, true) && (
+
+ )}
);
diff --git a/src/pages/channels/ChannelHeader.tsx b/src/pages/channels/ChannelHeader.tsx
index 84f188af..3fa8e835 100644
--- a/src/pages/channels/ChannelHeader.tsx
+++ b/src/pages/channels/ChannelHeader.tsx
@@ -1,4 +1,4 @@
-import { At, Hash, Menu } from "@styled-icons/boxicons-regular";
+import { At, Hash } from "@styled-icons/boxicons-regular";
import { Notepad, Group } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels";
@@ -7,6 +7,9 @@ import styled, { css } from "styled-components";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
+import { useApplicationState } from "../../mobx/State";
+import { SIDEBAR_MEMBERS } from "../../mobx/stores/Layout";
+
import { useIntermediate } from "../../context/intermediate/Intermediate";
import { getChannelName } from "../../context/revoltjs/util";
@@ -69,14 +72,16 @@ const IconConainer = styled.div`
cursor: pointer;
color: var(--secondary-foreground);
- ${!isTouchscreenDevice && css`
+ ${!isTouchscreenDevice &&
+ css`
&:hover {
color: var(--foreground);
}
`}
-`
+`;
-export default observer(({ channel, toggleSidebar, toggleChannelSidebar }: ChannelHeaderProps) => {
+export default observer(({ channel }: ChannelHeaderProps) => {
+ const layout = useApplicationState().layout;
const { openScreen } = useIntermediate();
const name = getChannelName(channel);
@@ -100,7 +105,12 @@ export default observer(({ channel, toggleSidebar, toggleChannelSidebar }: Chann
return (
- {icon}
+
+ layout.toggleSectionState(SIDEBAR_MEMBERS, true)
+ }>
+ {icon}
+
{name}
{isTouchscreenDevice &&
@@ -143,7 +153,7 @@ export default observer(({ channel, toggleSidebar, toggleChannelSidebar }: Chann
>
)}
-
+
);
});
diff --git a/src/pages/channels/actions/HeaderActions.tsx b/src/pages/channels/actions/HeaderActions.tsx
index 514a1b2e..0466e0ca 100644
--- a/src/pages/channels/actions/HeaderActions.tsx
+++ b/src/pages/channels/actions/HeaderActions.tsx
@@ -14,6 +14,9 @@ import { internalEmit } from "../../../lib/eventEmitter";
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
import { voiceState, VoiceStatus } from "../../../lib/vortex/VoiceState";
+import { useApplicationState } from "../../../mobx/State";
+import { SIDEBAR_MEMBERS } from "../../../mobx/stores/Layout";
+
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import UpdateIndicator from "../../../components/common/UpdateIndicator";
@@ -21,10 +24,8 @@ import IconButton from "../../../components/ui/IconButton";
import { ChannelHeaderProps } from "../ChannelHeader";
-export default function HeaderActions({
- channel,
- toggleSidebar,
-}: ChannelHeaderProps) {
+export default function HeaderActions({ channel }: ChannelHeaderProps) {
+ const layout = useApplicationState().layout;
const { openScreen } = useIntermediate();
const history = useHistory();
@@ -40,7 +41,7 @@ export default function HeaderActions({
if (isTouchscreenDevice) {
openRightSidebar();
} else {
- toggleSidebar?.();
+ layout.toggleSectionState(SIDEBAR_MEMBERS, true);
}
}
diff --git a/src/pages/channels/messaging/MessageRenderer.tsx b/src/pages/channels/messaging/MessageRenderer.tsx
index 47f0f1ae..2a7f629c 100644
--- a/src/pages/channels/messaging/MessageRenderer.tsx
+++ b/src/pages/channels/messaging/MessageRenderer.tsx
@@ -10,15 +10,12 @@ import styled from "styled-components";
import { decodeTime } from "ulid";
import { Text } from "preact-i18n";
-import { memo } from "preact/compat";
import { useEffect, useState } from "preact/hooks";
import { internalSubscribe, internalEmit } from "../../../lib/eventEmitter";
import { ChannelRenderer } from "../../../lib/renderer/Singleton";
import { useApplicationState } from "../../../mobx/State";
-import { connectState } from "../../../redux/connector";
-import { QueuedMessage } from "../../../redux/reducers/queue";
import RequiresOnline from "../../../context/revoltjs/RequiresOnline";
import { useClient } from "../../../context/revoltjs/RevoltClient";
diff --git a/src/pages/home/Home.tsx b/src/pages/home/Home.tsx
index 812ed082..f188295d 100644
--- a/src/pages/home/Home.tsx
+++ b/src/pages/home/Home.tsx
@@ -4,11 +4,12 @@ import styled, { css } from "styled-components";
import styles from "./Home.module.scss";
import { Text } from "preact-i18n";
-import { useContext, useState } from "preact/hooks";
+import { useContext } from "preact/hooks";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
-import { dispatch, getState } from "../../redux";
+import { useApplicationState } from "../../mobx/State";
+import { SIDEBAR_CHANNELS } from "../../mobx/stores/Layout";
import { AppContext } from "../../context/revoltjs/RevoltClient";
@@ -18,8 +19,6 @@ import Tooltip from "../../components/common/Tooltip";
import Header from "../../components/ui/Header";
import CategoryButton from "../../components/ui/fluent/CategoryButton";
-const CHANNELS_SIDEBAR_KEY = "sidebar_channels";
-
const IconConainer = styled.div`
cursor: pointer;
color: var(--secondary-foreground);
@@ -34,29 +33,14 @@ const IconConainer = styled.div`
export default function Home() {
const client = useContext(AppContext);
- const [showChannels, setChannels] = useState(
- getState().sectionToggle[CHANNELS_SIDEBAR_KEY] ?? true,
- );
+ const layout = useApplicationState().layout;
const toggleChannelSidebar = () => {
if (isTouchscreenDevice) {
return;
}
- setChannels(!showChannels);
-
- if (showChannels) {
- dispatch({
- type: "SECTION_TOGGLE_SET",
- id: CHANNELS_SIDEBAR_KEY,
- state: false,
- });
- } else {
- dispatch({
- type: "SECTION_TOGGLE_UNSET",
- id: CHANNELS_SIDEBAR_KEY,
- });
- }
+ layout.toggleSectionState(SIDEBAR_CHANNELS, true);
};
return (
diff --git a/src/pages/invite/Invite.tsx b/src/pages/invite/Invite.tsx
index be29427b..daba4967 100644
--- a/src/pages/invite/Invite.tsx
+++ b/src/pages/invite/Invite.tsx
@@ -10,8 +10,6 @@ import { useContext, useEffect, useState } from "preact/hooks";
import { defer } from "../../lib/defer";
import { TextReact } from "../../lib/i18n";
-import { dispatch } from "../../redux";
-
import RequiresOnline from "../../context/revoltjs/RequiresOnline";
import {
AppContext,
@@ -168,11 +166,9 @@ export default function Invite() {
defer(() => {
if (server) {
- dispatch({
- type: "UNREADS_MARK_MULTIPLE_READ",
- channels:
- server.channel_ids,
- });
+ client.unreads!.markMultipleRead(
+ server.channel_ids,
+ );
history.push(
`/server/${server._id}/channel/${invite.channel_id}`,
diff --git a/src/pages/settings/panes/Audio.tsx b/src/pages/settings/panes/Audio.tsx
index 35776b89..ecc7f4c1 100644
--- a/src/pages/settings/panes/Audio.tsx
+++ b/src/pages/settings/panes/Audio.tsx
@@ -6,8 +6,6 @@ import { TextReact } from "../../../lib/i18n";
import { stopPropagation } from "../../../lib/stopPropagation";
import { voiceState } from "../../../lib/vortex/VoiceState";
-import { connectState } from "../../../redux/connector";
-
import Button from "../../../components/ui/Button";
import ComboBox from "../../../components/ui/ComboBox";
import Overline from "../../../components/ui/Overline";
@@ -17,7 +15,7 @@ const constraints = { audio: true };
// TODO: do not rewrite this code until voice is rewritten!
-export function Component() {
+export function Audio() {
const [mediaStream, setMediaStream] = useState(
undefined,
);
@@ -163,7 +161,3 @@ function changeAudioDevice(deviceId: string, deviceType: string) {
window.localStorage.setItem("audioOutputDevice", deviceId);
}
}
-
-export const Audio = connectState(Component, () => {
- return;
-});
diff --git a/src/pages/settings/panes/Languages.tsx b/src/pages/settings/panes/Languages.tsx
index 98f76e0d..6947bb12 100644
--- a/src/pages/settings/panes/Languages.tsx
+++ b/src/pages/settings/panes/Languages.tsx
@@ -4,12 +4,7 @@ import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
import { useMemo } from "preact/hooks";
-import PaintCounter from "../../../lib/PaintCounter";
-
import { useApplicationState } from "../../../mobx/State";
-import LocaleOptions from "../../../mobx/stores/LocaleOptions";
-import { dispatch } from "../../../redux";
-import { connectState } from "../../../redux/connector";
import {
Language,
diff --git a/src/pages/settings/panes/Notifications.tsx b/src/pages/settings/panes/Notifications.tsx
index 879f7c62..6643dd37 100644
--- a/src/pages/settings/panes/Notifications.tsx
+++ b/src/pages/settings/panes/Notifications.tsx
@@ -5,23 +5,16 @@ import { useContext, useEffect, useState } from "preact/hooks";
import { urlBase64ToUint8Array } from "../../../lib/conversion";
import { useApplicationState } from "../../../mobx/State";
-import { dispatch } from "../../../redux";
-import { connectState } from "../../../redux/connector";
-import { NotificationOptions } from "../../../redux/reducers/settings";
import { useIntermediate } from "../../../context/intermediate/Intermediate";
import { AppContext } from "../../../context/revoltjs/RevoltClient";
import Checkbox from "../../../components/ui/Checkbox";
-interface Props {
- options?: NotificationOptions;
-}
-
-export function Component({ options }: Props) {
+export function Notifications() {
const client = useContext(AppContext);
const { openScreen } = useIntermediate();
- const sounds = useApplicationState().settings.sounds;
+ const settings = useApplicationState().settings;
const [pushEnabled, setPushEnabled] = useState(
undefined,
);
@@ -43,7 +36,7 @@ export function Component({ options }: Props) {
}
@@ -51,6 +44,7 @@ export function Component({ options }: Props) {
if (desktopEnabled) {
const permission =
await Notification.requestPermission();
+
if (permission !== "granted") {
return openScreen({
id: "error",
@@ -59,10 +53,7 @@ export function Component({ options }: Props) {
}
}
- dispatch({
- type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
- options: { desktopEnabled },
- });
+ settings.set("notifications:desktop", desktopEnabled);
}}>
@@ -115,20 +106,16 @@ export function Component({ options }: Props) {
- {sounds.getState().map(({ id, enabled }) => (
+ {settings.sounds.getState().map(({ id, enabled }) => (
sounds.setEnabled(id, enabled)}>
+ onChange={(enabled) =>
+ settings.sounds.setEnabled(id, enabled)
+ }>
))}
);
}
-
-export const Notifications = connectState(Component, (state) => {
- return {
- options: state.settings.notification,
- };
-});
diff --git a/src/pages/settings/panes/Sync.tsx b/src/pages/settings/panes/Sync.tsx
index c77b5df2..1eff8640 100644
--- a/src/pages/settings/panes/Sync.tsx
+++ b/src/pages/settings/panes/Sync.tsx
@@ -1,17 +1,16 @@
+import { observer } from "mobx-react-lite";
+
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
-import { dispatch } from "../../../redux";
-import { connectState } from "../../../redux/connector";
-import { SyncKeys, SyncOptions } from "../../../redux/reducers/sync";
+import { useApplicationState } from "../../../mobx/State";
+import { SyncKeys } from "../../../mobx/stores/Sync";
import Checkbox from "../../../components/ui/Checkbox";
-interface Props {
- options?: SyncOptions;
-}
+export const Sync = observer(() => {
+ const sync = useApplicationState().sync;
-export function Component(props: Props) {
return (
@@ -27,31 +26,16 @@ export function Component(props: Props) {
).map(([key, title]) => (
}
- onChange={(enabled) =>
- dispatch({
- type: enabled
- ? "SYNC_ENABLE_KEY"
- : "SYNC_DISABLE_KEY",
- key,
- })
- }>
+ onChange={() => sync.toggle(key)}>
))}
);
-}
-
-export const Sync = connectState(Component, (state) => {
- return {
- options: state.sync,
- };
});
diff --git a/src/pages/settings/panes/ThemeShop.tsx b/src/pages/settings/panes/ThemeShop.tsx
index 11965e64..10220ec7 100644
--- a/src/pages/settings/panes/ThemeShop.tsx
+++ b/src/pages/settings/panes/ThemeShop.tsx
@@ -3,9 +3,8 @@ import styled from "styled-components";
import { useEffect, useState } from "preact/hooks";
import { useApplicationState } from "../../../mobx/State";
-import { dispatch } from "../../../redux";
-import { Theme, generateVariables, ThemeOptions } from "../../../context/Theme";
+import { Theme, generateVariables } from "../../../context/Theme";
import Tip from "../../../components/ui/Tip";
import previewPath from "../assets/preview.svg";
diff --git a/src/redux/State.tsx b/src/redux/State.tsx
deleted file mode 100644
index 7edfd4b7..00000000
--- a/src/redux/State.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import localForage from "localforage";
-import { Provider } from "react-redux";
-
-import { useEffect, useRef, useState } from "preact/hooks";
-
-import MobXState, { StateContextProvider } from "../mobx/State";
-
-import { dispatch, State, store } from ".";
-import { Children } from "../types/Preact";
-
-interface Props {
- children: Children;
-}
-
-/**
- * Component for loading application state.
- * @param props Provided children
- */
-export default function StateLoader(props: Props) {
- const [loaded, setLoaded] = useState(false);
- const { current: state } = useRef(new MobXState());
-
- // Globally expose the application state.
- useEffect(() => {
- (window as unknown as Record).state = state;
- }, [state]);
-
- useEffect(() => {
- localForage.getItem("state").then((s) => {
- if (s !== null) {
- dispatch({ type: "__INIT", state: s as State });
- }
-
- state.hydrate().then(() => setLoaded(true));
- });
- }, []);
-
- if (!loaded) return null;
-
- useEffect(state.registerListeners);
-
- return (
-
-
- {props.children}
-
-
- );
-}
diff --git a/src/redux/connector.tsx b/src/redux/connector.tsx
deleted file mode 100644
index 699238d6..00000000
--- a/src/redux/connector.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-import { connect, ConnectedComponent } from "react-redux";
-
-import { h } from "preact";
-import { memo } from "preact/compat";
-
-import { State } from ".";
-
-export function connectState(
- component: (props: any) => h.JSX.Element | null,
- mapKeys: (state: State, props: T) => any,
- memoize?: boolean,
-): ConnectedComponent<(props: any) => h.JSX.Element | null, T> {
- const c = connect(mapKeys)(component);
- return memoize ? memo(c) : c;
-}
diff --git a/src/redux/index.ts b/src/redux/index.ts
deleted file mode 100644
index 2e5b7d84..00000000
--- a/src/redux/index.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import localForage from "localforage";
-import { createStore } from "redux";
-import { RevoltConfiguration } from "revolt-api/types/Core";
-
-import { Language } from "../context/Locale";
-
-import rootReducer, { Action } from "./reducers";
-import { AuthState } from "./reducers/auth";
-import { Drafts } from "./reducers/drafts";
-import { ExperimentOptions } from "./reducers/experiments";
-import { LastOpened } from "./reducers/last_opened";
-import { Notifications } from "./reducers/notifications";
-import { QueuedMessage } from "./reducers/queue";
-import { SectionToggle } from "./reducers/section_toggle";
-import { Settings } from "./reducers/settings";
-import { SyncOptions } from "./reducers/sync";
-import { Themes } from "./reducers/themes";
-import { TrustedLinks } from "./reducers/trusted_links";
-import { Unreads } from "./reducers/unreads";
-
-export type State = {
- config: RevoltConfiguration;
- locale: Language;
- auth: AuthState;
- settings: Settings;
- unreads: Unreads;
- queue: QueuedMessage[];
- drafts: Drafts;
- sync: SyncOptions;
- experiments: ExperimentOptions;
- lastOpened: LastOpened;
- notifications: Notifications;
- sectionToggle: SectionToggle;
- trustedLinks: TrustedLinks;
- themes: Themes;
-};
-
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export const store = createStore((state: any, action: any) => {
- if (import.meta.env.DEV) {
- console.debug("State Update:", action);
- }
-
- if (action.type === "__INIT") {
- return action.state;
- }
-
- return rootReducer(state, action);
-});
-
-// Save state using localForage.
-store.subscribe(() => {
- const {
- config,
- locale,
- auth,
- settings,
- unreads,
- queue,
- drafts,
- sync,
- experiments,
- lastOpened,
- notifications,
- sectionToggle,
- trustedLinks,
- themes,
- } = store.getState() as State;
-
- localForage.setItem("state", {
- config,
- locale,
- auth,
- settings,
- unreads,
- queue,
- drafts,
- sync,
- experiments,
- lastOpened,
- notifications,
- sectionToggle,
- trustedLinks,
- themes,
- });
-});
-
-export function dispatch(action: Action) {
- store.dispatch(action);
-}
-
-export function getState(): State {
- return store.getState();
-}
diff --git a/src/redux/reducers/auth.ts b/src/redux/reducers/auth.ts
deleted file mode 100644
index 2bb2ce99..00000000
--- a/src/redux/reducers/auth.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Session } from "revolt-api/types/Auth";
-
-export interface AuthState {
- accounts: {
- [key: string]: {
- session: Session;
- };
- };
- active?: string;
-}
-
-export type AuthAction =
- | { type: undefined }
- | {
- type: "LOGIN";
- session: Session;
- }
- | {
- type: "LOGOUT";
- user_id?: string;
- };
-
-export function auth(
- state = { accounts: {} } as AuthState,
- action: AuthAction,
-): AuthState {
- switch (action.type) {
- case "LOGIN":
- return {
- accounts: {
- ...state.accounts,
- [action.session.user_id]: {
- session: action.session,
- },
- },
- active: action.session.user_id,
- };
- case "LOGOUT": {
- const accounts = Object.assign({}, state.accounts);
- action.user_id && delete accounts[action.user_id];
-
- return {
- accounts,
- };
- }
- default:
- return state;
- }
-}
diff --git a/src/redux/reducers/drafts.ts b/src/redux/reducers/drafts.ts
deleted file mode 100644
index 4f36a846..00000000
--- a/src/redux/reducers/drafts.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-export type Drafts = { [key: string]: string };
-
-export type DraftAction =
- | { type: undefined }
- | {
- type: "SET_DRAFT";
- channel: string;
- content: string;
- }
- | {
- type: "CLEAR_DRAFT";
- channel: string;
- }
- | {
- type: "RESET";
- };
-
-export function drafts(state: Drafts = {}, action: DraftAction): Drafts {
- switch (action.type) {
- case "SET_DRAFT":
- return {
- ...state,
- [action.channel]: action.content,
- };
- case "CLEAR_DRAFT": {
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const { [action.channel]: _, ...newState } = state;
- return newState;
- }
- case "RESET":
- return {};
- default:
- return state;
- }
-}
diff --git a/src/redux/reducers/experiments.ts b/src/redux/reducers/experiments.ts
deleted file mode 100644
index 7d081f86..00000000
--- a/src/redux/reducers/experiments.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { getState } from "..";
-
-export type Experiments = "search" | "theme_shop";
-
-export const AVAILABLE_EXPERIMENTS: Experiments[] = ["theme_shop"];
-
-export const EXPERIMENTS: {
- [key in Experiments]: { title: string; description: string };
-} = {
- search: {
- title: "Search",
- description: "Allows you to search for messages in channels.",
- },
- theme_shop: {
- title: "Theme Shop",
- description: "Allows you to access and set user submitted themes.",
- },
-};
-
-export interface ExperimentOptions {
- enabled?: Experiments[];
-}
-
-export type ExperimentsAction =
- | { type: undefined }
- | {
- type: "EXPERIMENTS_ENABLE";
- key: Experiments;
- }
- | {
- type: "EXPERIMENTS_DISABLE";
- key: Experiments;
- };
-
-export function experiments(
- state = {} as ExperimentOptions,
- action: ExperimentsAction,
-): ExperimentOptions {
- switch (action.type) {
- case "EXPERIMENTS_ENABLE":
- return {
- ...state,
- enabled: [
- ...(state.enabled ?? [])
- .filter((x) => AVAILABLE_EXPERIMENTS.includes(x))
- .filter((v) => v !== action.key),
- action.key,
- ],
- };
- case "EXPERIMENTS_DISABLE":
- return {
- ...state,
- enabled: state.enabled
- ?.filter((v) => v !== action.key)
- .filter((x) => AVAILABLE_EXPERIMENTS.includes(x)),
- };
- default:
- return state;
- }
-}
-
-export function isExperimentEnabled(
- name: Experiments,
- experiments: ExperimentOptions = getState().experiments,
-) {
- return experiments.enabled?.includes(name) ?? false;
-}
diff --git a/src/redux/reducers/index.ts b/src/redux/reducers/index.ts
deleted file mode 100644
index aa8be8f4..00000000
--- a/src/redux/reducers/index.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { combineReducers } from "redux";
-
-import { State } from "..";
-import { auth, AuthAction } from "./auth";
-import { drafts, DraftAction } from "./drafts";
-import { experiments, ExperimentsAction } from "./experiments";
-import { lastOpened, LastOpenedAction } from "./last_opened";
-import { locale, LocaleAction } from "./locale";
-import { notifications, NotificationsAction } from "./notifications";
-import { queue, QueueAction } from "./queue";
-import { sectionToggle, SectionToggleAction } from "./section_toggle";
-import { config, ConfigAction } from "./server_config";
-import { settings, SettingsAction } from "./settings";
-import { sync, SyncAction } from "./sync";
-import { themes, ThemesAction } from "./themes";
-import { trustedLinks, TrustedLinksAction } from "./trusted_links";
-import { unreads, UnreadsAction } from "./unreads";
-
-export default combineReducers({
- config,
- locale,
- auth,
- settings,
- unreads,
- queue,
- drafts,
- sync,
- experiments,
- lastOpened,
- notifications,
- sectionToggle,
- trustedLinks,
- themes,
-});
-
-export type Action =
- | ConfigAction
- | LocaleAction
- | AuthAction
- | SettingsAction
- | UnreadsAction
- | QueueAction
- | DraftAction
- | SyncAction
- | ExperimentsAction
- | LastOpenedAction
- | NotificationsAction
- | SectionToggleAction
- | TrustedLinksAction
- | ThemesAction
- | { type: "__INIT"; state: State };
diff --git a/src/redux/reducers/last_opened.ts b/src/redux/reducers/last_opened.ts
deleted file mode 100644
index e49c444e..00000000
--- a/src/redux/reducers/last_opened.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-export interface LastOpened {
- [key: string]: string;
-}
-
-export type LastOpenedAction =
- | { type: undefined }
- | {
- type: "LAST_OPENED_SET";
- parent: string;
- child: string;
- }
- | {
- type: "RESET";
- };
-
-export function lastOpened(
- state = {} as LastOpened,
- action: LastOpenedAction,
-): LastOpened {
- switch (action.type) {
- case "LAST_OPENED_SET": {
- return {
- ...state,
- [action.parent]: action.child,
- };
- }
- case "RESET":
- return {};
- default:
- return state;
- }
-}
diff --git a/src/redux/reducers/locale.ts b/src/redux/reducers/locale.ts
deleted file mode 100644
index 5e8e4ec0..00000000
--- a/src/redux/reducers/locale.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Language, Languages } from "../../context/Locale";
-
-import type { SyncUpdateAction } from "./sync";
-
-export type LocaleAction =
- | { type: undefined }
- | {
- type: "SET_LOCALE";
- locale: Language;
- }
- | SyncUpdateAction;
-
-export function findLanguage(lang?: string): Language {
- if (!lang) {
- if (typeof navigator === "undefined") {
- lang = Language.ENGLISH;
- } else {
- lang = navigator.language;
- }
- }
-
- const code = lang.replace("-", "_");
- const short = code.split("_")[0];
-
- const values = [];
- for (const key in Language) {
- const value = Language[key as keyof typeof Language];
-
- // Skip alternative/joke languages
- if (Languages[value].cat === "alt") continue;
-
- values.push(value);
- if (value.startsWith(code)) {
- return value as Language;
- }
- }
-
- for (const value of values.reverse()) {
- if (value.startsWith(short)) {
- return value as Language;
- }
- }
-
- return Language.ENGLISH;
-}
-
-export function locale(state = findLanguage(), action: LocaleAction): Language {
- switch (action.type) {
- case "SET_LOCALE":
- return action.locale;
- case "SYNC_UPDATE":
- return (action.update.locale?.[1] ?? state) as Language;
- default:
- return state;
- }
-}
diff --git a/src/redux/reducers/notifications.ts b/src/redux/reducers/notifications.ts
deleted file mode 100644
index 76e3679b..00000000
--- a/src/redux/reducers/notifications.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-import { Channel } from "revolt.js/dist/maps/Channels";
-import { Message } from "revolt.js/dist/maps/Messages";
-
-import type { SyncUpdateAction } from "./sync";
-
-export type NotificationState = "all" | "mention" | "none" | "muted";
-
-export type Notifications = {
- [key: string]: NotificationState;
-};
-
-export const DEFAULT_STATES: {
- [key in Channel["channel_type"]]: NotificationState;
-} = {
- SavedMessages: "all",
- DirectMessage: "all",
- Group: "all",
- TextChannel: "mention",
- VoiceChannel: "mention",
-};
-
-export function getNotificationState(
- notifications: Notifications,
- channel: Channel,
-) {
- return notifications[channel._id] ?? DEFAULT_STATES[channel.channel_type];
-}
-
-export function shouldNotify(
- state: NotificationState,
- message: Message,
- user_id: string,
-) {
- switch (state) {
- case "muted":
- case "none":
- return false;
- case "mention": {
- if (!message.mention_ids?.includes(user_id)) return false;
- }
- }
-
- return true;
-}
-
-export type NotificationsAction =
- | { type: undefined }
- | {
- type: "NOTIFICATIONS_SET";
- key: string;
- state: NotificationState;
- }
- | {
- type: "NOTIFICATIONS_REMOVE";
- key: string;
- }
- | SyncUpdateAction
- | {
- type: "RESET";
- };
-
-export function notifications(
- state = {} as Notifications,
- action: NotificationsAction,
-): Notifications {
- switch (action.type) {
- case "NOTIFICATIONS_SET":
- return {
- ...state,
- [action.key]: action.state,
- };
- case "NOTIFICATIONS_REMOVE": {
- const { [action.key]: _, ...newState } = state;
- return newState;
- }
- case "SYNC_UPDATE":
- return action.update.notifications?.[1] ?? state;
- case "RESET":
- return {};
- default:
- return state;
- }
-}
diff --git a/src/redux/reducers/queue.ts b/src/redux/reducers/queue.ts
deleted file mode 100644
index c4059e05..00000000
--- a/src/redux/reducers/queue.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-export enum QueueStatus {
- SENDING = "sending",
- ERRORED = "errored",
-}
-
-export interface Reply {
- id: string;
- mention: boolean;
-}
-
-export type QueuedMessageData = {
- _id: string;
- author: string;
- channel: string;
-
- content: string;
- replies: Reply[];
-};
-
-export interface QueuedMessage {
- id: string;
- channel: string;
- data: QueuedMessageData;
- status: QueueStatus;
- error?: string;
-}
-
-export type QueueAction =
- | { type: undefined }
- | {
- type: "QUEUE_ADD";
- nonce: string;
- channel: string;
- message: QueuedMessageData;
- }
- | {
- type: "QUEUE_FAIL";
- nonce: string;
- error: string;
- }
- | {
- type: "QUEUE_START";
- nonce: string;
- }
- | {
- type: "QUEUE_REMOVE";
- nonce: string;
- }
- | {
- type: "QUEUE_DROP_ALL";
- }
- | {
- type: "QUEUE_FAIL_ALL";
- }
- | {
- type: "RESET";
- };
-
-export function queue(
- state: QueuedMessage[] = [],
- action: QueueAction,
-): QueuedMessage[] {
- switch (action.type) {
- case "QUEUE_ADD": {
- return [
- ...state.filter((x) => x.id !== action.nonce),
- {
- id: action.nonce,
- data: action.message,
- channel: action.channel,
- status: QueueStatus.SENDING,
- },
- ];
- }
- case "QUEUE_FAIL": {
- const entry = state.find(
- (x) => x.id === action.nonce,
- ) as QueuedMessage;
- return [
- ...state.filter((x) => x.id !== action.nonce),
- {
- ...entry,
- status: QueueStatus.ERRORED,
- error: action.error,
- },
- ];
- }
- case "QUEUE_START": {
- const entry = state.find(
- (x) => x.id === action.nonce,
- ) as QueuedMessage;
- return [
- ...state.filter((x) => x.id !== action.nonce),
- {
- ...entry,
- status: QueueStatus.SENDING,
- },
- ];
- }
- case "QUEUE_REMOVE":
- return state.filter((x) => x.id !== action.nonce);
- case "QUEUE_FAIL_ALL":
- return state.map((x) => {
- return {
- ...x,
- status: QueueStatus.ERRORED,
- };
- });
- case "QUEUE_DROP_ALL":
- case "RESET":
- return [];
- default:
- return state;
- }
-}
diff --git a/src/redux/reducers/section_toggle.ts b/src/redux/reducers/section_toggle.ts
deleted file mode 100644
index cff440d0..00000000
--- a/src/redux/reducers/section_toggle.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-export interface SectionToggle {
- [key: string]: boolean;
-}
-
-export type SectionToggleAction =
- | { type: undefined }
- | {
- type: "SECTION_TOGGLE_SET";
- id: string;
- state: boolean;
- }
- | {
- type: "SECTION_TOGGLE_UNSET";
- id: string;
- }
- | {
- type: "RESET";
- };
-
-export function sectionToggle(
- state = {} as SectionToggle,
- action: SectionToggleAction,
-): SectionToggle {
- switch (action.type) {
- case "SECTION_TOGGLE_SET": {
- return {
- ...state,
- [action.id]: action.state,
- };
- }
- case "SECTION_TOGGLE_UNSET": {
- const { [action.id]: _, ...newState } = state;
- return newState;
- }
- case "RESET":
- return {};
- default:
- return state;
- }
-}
diff --git a/src/redux/reducers/server_config.ts b/src/redux/reducers/server_config.ts
deleted file mode 100644
index 916cb2d7..00000000
--- a/src/redux/reducers/server_config.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { RevoltConfiguration } from "revolt-api/types/Core";
-
-export type ConfigAction =
- | { type: undefined }
- | {
- type: "SET_CONFIG";
- config: RevoltConfiguration;
- };
-
-export function config(
- state = {} as RevoltConfiguration,
- action: ConfigAction,
-): RevoltConfiguration {
- switch (action.type) {
- case "SET_CONFIG":
- return action.config;
- default:
- return state;
- }
-}
diff --git a/src/redux/reducers/settings.ts b/src/redux/reducers/settings.ts
deleted file mode 100644
index 42248faa..00000000
--- a/src/redux/reducers/settings.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-import type { Theme, ThemeOptions } from "../../context/Theme";
-
-import { setGlobalEmojiPack } from "../../components/common/Emoji";
-
-import type { SyncUpdateAction } from "./sync";
-
-type Sounds = "message" | "outbound" | "call_join" | "call_leave";
-
-export type SoundOptions = {
- [key in Sounds]?: boolean;
-};
-
-export const DEFAULT_SOUNDS: SoundOptions = {
- message: true,
- outbound: false,
- call_join: true,
- call_leave: true,
-};
-
-export interface NotificationOptions {
- desktopEnabled?: boolean;
- sounds?: SoundOptions;
-}
-
-export type EmojiPacks = "mutant" | "twemoji" | "noto" | "openmoji";
-export interface AppearanceOptions {
- emojiPack?: EmojiPacks;
-}
-
-export interface Settings {
- theme?: ThemeOptions;
- appearance?: AppearanceOptions;
- notification?: NotificationOptions;
-}
-
-export type SettingsAction =
- | { type: undefined }
- | {
- type: "SETTINGS_SET_THEME";
- theme: ThemeOptions;
- }
- | {
- type: "SETTINGS_SET_THEME_OVERRIDE";
- custom?: Partial;
- }
- | {
- type: "SETTINGS_SET_NOTIFICATION_OPTIONS";
- options: NotificationOptions;
- }
- | {
- type: "SETTINGS_SET_APPEARANCE";
- options: Partial;
- }
- | SyncUpdateAction
- | {
- type: "RESET";
- };
-
-export function settings(
- state = {} as Settings,
- action: SettingsAction,
-): Settings {
- // setGlobalEmojiPack(state.appearance?.emojiPack ?? "mutant");
-
- switch (action.type) {
- case "SETTINGS_SET_THEME":
- return {
- ...state,
- theme: {
- ...state.theme,
- ...action.theme,
- },
- };
- case "SETTINGS_SET_THEME_OVERRIDE":
- return {
- ...state,
- theme: {
- ...state.theme,
- custom: {
- ...state.theme?.custom,
- ...action.custom,
- },
- },
- };
- case "SETTINGS_SET_NOTIFICATION_OPTIONS":
- return {
- ...state,
- notification: {
- ...state.notification,
- ...action.options,
- },
- };
- case "SETTINGS_SET_APPEARANCE":
- return {
- ...state,
- appearance: {
- ...state.appearance,
- ...action.options,
- },
- };
- case "SYNC_UPDATE":
- return {
- ...state,
- appearance: action.update.appearance?.[1] ?? state.appearance,
- theme: action.update.theme?.[1] ?? state.theme,
- };
- case "RESET":
- return {};
- default:
- return state;
- }
-}
diff --git a/src/redux/reducers/sync.ts b/src/redux/reducers/sync.ts
deleted file mode 100644
index e62c31c6..00000000
--- a/src/redux/reducers/sync.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import type { Language } from "../../context/Locale";
-import type { ThemeOptions } from "../../context/Theme";
-
-import type { Notifications } from "./notifications";
-import type { AppearanceOptions } from "./settings";
-
-export type SyncKeys = "theme" | "appearance" | "locale" | "notifications";
-
-export interface SyncData {
- locale?: Language;
- theme?: ThemeOptions;
- appearance?: AppearanceOptions;
- notifications?: Notifications;
-}
-
-export const DEFAULT_ENABLED_SYNC: SyncKeys[] = [
- "theme",
- "appearance",
- "locale",
- "notifications",
-];
-export interface SyncOptions {
- disabled?: SyncKeys[];
- revision?: {
- [key: string]: number;
- };
-}
-
-export type SyncUpdateAction = {
- type: "SYNC_UPDATE";
- update: { [key in SyncKeys]?: [number, SyncData[key]] };
-};
-
-export type SyncAction =
- | { type: undefined }
- | {
- type: "SYNC_ENABLE_KEY";
- key: SyncKeys;
- }
- | {
- type: "SYNC_DISABLE_KEY";
- key: SyncKeys;
- }
- | {
- type: "SYNC_SET_REVISION";
- key: SyncKeys;
- timestamp: number;
- }
- | SyncUpdateAction;
-
-export function sync(
- state = {} as SyncOptions,
- action: SyncAction,
-): SyncOptions {
- switch (action.type) {
- case "SYNC_DISABLE_KEY":
- return {
- ...state,
- disabled: [
- ...(state.disabled ?? []).filter((v) => v !== action.key),
- action.key,
- ],
- };
- case "SYNC_ENABLE_KEY":
- return {
- ...state,
- disabled: state.disabled?.filter((v) => v !== action.key),
- };
- case "SYNC_SET_REVISION":
- return {
- ...state,
- revision: {
- ...state.revision,
- [action.key]: action.timestamp,
- },
- };
- case "SYNC_UPDATE": {
- const revision = { ...state.revision };
- for (const key of Object.keys(action.update)) {
- const value = action.update[key as SyncKeys];
- if (value) {
- revision[key] = value[0];
- }
- }
-
- return {
- ...state,
- revision,
- };
- }
- default:
- return state;
- }
-}
diff --git a/src/redux/reducers/themes.ts b/src/redux/reducers/themes.ts
deleted file mode 100644
index 89b9c840..00000000
--- a/src/redux/reducers/themes.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Theme } from "../../context/Theme";
-
-import { ThemeMetadata } from "../../pages/settings/panes/ThemeShop";
-
-export interface StoredTheme {
- slug: string;
- meta: ThemeMetadata;
- theme: Theme;
-}
-
-export type Themes = Record;
-
-export type ThemesAction =
- | { type: undefined }
- | { type: "THEMES_SET_THEME"; theme: StoredTheme }
- | { type: "THEMES_REMOVE_THEME"; slug: string }
- | { type: "RESET" };
-
-export function themes(state: Themes = {}, action: ThemesAction) {
- switch (action.type) {
- case "THEMES_SET_THEME":
- return {
- ...state,
- [action.theme.slug]: action.theme,
- };
- case "THEMES_REMOVE_THEME":
- return { ...state, [action.slug]: null };
- case "RESET":
- return {};
- default:
- return state;
- }
-}
diff --git a/src/redux/reducers/trusted_links.ts b/src/redux/reducers/trusted_links.ts
deleted file mode 100644
index 4675b3cd..00000000
--- a/src/redux/reducers/trusted_links.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-export interface TrustedLinks {
- domains?: string[];
-}
-
-export type TrustedLinksAction =
- | { type: undefined }
- | {
- type: "TRUSTED_LINKS_ADD_DOMAIN";
- domain: string;
- }
- | {
- type: "TRUSTED_LINKS_REMOVE_DOMAIN";
- domain: string;
- };
-
-export function trustedLinks(
- state = {} as TrustedLinks,
- action: TrustedLinksAction,
-): TrustedLinks {
- switch (action.type) {
- case "TRUSTED_LINKS_ADD_DOMAIN":
- return {
- ...state,
- domains: [
- ...(state.domains ?? []).filter((v) => v !== action.domain),
- action.domain,
- ],
- };
- case "TRUSTED_LINKS_REMOVE_DOMAIN":
- return {
- ...state,
- domains: state.domains?.filter((v) => v !== action.domain),
- };
- default:
- return state;
- }
-}
diff --git a/src/redux/reducers/unreads.ts b/src/redux/reducers/unreads.ts
deleted file mode 100644
index e7f57886..00000000
--- a/src/redux/reducers/unreads.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import type { ChannelUnread } from "revolt-api/types/Sync";
-import { ulid } from "ulid";
-
-export interface Unreads {
- [key: string]: Partial>;
-}
-
-export type UnreadsAction =
- | { type: undefined }
- | {
- type: "UNREADS_MARK_READ";
- channel: string;
- message: string;
- }
- | {
- type: "UNREADS_MARK_MULTIPLE_READ";
- channels: string[];
- }
- | {
- type: "UNREADS_SET";
- unreads: ChannelUnread[];
- }
- | {
- type: "UNREADS_MENTION";
- channel: string;
- message: string;
- }
- | {
- type: "RESET";
- };
-
-export function unreads(state = {} as Unreads, action: UnreadsAction): Unreads {
- switch (action.type) {
- case "UNREADS_MARK_READ":
- return {
- ...state,
- [action.channel]: {
- last_id: action.message,
- },
- };
- case "UNREADS_MARK_MULTIPLE_READ": {
- const newState = { ...state };
- const last_id = ulid();
- for (const channel of action.channels) {
- newState[channel] = {
- last_id,
- };
- }
-
- return newState;
- }
- case "UNREADS_SET": {
- const obj: Unreads = {};
- for (const entry of action.unreads) {
- const { _id, ...v } = entry;
- obj[_id.channel] = v;
- }
-
- return obj;
- }
- case "UNREADS_MENTION": {
- const obj = state[action.channel];
-
- return {
- ...state,
- [action.channel]: {
- ...obj,
- mentions: [...(obj?.mentions ?? []), action.message],
- },
- };
- }
- case "RESET":
- return {};
- default:
- return state;
- }
-}
diff --git a/yarn.lock b/yarn.lock
index 44bc6454..ce022ce1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2463,10 +2463,10 @@ events@^3.3.0:
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
-exponential-backoff@^3.1.0:
+"exponential-backoff@npm:@insertish/exponential-backoff":
version "3.1.0"
- resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.0.tgz#9409c7e579131f8bd4b32d7d8094a911040f2e68"
- integrity sha512-oBuz5SYz5zzyuHINoe9ooePwSu0xApKWgeNzok4hZ5YKXFh9zrQBEM15CXqoZkJJPuI2ArvqjPQd8UKJA753XA==
+ resolved "https://registry.yarnpkg.com/@insertish/exponential-backoff/-/exponential-backoff-3.1.0.tgz#1d2e4c215fa8647779cfeab74ecb54a5c36835e6"
+ integrity sha512-8Jab9OfjheI84T04QjUwXceSO1DMGy8goDqVdnuoffC2fg23zBnikLJkrRHiT/ao4c08v4R2mU7+/DXMWmROng==
fake-mediastreamtrack@^1.1.6:
version "1.1.6"
@@ -2912,10 +2912,10 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
-isomorphic-ws@^4.0.1:
+"isomorphic-ws@npm:@insertish/isomorphic-ws":
version "4.0.1"
- resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc"
- integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==
+ resolved "https://registry.yarnpkg.com/@insertish/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#5bcd6f73b93efa9ccdb6abf887ae808d40827169"
+ integrity sha512-kFD/p8T4Hkqr992QrdkbW/cQ/W/q2d9MPCobwzBv2PwTKLkCD9RaYDy6m17qRnSLQQ5PU0kHCG8kaOwAqzj1vQ==
javascript-natural-sort@0.7.1:
version "0.7.1"
@@ -3576,18 +3576,6 @@ react-redux@^7.2.0:
prop-types "^15.7.2"
react-is "^16.13.1"
-react-redux@^7.2.4:
- version "7.2.4"
- resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.4.tgz#1ebb474032b72d806de2e0519cd07761e222e225"
- integrity sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==
- dependencies:
- "@babel/runtime" "^7.12.1"
- "@types/react-redux" "^7.1.16"
- hoist-non-react-statics "^3.3.2"
- loose-envify "^1.4.0"
- prop-types "^15.7.2"
- react-is "^16.13.1"
-
react-router-dom@^5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.1.tgz#34af8b551a4ce17487d3f80e651b91651978dff6"
@@ -3651,7 +3639,7 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
-redux@^4.0.0, redux@^4.0.4, redux@^4.1.0:
+redux@^4.0.0, redux@^4.0.4:
version "4.1.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.1.tgz#76f1c439bb42043f985fbd9bf21990e60bd67f47"
integrity sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==
@@ -3770,15 +3758,15 @@ 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.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==
+revolt.js@5.2.1-patch.1:
+ version "5.2.1-patch.1"
+ resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.2.1-patch.1.tgz#4b392d4dae12ea28f559ef89790368f53788c81d"
+ integrity sha512-u2vvbCWXKx+vZKqlt5izowf9XnMbWdh3GaPMzipek6l6mBYSCIlr796HoiiIO3c2T3AWqh3zav97rm8z3jOIXg==
dependencies:
axios "^0.21.4"
eventemitter3 "^4.0.7"
- exponential-backoff "^3.1.0"
- isomorphic-ws "^4.0.1"
+ exponential-backoff "npm:@insertish/exponential-backoff"
+ isomorphic-ws "npm:@insertish/isomorphic-ws"
lodash.defaultsdeep "^4.6.1"
lodash.flatten "^4.4.0"
lodash.isequal "^4.5.0"