mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-22 07:00:58 -05:00
feat(mobx): refactor and remove (react-)redux
This commit is contained in:
parent
6e1bcab92b
commit
cc0e45526c
55 changed files with 249 additions and 1522 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -10,3 +10,5 @@ dist-ssr
|
||||||
public/assets
|
public/assets
|
||||||
public/assets_*
|
public/assets_*
|
||||||
!public/assets_default
|
!public/assets_default
|
||||||
|
|
||||||
|
.vscode/vscode-chrome-debug-userdatadir
|
||||||
|
|
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -136,14 +136,12 @@
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-hook-form": "6.3.0",
|
"react-hook-form": "6.3.0",
|
||||||
"react-overlapping-panels": "1.2.2",
|
"react-overlapping-panels": "1.2.2",
|
||||||
"react-redux": "^7.2.4",
|
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scroll": "^1.8.2",
|
"react-scroll": "^1.8.2",
|
||||||
"react-virtualized-auto-sizer": "^1.0.5",
|
"react-virtualized-auto-sizer": "^1.0.5",
|
||||||
"react-virtuoso": "^1.10.4",
|
"react-virtuoso": "^1.10.4",
|
||||||
"redux": "^4.1.0",
|
|
||||||
"revolt-api": "0.5.3-alpha.10",
|
"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",
|
"rimraf": "^3.0.2",
|
||||||
"sass": "^1.35.1",
|
"sass": "^1.35.1",
|
||||||
"shade-blend-color": "^1.0.0",
|
"shade-blend-color": "^1.0.0",
|
||||||
|
|
|
@ -6,7 +6,8 @@ import styled from "styled-components";
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { useState } from "preact/hooks";
|
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 Button from "../ui/Button";
|
||||||
import Checkbox from "../ui/Checkbox";
|
import Checkbox from "../ui/Checkbox";
|
||||||
|
@ -49,9 +50,7 @@ type Props = {
|
||||||
|
|
||||||
export default observer((props: Props) => {
|
export default observer((props: Props) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [consent, setConsent] = useState(
|
const layout = useApplicationState().layout;
|
||||||
getState().sectionToggle["nsfw"] ?? false,
|
|
||||||
);
|
|
||||||
const [ageGate, setAgeGate] = useState(false);
|
const [ageGate, setAgeGate] = useState(false);
|
||||||
|
|
||||||
if (ageGate || !props.gated) {
|
if (ageGate || !props.gated) {
|
||||||
|
@ -81,26 +80,19 @@ export default observer((props: Props) => {
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={consent}
|
checked={layout.getSectionState(SECTION_NSFW, false)}
|
||||||
onChange={(v) => {
|
onChange={() => layout.toggleSectionState(SECTION_NSFW, false)}>
|
||||||
setConsent(v);
|
|
||||||
if (v) {
|
|
||||||
dispatch({
|
|
||||||
type: "SECTION_TOGGLE_SET",
|
|
||||||
id: "nsfw",
|
|
||||||
state: true,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
dispatch({ type: "SECTION_TOGGLE_UNSET", id: "nsfw" });
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<Text id="app.main.channel.nsfw.confirm" />
|
<Text id="app.main.channel.nsfw.confirm" />
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
<Button contrast onClick={() => history.goBack()}>
|
<Button contrast onClick={() => history.goBack()}>
|
||||||
<Text id="app.special.modals.actions.back" />
|
<Text id="app.special.modals.actions.back" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button contrast onClick={() => consent && setAgeGate(true)}>
|
<Button
|
||||||
|
contrast
|
||||||
|
onClick={() =>
|
||||||
|
layout.getSectionState(SECTION_NSFW) && setAgeGate(true)
|
||||||
|
}>
|
||||||
<Text id={`app.main.channel.nsfw.${props.type}.confirm`} />
|
<Text id={`app.main.channel.nsfw.${props.type}.confirm`} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { useState } from "preact/hooks";
|
||||||
|
|
||||||
import { internalEmit } from "../../../lib/eventEmitter";
|
import { internalEmit } from "../../../lib/eventEmitter";
|
||||||
|
|
||||||
import { QueuedMessage } from "../../../redux/reducers/queue";
|
import { QueuedMessage } from "../../../mobx/stores/MessageQueue";
|
||||||
|
|
||||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||||
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
|
@ -21,7 +21,7 @@ import {
|
||||||
} from "../../../lib/renderer/Singleton";
|
} from "../../../lib/renderer/Singleton";
|
||||||
|
|
||||||
import { useApplicationState } from "../../../mobx/State";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
import { Reply } from "../../../redux/reducers/queue";
|
import { Reply } from "../../../mobx/stores/MessageQueue";
|
||||||
|
|
||||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||||
import {
|
import {
|
||||||
|
@ -111,7 +111,7 @@ const Action = styled.div`
|
||||||
const RE_SED = new RegExp("^s/([^])*/([^])*$");
|
const RE_SED = new RegExp("^s/([^])*/([^])*$");
|
||||||
|
|
||||||
// ! FIXME: add to app config and load from app config
|
// ! 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) => {
|
export default observer(({ channel }: Props) => {
|
||||||
const state = useApplicationState();
|
const state = useApplicationState();
|
||||||
|
|
|
@ -10,8 +10,9 @@ import { StateUpdater, useEffect } from "preact/hooks";
|
||||||
|
|
||||||
import { internalSubscribe } from "../../../../lib/eventEmitter";
|
import { internalSubscribe } from "../../../../lib/eventEmitter";
|
||||||
|
|
||||||
import { dispatch, getState } from "../../../../redux";
|
import { useApplicationState } from "../../../../mobx/State";
|
||||||
import { Reply } from "../../../../redux/reducers/queue";
|
import { SECTION_MENTION } from "../../../../mobx/stores/Layout";
|
||||||
|
import { Reply } from "../../../../mobx/stores/MessageQueue";
|
||||||
|
|
||||||
import IconButton from "../../../ui/IconButton";
|
import IconButton from "../../../ui/IconButton";
|
||||||
|
|
||||||
|
@ -81,6 +82,7 @@ const Base = styled.div`
|
||||||
const MAX_REPLIES = 5;
|
const MAX_REPLIES = 5;
|
||||||
export default observer(({ channel, replies, setReplies }: Props) => {
|
export default observer(({ channel, replies, setReplies }: Props) => {
|
||||||
const client = channel.client;
|
const client = channel.client;
|
||||||
|
const layout = useApplicationState().layout;
|
||||||
|
|
||||||
// Event listener for adding new messages to reply bar.
|
// Event listener for adding new messages to reply bar.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -99,7 +101,7 @@ export default observer(({ channel, replies, setReplies }: Props) => {
|
||||||
mention:
|
mention:
|
||||||
message.author_id === client.user!._id
|
message.author_id === client.user!._id
|
||||||
? false
|
? false
|
||||||
: getState().sectionToggle.mention ?? false,
|
: layout.getSectionState("SECTION_MENTION", false),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -181,11 +183,11 @@ export default observer(({ channel, replies, setReplies }: Props) => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch({
|
layout.setSectionState(
|
||||||
type: "SECTION_TOGGLE_SET",
|
SECTION_MENTION,
|
||||||
id: "mention",
|
|
||||||
state,
|
state,
|
||||||
});
|
false,
|
||||||
|
);
|
||||||
}}>
|
}}>
|
||||||
<span class="toggle">
|
<span class="toggle">
|
||||||
<At size={15} />
|
<At size={15} />
|
||||||
|
|
|
@ -10,8 +10,6 @@ import { useContext, useEffect, useState } from "preact/hooks";
|
||||||
import { defer } from "../../../../lib/defer";
|
import { defer } from "../../../../lib/defer";
|
||||||
import { isTouchscreenDevice } from "../../../../lib/isTouchscreenDevice";
|
import { isTouchscreenDevice } from "../../../../lib/isTouchscreenDevice";
|
||||||
|
|
||||||
import { dispatch } from "../../../../redux";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AppContext,
|
AppContext,
|
||||||
ClientStatus,
|
ClientStatus,
|
||||||
|
@ -44,8 +42,7 @@ const EmbedInviteBase = styled.div`
|
||||||
> button {
|
> button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
`
|
`}
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EmbedInviteDetails = styled.div`
|
const EmbedInviteDetails = styled.div`
|
||||||
|
@ -55,8 +52,7 @@ const EmbedInviteDetails = styled.div`
|
||||||
isTouchscreenDevice &&
|
isTouchscreenDevice &&
|
||||||
css`
|
css`
|
||||||
width: calc(100% - 55px);
|
width: calc(100% - 55px);
|
||||||
`
|
`}
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EmbedInviteName = styled.div`
|
const EmbedInviteName = styled.div`
|
||||||
|
@ -74,11 +70,10 @@ type Props = {
|
||||||
code: string;
|
code: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function EmbedInvite(props: Props) {
|
export function EmbedInvite({ code }: Props) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
const status = useContext(StatusContext);
|
const status = useContext(StatusContext);
|
||||||
const code = props.code;
|
|
||||||
const [processing, setProcessing] = useState(false);
|
const [processing, setProcessing] = useState(false);
|
||||||
const [error, setError] = useState<string | undefined>(undefined);
|
const [error, setError] = useState<string | undefined>(undefined);
|
||||||
const [joinError, setJoinError] = useState<string | undefined>(undefined);
|
const [joinError, setJoinError] = useState<string | undefined>(undefined);
|
||||||
|
@ -124,7 +119,8 @@ export function EmbedInvite(props: Props) {
|
||||||
<EmbedInviteDetails>
|
<EmbedInviteDetails>
|
||||||
<EmbedInviteName>{invite.server_name}</EmbedInviteName>
|
<EmbedInviteName>{invite.server_name}</EmbedInviteName>
|
||||||
<EmbedInviteMemberCount>
|
<EmbedInviteMemberCount>
|
||||||
{invite.member_count.toLocaleString()} {invite.member_count === 1 ? "member" : "members"}
|
{invite.member_count.toLocaleString()}{" "}
|
||||||
|
{invite.member_count === 1 ? "member" : "members"}
|
||||||
</EmbedInviteMemberCount>
|
</EmbedInviteMemberCount>
|
||||||
</EmbedInviteDetails>
|
</EmbedInviteDetails>
|
||||||
{processing ? (
|
{processing ? (
|
||||||
|
@ -151,10 +147,9 @@ export function EmbedInvite(props: Props) {
|
||||||
|
|
||||||
defer(() => {
|
defer(() => {
|
||||||
if (server) {
|
if (server) {
|
||||||
dispatch({
|
client.unreads!.markMultipleRead(
|
||||||
type: "UNREADS_MARK_MULTIPLE_READ",
|
server.channel_ids,
|
||||||
channels: server.channel_ids,
|
);
|
||||||
});
|
|
||||||
|
|
||||||
history.push(
|
history.push(
|
||||||
`/server/${server._id}/channel/${invite.channel_id}`,
|
`/server/${server._id}/channel/${invite.channel_id}`,
|
||||||
|
@ -172,7 +167,9 @@ export function EmbedInvite(props: Props) {
|
||||||
setProcessing(false);
|
setProcessing(false);
|
||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
{client.servers.get(invite.server_id) ? "Joined" : "Join"}
|
{client.servers.get(invite.server_id)
|
||||||
|
? "Joined"
|
||||||
|
: "Join"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</EmbedInviteBase>
|
</EmbedInviteBase>
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import { Route, Switch } from "react-router";
|
import { Route, Switch } from "react-router";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../mobx/State";
|
||||||
|
import { SIDEBAR_CHANNELS } from "../../mobx/stores/Layout";
|
||||||
|
|
||||||
import SidebarBase from "./SidebarBase";
|
import SidebarBase from "./SidebarBase";
|
||||||
import HomeSidebar from "./left/HomeSidebar";
|
import HomeSidebar from "./left/HomeSidebar";
|
||||||
import ServerListSidebar from "./left/ServerListSidebar";
|
import ServerListSidebar from "./left/ServerListSidebar";
|
||||||
import ServerSidebar from "./left/ServerSidebar";
|
import ServerSidebar from "./left/ServerSidebar";
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { State } from "../../redux";
|
|
||||||
|
|
||||||
export default function LeftSidebar() {
|
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 (
|
return (
|
||||||
<SidebarBase>
|
<SidebarBase>
|
||||||
|
|
|
@ -11,7 +11,6 @@ import { internalEmit } from "../../../lib/eventEmitter";
|
||||||
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
|
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
|
||||||
|
|
||||||
import { useApplicationState } from "../../../mobx/State";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
import { connectState } from "../../../redux/connector";
|
|
||||||
|
|
||||||
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
|
|
|
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -167,7 +167,7 @@ export default function Modal(props: Props) {
|
||||||
isModalClosing = animateClose;
|
isModalClosing = animateClose;
|
||||||
const onClose = useCallback(() => {
|
const onClose = useCallback(() => {
|
||||||
setAnimateClose(true);
|
setAnimateClose(true);
|
||||||
setTimeout(() => props.onClose?.(), 2e2);
|
setTimeout(() => props.onClose!(), 2e2);
|
||||||
}, [setAnimateClose, props]);
|
}, [setAnimateClose, props]);
|
||||||
|
|
||||||
useEffect(() => internalSubscribe("Modal", "close", onClose), [onClose]);
|
useEffect(() => internalSubscribe("Modal", "close", onClose), [onClose]);
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { createGlobalStyle } from "styled-components";
|
||||||
import { useEffect } from "preact/hooks";
|
import { useEffect } from "preact/hooks";
|
||||||
|
|
||||||
import { useApplicationState } from "../mobx/State";
|
import { useApplicationState } from "../mobx/State";
|
||||||
import { getState } from "../redux";
|
|
||||||
|
|
||||||
export type Variables =
|
export type Variables =
|
||||||
| "accent"
|
| "accent"
|
||||||
|
@ -280,28 +279,6 @@ export const PRESETS: Record<string, Theme> = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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 keys = Object.keys(PRESETS.dark);
|
||||||
const GlobalTheme = createGlobalStyle<{ theme: Theme }>`
|
const GlobalTheme = createGlobalStyle<{ theme: Theme }>`
|
||||||
:root {
|
:root {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { BrowserRouter as Router } from "react-router-dom";
|
import { BrowserRouter as Router } from "react-router-dom";
|
||||||
|
|
||||||
import State from "../redux/State";
|
|
||||||
|
|
||||||
import { Children } from "../types/Preact";
|
import { Children } from "../types/Preact";
|
||||||
import Locale from "./Locale";
|
import Locale from "./Locale";
|
||||||
import Theme from "./Theme";
|
import Theme from "./Theme";
|
||||||
|
@ -15,14 +13,12 @@ import Client from "./revoltjs/RevoltClient";
|
||||||
export default function Context({ children }: { children: Children }) {
|
export default function Context({ children }: { children: Children }) {
|
||||||
return (
|
return (
|
||||||
<Router basename={import.meta.env.BASE_URL}>
|
<Router basename={import.meta.env.BASE_URL}>
|
||||||
<State>
|
|
||||||
<Locale>
|
<Locale>
|
||||||
<Intermediate>
|
<Intermediate>
|
||||||
<Client>{children}</Client>
|
<Client>{children}</Client>
|
||||||
</Intermediate>
|
</Intermediate>
|
||||||
</Locale>
|
</Locale>
|
||||||
<Theme />
|
<Theme />
|
||||||
</State>
|
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
|
|
||||||
import { useApplicationState } from "../../../mobx/State";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
import { dispatch } from "../../../redux";
|
|
||||||
|
|
||||||
import Modal from "../../../components/ui/Modal";
|
import Modal from "../../../components/ui/Modal";
|
||||||
|
|
||||||
|
|
|
@ -6,31 +6,28 @@ import { Message } from "revolt.js/dist/maps/Messages";
|
||||||
import { useContext, useEffect } from "preact/hooks";
|
import { useContext, useEffect } from "preact/hooks";
|
||||||
|
|
||||||
import { useApplicationState } from "../../mobx/State";
|
import { useApplicationState } from "../../mobx/State";
|
||||||
import { connectState } from "../../redux/connector";
|
|
||||||
import { QueuedMessage } from "../../redux/reducers/queue";
|
|
||||||
|
|
||||||
import { setGlobalEmojiPack } from "../../components/common/Emoji";
|
import { setGlobalEmojiPack } from "../../components/common/Emoji";
|
||||||
|
|
||||||
import { AppContext } from "./RevoltClient";
|
import { AppContext } from "./RevoltClient";
|
||||||
|
|
||||||
type Props = {
|
export default function StateMonitor() {
|
||||||
messages: QueuedMessage[];
|
|
||||||
};
|
|
||||||
|
|
||||||
function StateMonitor(props: Props) {
|
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
const state = useApplicationState();
|
const state = useApplicationState();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function add(msg: Message) {
|
function add(msg: Message) {
|
||||||
if (!msg.nonce) return;
|
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);
|
state.queue.remove(msg.nonce);
|
||||||
}
|
}
|
||||||
|
|
||||||
client.addListener("message", add);
|
client.addListener("message", add);
|
||||||
return () => client.removeListener("message", add);
|
return () => client.removeListener("message", add);
|
||||||
}, [client, props.messages]);
|
}, [client]);
|
||||||
|
|
||||||
// Set global emoji pack.
|
// Set global emoji pack.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -40,9 +37,3 @@ function StateMonitor(props: Props) {
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connectState(StateMonitor, (state) => {
|
|
||||||
return {
|
|
||||||
messages: [...state.queue],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,25 +1,6 @@
|
||||||
/**
|
/**
|
||||||
* This file monitors changes to settings and syncs them to the server.
|
* 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 = {
|
/*type Props = {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { ClientboundNotification } from "revolt.js/dist/websocket/notifications"
|
||||||
import { StateUpdater } from "preact/hooks";
|
import { StateUpdater } from "preact/hooks";
|
||||||
|
|
||||||
import Auth from "../../mobx/stores/Auth";
|
import Auth from "../../mobx/stores/Auth";
|
||||||
import { dispatch } from "../../redux";
|
|
||||||
|
|
||||||
import { ClientStatus } from "./RevoltClient";
|
import { ClientStatus } from "./RevoltClient";
|
||||||
|
|
||||||
|
@ -46,29 +45,6 @@ export function registerEvents(
|
||||||
attemptReconnect();
|
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),
|
ready: () => setStatus(ClientStatus.ONLINE),
|
||||||
|
|
||||||
logout: () => {
|
logout: () => {
|
||||||
|
|
|
@ -33,14 +33,8 @@ import { Text } from "preact-i18n";
|
||||||
import { useContext } from "preact/hooks";
|
import { useContext } from "preact/hooks";
|
||||||
|
|
||||||
import { useApplicationState } from "../mobx/State";
|
import { useApplicationState } from "../mobx/State";
|
||||||
import { dispatch } from "../redux";
|
import { QueuedMessage } from "../mobx/stores/MessageQueue";
|
||||||
import { connectState } from "../redux/connector";
|
import { NotificationState } from "../mobx/stores/NotificationOptions";
|
||||||
import {
|
|
||||||
getNotificationState,
|
|
||||||
Notifications,
|
|
||||||
NotificationState,
|
|
||||||
} from "../redux/reducers/notifications";
|
|
||||||
import { QueuedMessage } from "../redux/reducers/queue";
|
|
||||||
|
|
||||||
import { Screen, useIntermediate } from "../context/intermediate/Intermediate";
|
import { Screen, useIntermediate } from "../context/intermediate/Intermediate";
|
||||||
import {
|
import {
|
||||||
|
@ -174,21 +168,19 @@ export default function ContextMenus() {
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
dispatch({
|
client.unreads!.markRead(
|
||||||
type: "UNREADS_MARK_READ",
|
data.channel._id,
|
||||||
channel: data.channel._id,
|
data.channel.last_message_id!,
|
||||||
message: data.channel.last_message_id!,
|
true,
|
||||||
});
|
true,
|
||||||
|
);
|
||||||
data.channel.ack(undefined, true);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "mark_server_as_read":
|
case "mark_server_as_read":
|
||||||
{
|
{
|
||||||
dispatch({
|
client.unreads!.markMultipleRead(
|
||||||
type: "UNREADS_MARK_MULTIPLE_READ",
|
data.server.channel_ids,
|
||||||
channels: data.server.channel_ids,
|
);
|
||||||
});
|
|
||||||
|
|
||||||
data.server.ack();
|
data.server.ack();
|
||||||
}
|
}
|
||||||
|
@ -439,16 +431,6 @@ export default function ContextMenus() {
|
||||||
case "open_server_settings":
|
case "open_server_settings":
|
||||||
history.push(`/server/${data.id}/settings`);
|
history.push(`/server/${data.id}/settings`);
|
||||||
break;
|
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) => {
|
})().catch((err) => {
|
||||||
openScreen({ id: "error", error: takeError(err) });
|
openScreen({ id: "error", error: takeError(err) });
|
||||||
|
|
|
@ -14,6 +14,7 @@ import MessageQueue from "./stores/MessageQueue";
|
||||||
import NotificationOptions from "./stores/NotificationOptions";
|
import NotificationOptions from "./stores/NotificationOptions";
|
||||||
import ServerConfig from "./stores/ServerConfig";
|
import ServerConfig from "./stores/ServerConfig";
|
||||||
import Settings from "./stores/Settings";
|
import Settings from "./stores/Settings";
|
||||||
|
import Sync from "./stores/Sync";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles global application state.
|
* Handles global application state.
|
||||||
|
@ -28,6 +29,7 @@ export default class State {
|
||||||
notifications: NotificationOptions;
|
notifications: NotificationOptions;
|
||||||
queue: MessageQueue;
|
queue: MessageQueue;
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
|
sync: Sync;
|
||||||
|
|
||||||
private persistent: [string, Persistent<unknown>][] = [];
|
private persistent: [string, Persistent<unknown>][] = [];
|
||||||
|
|
||||||
|
@ -44,6 +46,7 @@ export default class State {
|
||||||
this.notifications = new NotificationOptions();
|
this.notifications = new NotificationOptions();
|
||||||
this.queue = new MessageQueue();
|
this.queue = new MessageQueue();
|
||||||
this.settings = new Settings();
|
this.settings = new Settings();
|
||||||
|
this.sync = new Sync();
|
||||||
|
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
this.registerListeners = this.registerListeners.bind(this);
|
this.registerListeners = this.registerListeners.bind(this);
|
||||||
|
@ -116,14 +119,25 @@ export default class State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const StateContext = createContext<State>(null!);
|
var state: State;
|
||||||
|
|
||||||
export const StateContextProvider = StateContext.Provider;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the application state
|
* Get the application state
|
||||||
* @returns Application state
|
* @returns Application state
|
||||||
*/
|
*/
|
||||||
export function useApplicationState() {
|
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));
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
|
@ -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
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AuthState } from "../../redux/reducers/auth";
|
import { Session } from "revolt-api/types/Auth";
|
||||||
|
|
||||||
import { Language } from "../../context/Locale";
|
import { Language } from "../../context/Locale";
|
||||||
import { Fonts, MonospaceFonts, Overrides } from "../../context/Theme";
|
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 DataLocaleOptions } from "../stores/LocaleOptions";
|
||||||
import { Data as DataNotificationOptions } from "../stores/NotificationOptions";
|
import { Data as DataNotificationOptions } from "../stores/NotificationOptions";
|
||||||
import { ISettings } from "../stores/Settings";
|
import { ISettings } from "../stores/Settings";
|
||||||
|
import { Data as DataSync } from "../stores/Sync";
|
||||||
|
|
||||||
export type LegacyTheme = Overrides & {
|
export type LegacyTheme = Overrides & {
|
||||||
light?: boolean;
|
light?: boolean;
|
||||||
|
@ -39,7 +40,29 @@ export interface LegacySyncData {
|
||||||
notifications?: LegacyNotifications;
|
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 {
|
return {
|
||||||
current: auth.active,
|
current: auth.active,
|
||||||
sessions: auth.accounts,
|
sessions: auth.accounts,
|
||||||
|
@ -82,3 +105,12 @@ function legacyMigrateNotification(
|
||||||
channel,
|
channel,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function legacyMigrateSync(sync: LegacySyncOptions): DataSync {
|
||||||
|
return {
|
||||||
|
disabled: sync.disabled ?? [],
|
||||||
|
revision: {
|
||||||
|
...sync.revision,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,11 @@ export interface Data {
|
||||||
openSections?: Record<string, boolean>;
|
openSections?: Record<string, boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
* Keeps track of the last open channels, tabs, etc.
|
||||||
* Handles providing good UX experience on navigating
|
* Handles providing good UX experience on navigating
|
||||||
|
@ -165,4 +170,13 @@ export default class Layout implements Store, Persistent<Data> {
|
||||||
this.openSections.set(id, value);
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,10 +78,14 @@ export default class Settings implements Store, Persistent<ISettings> {
|
||||||
/**
|
/**
|
||||||
* Get a settings key.
|
* Get a settings key.
|
||||||
* @param key Colon-divided key
|
* @param key Colon-divided key
|
||||||
|
* @param defaultValue Default value if not present
|
||||||
* @returns Value at key
|
* @returns Value at key
|
||||||
*/
|
*/
|
||||||
@computed get<T extends keyof ISettings>(key: T) {
|
@computed get<T extends keyof ISettings>(
|
||||||
return this.data.get(key) as ISettings[T] | undefined;
|
key: T,
|
||||||
|
defaultValue?: ISettings[T],
|
||||||
|
) {
|
||||||
|
return (this.data.get(key) as ISettings[T] | undefined) ?? defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action remove<T extends keyof ISettings>(key: T) {
|
@action remove<T extends keyof ISettings>(key: T) {
|
||||||
|
|
|
@ -23,6 +23,9 @@ export const SYNC_KEYS: SyncKeys[] = [
|
||||||
|
|
||||||
export interface Data {
|
export interface Data {
|
||||||
disabled: SyncKeys[];
|
disabled: SyncKeys[];
|
||||||
|
revision: {
|
||||||
|
[key: string]: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,12 +33,14 @@ export interface Data {
|
||||||
*/
|
*/
|
||||||
export default class Sync implements Store, Persistent<Data> {
|
export default class Sync implements Store, Persistent<Data> {
|
||||||
private disabled: ObservableSet<SyncKeys>;
|
private disabled: ObservableSet<SyncKeys>;
|
||||||
|
private revision: ObservableMap<SyncKeys, number>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct new Sync store.
|
* Construct new Sync store.
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
this.disabled = new ObservableSet();
|
this.disabled = new ObservableSet();
|
||||||
|
this.revision = new ObservableMap();
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
this.isEnabled = this.isEnabled.bind(this);
|
this.isEnabled = this.isEnabled.bind(this);
|
||||||
}
|
}
|
||||||
|
@ -47,6 +52,7 @@ export default class Sync implements Store, Persistent<Data> {
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
enabled: [...this.disabled],
|
enabled: [...this.disabled],
|
||||||
|
revision: mapToRecord(this.revision),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +64,22 @@ export default class Sync implements Store, Persistent<Data> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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) {
|
@computed isEnabled(key: SyncKeys) {
|
||||||
return !this.disabled.has(key);
|
return !this.disabled.has(key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,12 @@ import { Channel as ChannelI } from "revolt.js/dist/maps/Channels";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect } from "preact/hooks";
|
||||||
|
|
||||||
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
|
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";
|
import { useClient } from "../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
|
@ -83,15 +84,8 @@ export function Channel({ id }: { id: string }) {
|
||||||
return <TextChannel channel={channel} />;
|
return <TextChannel channel={channel} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MEMBERS_SIDEBAR_KEY = "sidebar_members";
|
|
||||||
const CHANNELS_SIDEBAR_KEY = "sidebar_channels";
|
|
||||||
const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
|
const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
|
||||||
const [showMembers, setMembers] = useState(
|
const layout = useApplicationState().layout;
|
||||||
getState().sectionToggle[MEMBERS_SIDEBAR_KEY] ?? true,
|
|
||||||
);
|
|
||||||
const [showChannels, setChannels] = useState(
|
|
||||||
getState().sectionToggle[CHANNELS_SIDEBAR_KEY] ?? true,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Mark channel as read.
|
// Mark channel as read.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -121,45 +115,7 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
|
||||||
channel.nsfw
|
channel.nsfw
|
||||||
)
|
)
|
||||||
}>
|
}>
|
||||||
<ChannelHeader
|
<ChannelHeader channel={channel} />
|
||||||
channel={channel}
|
|
||||||
toggleSidebar={() => {
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ChannelMain>
|
<ChannelMain>
|
||||||
<ChannelContent>
|
<ChannelContent>
|
||||||
<VoiceHeader id={channel._id} />
|
<VoiceHeader id={channel._id} />
|
||||||
|
@ -168,7 +124,10 @@ const TextChannel = observer(({ channel }: { channel: ChannelI }) => {
|
||||||
<JumpToBottom channel={channel} />
|
<JumpToBottom channel={channel} />
|
||||||
<MessageBox channel={channel} />
|
<MessageBox channel={channel} />
|
||||||
</ChannelContent>
|
</ChannelContent>
|
||||||
{!isTouchscreenDevice && showMembers && <RightSidebar />}
|
{!isTouchscreenDevice &&
|
||||||
|
layout.getSectionState(SIDEBAR_MEMBERS, true) && (
|
||||||
|
<RightSidebar />
|
||||||
|
)}
|
||||||
</ChannelMain>
|
</ChannelMain>
|
||||||
</AgeGate>
|
</AgeGate>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 { Notepad, Group } from "@styled-icons/boxicons-solid";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Channel } from "revolt.js/dist/maps/Channels";
|
import { Channel } from "revolt.js/dist/maps/Channels";
|
||||||
|
@ -7,6 +7,9 @@ import styled, { css } from "styled-components";
|
||||||
|
|
||||||
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
|
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../mobx/State";
|
||||||
|
import { SIDEBAR_MEMBERS } from "../../mobx/stores/Layout";
|
||||||
|
|
||||||
import { useIntermediate } from "../../context/intermediate/Intermediate";
|
import { useIntermediate } from "../../context/intermediate/Intermediate";
|
||||||
import { getChannelName } from "../../context/revoltjs/util";
|
import { getChannelName } from "../../context/revoltjs/util";
|
||||||
|
|
||||||
|
@ -69,14 +72,16 @@ const IconConainer = styled.div`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--secondary-foreground);
|
color: var(--secondary-foreground);
|
||||||
|
|
||||||
${!isTouchscreenDevice && css`
|
${!isTouchscreenDevice &&
|
||||||
|
css`
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
`
|
`;
|
||||||
|
|
||||||
export default observer(({ channel, toggleSidebar, toggleChannelSidebar }: ChannelHeaderProps) => {
|
export default observer(({ channel }: ChannelHeaderProps) => {
|
||||||
|
const layout = useApplicationState().layout;
|
||||||
const { openScreen } = useIntermediate();
|
const { openScreen } = useIntermediate();
|
||||||
|
|
||||||
const name = getChannelName(channel);
|
const name = getChannelName(channel);
|
||||||
|
@ -100,7 +105,12 @@ export default observer(({ channel, toggleSidebar, toggleChannelSidebar }: Chann
|
||||||
return (
|
return (
|
||||||
<Header placement="primary">
|
<Header placement="primary">
|
||||||
<HamburgerAction />
|
<HamburgerAction />
|
||||||
<IconConainer onClick={toggleChannelSidebar}>{icon}</IconConainer>
|
<IconConainer
|
||||||
|
onClick={() =>
|
||||||
|
layout.toggleSectionState(SIDEBAR_MEMBERS, true)
|
||||||
|
}>
|
||||||
|
{icon}
|
||||||
|
</IconConainer>
|
||||||
<Info>
|
<Info>
|
||||||
<span className="name">{name}</span>
|
<span className="name">{name}</span>
|
||||||
{isTouchscreenDevice &&
|
{isTouchscreenDevice &&
|
||||||
|
@ -143,7 +153,7 @@ export default observer(({ channel, toggleSidebar, toggleChannelSidebar }: Chann
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Info>
|
</Info>
|
||||||
<HeaderActions channel={channel} toggleSidebar={toggleSidebar} />
|
<HeaderActions channel={channel} />
|
||||||
</Header>
|
</Header>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,9 @@ import { internalEmit } from "../../../lib/eventEmitter";
|
||||||
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
|
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
|
||||||
import { voiceState, VoiceStatus } from "../../../lib/vortex/VoiceState";
|
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 { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||||
|
|
||||||
import UpdateIndicator from "../../../components/common/UpdateIndicator";
|
import UpdateIndicator from "../../../components/common/UpdateIndicator";
|
||||||
|
@ -21,10 +24,8 @@ import IconButton from "../../../components/ui/IconButton";
|
||||||
|
|
||||||
import { ChannelHeaderProps } from "../ChannelHeader";
|
import { ChannelHeaderProps } from "../ChannelHeader";
|
||||||
|
|
||||||
export default function HeaderActions({
|
export default function HeaderActions({ channel }: ChannelHeaderProps) {
|
||||||
channel,
|
const layout = useApplicationState().layout;
|
||||||
toggleSidebar,
|
|
||||||
}: ChannelHeaderProps) {
|
|
||||||
const { openScreen } = useIntermediate();
|
const { openScreen } = useIntermediate();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ export default function HeaderActions({
|
||||||
if (isTouchscreenDevice) {
|
if (isTouchscreenDevice) {
|
||||||
openRightSidebar();
|
openRightSidebar();
|
||||||
} else {
|
} else {
|
||||||
toggleSidebar?.();
|
layout.toggleSectionState(SIDEBAR_MEMBERS, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,12 @@ import styled from "styled-components";
|
||||||
import { decodeTime } from "ulid";
|
import { decodeTime } from "ulid";
|
||||||
|
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { memo } from "preact/compat";
|
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
import { internalSubscribe, internalEmit } from "../../../lib/eventEmitter";
|
import { internalSubscribe, internalEmit } from "../../../lib/eventEmitter";
|
||||||
import { ChannelRenderer } from "../../../lib/renderer/Singleton";
|
import { ChannelRenderer } from "../../../lib/renderer/Singleton";
|
||||||
|
|
||||||
import { useApplicationState } from "../../../mobx/State";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
import { connectState } from "../../../redux/connector";
|
|
||||||
import { QueuedMessage } from "../../../redux/reducers/queue";
|
|
||||||
|
|
||||||
import RequiresOnline from "../../../context/revoltjs/RequiresOnline";
|
import RequiresOnline from "../../../context/revoltjs/RequiresOnline";
|
||||||
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
|
@ -4,11 +4,12 @@ import styled, { css } from "styled-components";
|
||||||
|
|
||||||
import styles from "./Home.module.scss";
|
import styles from "./Home.module.scss";
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { useContext, useState } from "preact/hooks";
|
import { useContext } from "preact/hooks";
|
||||||
|
|
||||||
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
|
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";
|
import { AppContext } from "../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
|
@ -18,8 +19,6 @@ import Tooltip from "../../components/common/Tooltip";
|
||||||
import Header from "../../components/ui/Header";
|
import Header from "../../components/ui/Header";
|
||||||
import CategoryButton from "../../components/ui/fluent/CategoryButton";
|
import CategoryButton from "../../components/ui/fluent/CategoryButton";
|
||||||
|
|
||||||
const CHANNELS_SIDEBAR_KEY = "sidebar_channels";
|
|
||||||
|
|
||||||
const IconConainer = styled.div`
|
const IconConainer = styled.div`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--secondary-foreground);
|
color: var(--secondary-foreground);
|
||||||
|
@ -34,29 +33,14 @@ const IconConainer = styled.div`
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
const [showChannels, setChannels] = useState(
|
const layout = useApplicationState().layout;
|
||||||
getState().sectionToggle[CHANNELS_SIDEBAR_KEY] ?? true,
|
|
||||||
);
|
|
||||||
|
|
||||||
const toggleChannelSidebar = () => {
|
const toggleChannelSidebar = () => {
|
||||||
if (isTouchscreenDevice) {
|
if (isTouchscreenDevice) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setChannels(!showChannels);
|
layout.toggleSectionState(SIDEBAR_CHANNELS, true);
|
||||||
|
|
||||||
if (showChannels) {
|
|
||||||
dispatch({
|
|
||||||
type: "SECTION_TOGGLE_SET",
|
|
||||||
id: CHANNELS_SIDEBAR_KEY,
|
|
||||||
state: false,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
dispatch({
|
|
||||||
type: "SECTION_TOGGLE_UNSET",
|
|
||||||
id: CHANNELS_SIDEBAR_KEY,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -10,8 +10,6 @@ import { useContext, useEffect, useState } from "preact/hooks";
|
||||||
import { defer } from "../../lib/defer";
|
import { defer } from "../../lib/defer";
|
||||||
import { TextReact } from "../../lib/i18n";
|
import { TextReact } from "../../lib/i18n";
|
||||||
|
|
||||||
import { dispatch } from "../../redux";
|
|
||||||
|
|
||||||
import RequiresOnline from "../../context/revoltjs/RequiresOnline";
|
import RequiresOnline from "../../context/revoltjs/RequiresOnline";
|
||||||
import {
|
import {
|
||||||
AppContext,
|
AppContext,
|
||||||
|
@ -168,11 +166,9 @@ export default function Invite() {
|
||||||
|
|
||||||
defer(() => {
|
defer(() => {
|
||||||
if (server) {
|
if (server) {
|
||||||
dispatch({
|
client.unreads!.markMultipleRead(
|
||||||
type: "UNREADS_MARK_MULTIPLE_READ",
|
|
||||||
channels:
|
|
||||||
server.channel_ids,
|
server.channel_ids,
|
||||||
});
|
);
|
||||||
|
|
||||||
history.push(
|
history.push(
|
||||||
`/server/${server._id}/channel/${invite.channel_id}`,
|
`/server/${server._id}/channel/${invite.channel_id}`,
|
||||||
|
|
|
@ -6,8 +6,6 @@ import { TextReact } from "../../../lib/i18n";
|
||||||
import { stopPropagation } from "../../../lib/stopPropagation";
|
import { stopPropagation } from "../../../lib/stopPropagation";
|
||||||
import { voiceState } from "../../../lib/vortex/VoiceState";
|
import { voiceState } from "../../../lib/vortex/VoiceState";
|
||||||
|
|
||||||
import { connectState } from "../../../redux/connector";
|
|
||||||
|
|
||||||
import Button from "../../../components/ui/Button";
|
import Button from "../../../components/ui/Button";
|
||||||
import ComboBox from "../../../components/ui/ComboBox";
|
import ComboBox from "../../../components/ui/ComboBox";
|
||||||
import Overline from "../../../components/ui/Overline";
|
import Overline from "../../../components/ui/Overline";
|
||||||
|
@ -17,7 +15,7 @@ const constraints = { audio: true };
|
||||||
|
|
||||||
// TODO: do not rewrite this code until voice is rewritten!
|
// TODO: do not rewrite this code until voice is rewritten!
|
||||||
|
|
||||||
export function Component() {
|
export function Audio() {
|
||||||
const [mediaStream, setMediaStream] = useState<MediaStream | undefined>(
|
const [mediaStream, setMediaStream] = useState<MediaStream | undefined>(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
@ -163,7 +161,3 @@ function changeAudioDevice(deviceId: string, deviceType: string) {
|
||||||
window.localStorage.setItem("audioOutputDevice", deviceId);
|
window.localStorage.setItem("audioOutputDevice", deviceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Audio = connectState(Component, () => {
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
|
@ -4,12 +4,7 @@ import styles from "./Panes.module.scss";
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import { useMemo } from "preact/hooks";
|
import { useMemo } from "preact/hooks";
|
||||||
|
|
||||||
import PaintCounter from "../../../lib/PaintCounter";
|
|
||||||
|
|
||||||
import { useApplicationState } from "../../../mobx/State";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
import LocaleOptions from "../../../mobx/stores/LocaleOptions";
|
|
||||||
import { dispatch } from "../../../redux";
|
|
||||||
import { connectState } from "../../../redux/connector";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Language,
|
Language,
|
||||||
|
|
|
@ -5,23 +5,16 @@ import { useContext, useEffect, useState } from "preact/hooks";
|
||||||
import { urlBase64ToUint8Array } from "../../../lib/conversion";
|
import { urlBase64ToUint8Array } from "../../../lib/conversion";
|
||||||
|
|
||||||
import { useApplicationState } from "../../../mobx/State";
|
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 { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||||
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
import { AppContext } from "../../../context/revoltjs/RevoltClient";
|
||||||
|
|
||||||
import Checkbox from "../../../components/ui/Checkbox";
|
import Checkbox from "../../../components/ui/Checkbox";
|
||||||
|
|
||||||
interface Props {
|
export function Notifications() {
|
||||||
options?: NotificationOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Component({ options }: Props) {
|
|
||||||
const client = useContext(AppContext);
|
const client = useContext(AppContext);
|
||||||
const { openScreen } = useIntermediate();
|
const { openScreen } = useIntermediate();
|
||||||
const sounds = useApplicationState().settings.sounds;
|
const settings = useApplicationState().settings;
|
||||||
const [pushEnabled, setPushEnabled] = useState<undefined | boolean>(
|
const [pushEnabled, setPushEnabled] = useState<undefined | boolean>(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
@ -43,7 +36,7 @@ export function Component({ options }: Props) {
|
||||||
</h3>
|
</h3>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
disabled={!("Notification" in window)}
|
disabled={!("Notification" in window)}
|
||||||
checked={options?.desktopEnabled ?? false}
|
checked={settings.get("notifications:desktop", false)!}
|
||||||
description={
|
description={
|
||||||
<Text id="app.settings.pages.notifications.descriptions.enable_desktop" />
|
<Text id="app.settings.pages.notifications.descriptions.enable_desktop" />
|
||||||
}
|
}
|
||||||
|
@ -51,6 +44,7 @@ export function Component({ options }: Props) {
|
||||||
if (desktopEnabled) {
|
if (desktopEnabled) {
|
||||||
const permission =
|
const permission =
|
||||||
await Notification.requestPermission();
|
await Notification.requestPermission();
|
||||||
|
|
||||||
if (permission !== "granted") {
|
if (permission !== "granted") {
|
||||||
return openScreen({
|
return openScreen({
|
||||||
id: "error",
|
id: "error",
|
||||||
|
@ -59,10 +53,7 @@ export function Component({ options }: Props) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
settings.set("notifications:desktop", desktopEnabled);
|
||||||
type: "SETTINGS_SET_NOTIFICATION_OPTIONS",
|
|
||||||
options: { desktopEnabled },
|
|
||||||
});
|
|
||||||
}}>
|
}}>
|
||||||
<Text id="app.settings.pages.notifications.enable_desktop" />
|
<Text id="app.settings.pages.notifications.enable_desktop" />
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
@ -115,20 +106,16 @@ export function Component({ options }: Props) {
|
||||||
<h3>
|
<h3>
|
||||||
<Text id="app.settings.pages.notifications.sounds" />
|
<Text id="app.settings.pages.notifications.sounds" />
|
||||||
</h3>
|
</h3>
|
||||||
{sounds.getState().map(({ id, enabled }) => (
|
{settings.sounds.getState().map(({ id, enabled }) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
key={id}
|
key={id}
|
||||||
checked={enabled}
|
checked={enabled}
|
||||||
onChange={(enabled) => sounds.setEnabled(id, enabled)}>
|
onChange={(enabled) =>
|
||||||
|
settings.sounds.setEnabled(id, enabled)
|
||||||
|
}>
|
||||||
<Text id={`app.settings.pages.notifications.sound.${id}`} />
|
<Text id={`app.settings.pages.notifications.sound.${id}`} />
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Notifications = connectState(Component, (state) => {
|
|
||||||
return {
|
|
||||||
options: state.settings.notification,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
import styles from "./Panes.module.scss";
|
import styles from "./Panes.module.scss";
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
|
|
||||||
import { dispatch } from "../../../redux";
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
import { connectState } from "../../../redux/connector";
|
import { SyncKeys } from "../../../mobx/stores/Sync";
|
||||||
import { SyncKeys, SyncOptions } from "../../../redux/reducers/sync";
|
|
||||||
|
|
||||||
import Checkbox from "../../../components/ui/Checkbox";
|
import Checkbox from "../../../components/ui/Checkbox";
|
||||||
|
|
||||||
interface Props {
|
export const Sync = observer(() => {
|
||||||
options?: SyncOptions;
|
const sync = useApplicationState().sync;
|
||||||
}
|
|
||||||
|
|
||||||
export function Component(props: Props) {
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.notifications}>
|
<div className={styles.notifications}>
|
||||||
<h3>
|
<h3>
|
||||||
|
@ -27,31 +26,16 @@ export function Component(props: Props) {
|
||||||
).map(([key, title]) => (
|
).map(([key, title]) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
key={key}
|
key={key}
|
||||||
checked={
|
checked={sync.isEnabled(key)}
|
||||||
(props.options?.disabled ?? []).indexOf(key) === -1
|
|
||||||
}
|
|
||||||
description={
|
description={
|
||||||
<Text
|
<Text
|
||||||
id={`app.settings.pages.sync.descriptions.${key}`}
|
id={`app.settings.pages.sync.descriptions.${key}`}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
onChange={(enabled) =>
|
onChange={() => sync.toggle(key)}>
|
||||||
dispatch({
|
|
||||||
type: enabled
|
|
||||||
? "SYNC_ENABLE_KEY"
|
|
||||||
: "SYNC_DISABLE_KEY",
|
|
||||||
key,
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
<Text id={`app.settings.pages.${title}`} />
|
<Text id={`app.settings.pages.${title}`} />
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
export const Sync = connectState(Component, (state) => {
|
|
||||||
return {
|
|
||||||
options: state.sync,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,9 +3,8 @@ import styled from "styled-components";
|
||||||
import { useEffect, useState } from "preact/hooks";
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
import { useApplicationState } from "../../../mobx/State";
|
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 Tip from "../../../components/ui/Tip";
|
||||||
import previewPath from "../assets/preview.svg";
|
import previewPath from "../assets/preview.svg";
|
||||||
|
|
|
@ -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<string, unknown>).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 (
|
|
||||||
<Provider store={store}>
|
|
||||||
<StateContextProvider value={state}>
|
|
||||||
{props.children}
|
|
||||||
</StateContextProvider>
|
|
||||||
</Provider>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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<T>(
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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 };
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Theme>;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "SETTINGS_SET_NOTIFICATION_OPTIONS";
|
|
||||||
options: NotificationOptions;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "SETTINGS_SET_APPEARANCE";
|
|
||||||
options: Partial<AppearanceOptions>;
|
|
||||||
}
|
|
||||||
| 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<string, StoredTheme>;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
import type { ChannelUnread } from "revolt-api/types/Sync";
|
|
||||||
import { ulid } from "ulid";
|
|
||||||
|
|
||||||
export interface Unreads {
|
|
||||||
[key: string]: Partial<Omit<ChannelUnread, "_id">>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
38
yarn.lock
38
yarn.lock
|
@ -2463,10 +2463,10 @@ events@^3.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
||||||
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
|
integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==
|
||||||
|
|
||||||
exponential-backoff@^3.1.0:
|
"exponential-backoff@npm:@insertish/exponential-backoff":
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.0.tgz#9409c7e579131f8bd4b32d7d8094a911040f2e68"
|
resolved "https://registry.yarnpkg.com/@insertish/exponential-backoff/-/exponential-backoff-3.1.0.tgz#1d2e4c215fa8647779cfeab74ecb54a5c36835e6"
|
||||||
integrity sha512-oBuz5SYz5zzyuHINoe9ooePwSu0xApKWgeNzok4hZ5YKXFh9zrQBEM15CXqoZkJJPuI2ArvqjPQd8UKJA753XA==
|
integrity sha512-8Jab9OfjheI84T04QjUwXceSO1DMGy8goDqVdnuoffC2fg23zBnikLJkrRHiT/ao4c08v4R2mU7+/DXMWmROng==
|
||||||
|
|
||||||
fake-mediastreamtrack@^1.1.6:
|
fake-mediastreamtrack@^1.1.6:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||||
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
|
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
|
||||||
|
|
||||||
isomorphic-ws@^4.0.1:
|
"isomorphic-ws@npm:@insertish/isomorphic-ws":
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc"
|
resolved "https://registry.yarnpkg.com/@insertish/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#5bcd6f73b93efa9ccdb6abf887ae808d40827169"
|
||||||
integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==
|
integrity sha512-kFD/p8T4Hkqr992QrdkbW/cQ/W/q2d9MPCobwzBv2PwTKLkCD9RaYDy6m17qRnSLQQ5PU0kHCG8kaOwAqzj1vQ==
|
||||||
|
|
||||||
javascript-natural-sort@0.7.1:
|
javascript-natural-sort@0.7.1:
|
||||||
version "0.7.1"
|
version "0.7.1"
|
||||||
|
@ -3576,18 +3576,6 @@ react-redux@^7.2.0:
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
react-is "^16.13.1"
|
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:
|
react-router-dom@^5.2.0:
|
||||||
version "5.2.1"
|
version "5.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.1.tgz#34af8b551a4ce17487d3f80e651b91651978dff6"
|
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:
|
dependencies:
|
||||||
picomatch "^2.2.1"
|
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"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.1.tgz#76f1c439bb42043f985fbd9bf21990e60bd67f47"
|
resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.1.tgz#76f1c439bb42043f985fbd9bf21990e60bd67f47"
|
||||||
integrity sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==
|
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"
|
resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-alpha.9.tgz#46e75b7d8f9c6702df39039b829dddbb7897f237"
|
||||||
integrity sha512-L8K9uPV3ME8bLdtWm8L9iPQvFM0GghA+5LzmWFjd6Gbn56u22ZYub2lABi4iHrWgeA2X41dGSsuSBgHSlts9Og==
|
integrity sha512-L8K9uPV3ME8bLdtWm8L9iPQvFM0GghA+5LzmWFjd6Gbn56u22ZYub2lABi4iHrWgeA2X41dGSsuSBgHSlts9Og==
|
||||||
|
|
||||||
revolt.js@5.2.0-patch.0:
|
revolt.js@5.2.1-patch.1:
|
||||||
version "5.2.0-patch.0"
|
version "5.2.1-patch.1"
|
||||||
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.2.0-patch.0.tgz#af6afc402399e5394b50b2e7d1573ff490fd3906"
|
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-5.2.1-patch.1.tgz#4b392d4dae12ea28f559ef89790368f53788c81d"
|
||||||
integrity sha512-PnHKqRpEvrBFm1xtLA/lGG5FIsp5kW4eB8sYiejjQCA1DWi7Xg6MNvyOjjha6jKftPXF8roivfZWEnM7sY1bnA==
|
integrity sha512-u2vvbCWXKx+vZKqlt5izowf9XnMbWdh3GaPMzipek6l6mBYSCIlr796HoiiIO3c2T3AWqh3zav97rm8z3jOIXg==
|
||||||
dependencies:
|
dependencies:
|
||||||
axios "^0.21.4"
|
axios "^0.21.4"
|
||||||
eventemitter3 "^4.0.7"
|
eventemitter3 "^4.0.7"
|
||||||
exponential-backoff "^3.1.0"
|
exponential-backoff "npm:@insertish/exponential-backoff"
|
||||||
isomorphic-ws "^4.0.1"
|
isomorphic-ws "npm:@insertish/isomorphic-ws"
|
||||||
lodash.defaultsdeep "^4.6.1"
|
lodash.defaultsdeep "^4.6.1"
|
||||||
lodash.flatten "^4.4.0"
|
lodash.flatten "^4.4.0"
|
||||||
lodash.isequal "^4.5.0"
|
lodash.isequal "^4.5.0"
|
||||||
|
|
Loading…
Reference in a new issue