forked from abner/for-legacy-web
Merge branch 'mobx'
This commit is contained in:
@@ -6,7 +6,8 @@ import styled from "styled-components";
|
||||
import { Text } from "preact-i18n";
|
||||
import { useState } from "preact/hooks";
|
||||
|
||||
import { dispatch, getState } from "../../redux";
|
||||
import { useApplicationState } from "../../mobx/State";
|
||||
import { SECTION_NSFW } from "../../mobx/stores/Layout";
|
||||
|
||||
import Button from "../ui/Button";
|
||||
import Checkbox from "../ui/Checkbox";
|
||||
@@ -49,9 +50,7 @@ type Props = {
|
||||
|
||||
export default observer((props: Props) => {
|
||||
const history = useHistory();
|
||||
const [consent, setConsent] = useState(
|
||||
getState().sectionToggle["nsfw"] ?? false,
|
||||
);
|
||||
const layout = useApplicationState().layout;
|
||||
const [ageGate, setAgeGate] = useState(false);
|
||||
|
||||
if (ageGate || !props.gated) {
|
||||
@@ -81,26 +80,19 @@ export default observer((props: Props) => {
|
||||
</span>
|
||||
|
||||
<Checkbox
|
||||
checked={consent}
|
||||
onChange={(v) => {
|
||||
setConsent(v);
|
||||
if (v) {
|
||||
dispatch({
|
||||
type: "SECTION_TOGGLE_SET",
|
||||
id: "nsfw",
|
||||
state: true,
|
||||
});
|
||||
} else {
|
||||
dispatch({ type: "SECTION_TOGGLE_UNSET", id: "nsfw" });
|
||||
}
|
||||
}}>
|
||||
checked={layout.getSectionState(SECTION_NSFW, false)}
|
||||
onChange={() => layout.toggleSectionState(SECTION_NSFW, false)}>
|
||||
<Text id="app.main.channel.nsfw.confirm" />
|
||||
</Checkbox>
|
||||
<div className="actions">
|
||||
<Button contrast onClick={() => history.goBack()}>
|
||||
<Text id="app.special.modals.actions.back" />
|
||||
</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`} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ChevronDown } from "@styled-icons/boxicons-regular";
|
||||
|
||||
import { State, store } from "../../redux";
|
||||
import { Action } from "../../redux/reducers";
|
||||
import { useApplicationState } from "../../mobx/State";
|
||||
|
||||
import Details from "../ui/Details";
|
||||
|
||||
@@ -25,27 +24,14 @@ export default function CollapsibleSection({
|
||||
children,
|
||||
...detailsProps
|
||||
}: Props) {
|
||||
const state: State = store.getState();
|
||||
|
||||
function setState(state: boolean) {
|
||||
if (state === defaultValue) {
|
||||
store.dispatch({
|
||||
type: "SECTION_TOGGLE_UNSET",
|
||||
id,
|
||||
} as Action);
|
||||
} else {
|
||||
store.dispatch({
|
||||
type: "SECTION_TOGGLE_SET",
|
||||
id,
|
||||
state,
|
||||
} as Action);
|
||||
}
|
||||
}
|
||||
const layout = useApplicationState().layout;
|
||||
|
||||
return (
|
||||
<Details
|
||||
open={state.sectionToggle[id] ?? defaultValue}
|
||||
onToggle={(e) => setState(e.currentTarget.open)}
|
||||
open={layout.getSectionState(id, defaultValue)}
|
||||
onToggle={(e) =>
|
||||
layout.setSectionState(id, e.currentTarget.open, defaultValue)
|
||||
}
|
||||
{...detailsProps}>
|
||||
<summary>
|
||||
<div class="padding">
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { EmojiPacks } from "../../redux/reducers/settings";
|
||||
export type EmojiPack = "mutant" | "twemoji" | "noto" | "openmoji";
|
||||
|
||||
let EMOJI_PACK = "mutant";
|
||||
let EMOJI_PACK: EmojiPack = "mutant";
|
||||
const REVISION = 3;
|
||||
|
||||
export function setEmojiPack(pack: EmojiPacks) {
|
||||
export function setGlobalEmojiPack(pack: EmojiPack) {
|
||||
EMOJI_PACK = pack;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import { dispatch } from "../../redux";
|
||||
import { connectState } from "../../redux/connector";
|
||||
import { useApplicationState } from "../../mobx/State";
|
||||
|
||||
import { Language, Languages } from "../../context/Locale";
|
||||
|
||||
import ComboBox from "../ui/ComboBox";
|
||||
|
||||
type Props = {
|
||||
locale: string;
|
||||
};
|
||||
/**
|
||||
* Component providing a language selector combobox.
|
||||
* Note: this is not an observer but this is fine as we are just using a combobox.
|
||||
*/
|
||||
export default function LocaleSelector() {
|
||||
const locale = useApplicationState().locale;
|
||||
|
||||
export function LocaleSelector(props: Props) {
|
||||
return (
|
||||
<ComboBox
|
||||
value={props.locale}
|
||||
value={locale.getLanguage()}
|
||||
onChange={(e) =>
|
||||
dispatch({
|
||||
type: "SET_LOCALE",
|
||||
locale: e.currentTarget.value as Language,
|
||||
})
|
||||
locale.setLanguage(e.currentTarget.value as Language)
|
||||
}>
|
||||
{Object.keys(Languages).map((x) => {
|
||||
const l = Languages[x as keyof typeof Languages];
|
||||
@@ -30,9 +28,3 @@ export function LocaleSelector(props: Props) {
|
||||
</ComboBox>
|
||||
);
|
||||
}
|
||||
|
||||
export default connectState(LocaleSelector, (state) => {
|
||||
return {
|
||||
locale: state.locale,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
import { Download, CloudDownload } from "@styled-icons/boxicons-regular";
|
||||
|
||||
import { useContext, useEffect, useState } from "preact/hooks";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
|
||||
import { internalSubscribe } from "../../lib/eventEmitter";
|
||||
|
||||
import { ThemeContext } from "../../context/Theme";
|
||||
import { useApplicationState } from "../../mobx/State";
|
||||
|
||||
import IconButton from "../ui/IconButton";
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function UpdateIndicator({ style }: Props) {
|
||||
});
|
||||
|
||||
if (!pending) return null;
|
||||
const theme = useContext(ThemeContext);
|
||||
const theme = useApplicationState().settings.theme;
|
||||
|
||||
if (style === "titlebar") {
|
||||
return (
|
||||
@@ -36,7 +36,10 @@ export default function UpdateIndicator({ style }: Props) {
|
||||
content="A new update is available!"
|
||||
placement="bottom">
|
||||
<div onClick={() => updateSW(true)}>
|
||||
<CloudDownload size={22} color={theme.success} />
|
||||
<CloudDownload
|
||||
size={22}
|
||||
color={theme.getVariable("success")}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -47,7 +50,7 @@ export default function UpdateIndicator({ style }: Props) {
|
||||
|
||||
return (
|
||||
<IconButton onClick={() => updateSW(true)}>
|
||||
<Download size={22} color={theme.success} />
|
||||
<Download size={22} color={theme.getVariable("success")} />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useState } from "preact/hooks";
|
||||
|
||||
import { internalEmit } from "../../../lib/eventEmitter";
|
||||
|
||||
import { QueuedMessage } from "../../../redux/reducers/queue";
|
||||
import { QueuedMessage } from "../../../mobx/stores/MessageQueue";
|
||||
|
||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
@@ -20,10 +20,9 @@ import {
|
||||
SMOOTH_SCROLL_ON_RECEIVE,
|
||||
} from "../../../lib/renderer/Singleton";
|
||||
|
||||
import { dispatch, getState } from "../../../redux";
|
||||
import { Reply } from "../../../redux/reducers/queue";
|
||||
import { useApplicationState } from "../../../mobx/State";
|
||||
import { Reply } from "../../../mobx/stores/MessageQueue";
|
||||
|
||||
import { SoundContext } from "../../../context/Settings";
|
||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
||||
import {
|
||||
FileUploader,
|
||||
@@ -112,17 +111,16 @@ const Action = styled.div`
|
||||
const RE_SED = new RegExp("^s/([^])*/([^])*$");
|
||||
|
||||
// ! FIXME: add to app config and load from app config
|
||||
export const CAN_UPLOAD_AT_ONCE = 4;
|
||||
export const CAN_UPLOAD_AT_ONCE = 5;
|
||||
|
||||
export default observer(({ channel }: Props) => {
|
||||
const [draft, setDraft] = useState(getState().drafts[channel._id] ?? "");
|
||||
const state = useApplicationState();
|
||||
|
||||
const [uploadState, setUploadState] = useState<UploadState>({
|
||||
type: "none",
|
||||
});
|
||||
const [typing, setTyping] = useState<boolean | number>(false);
|
||||
const [replies, setReplies] = useState<Reply[]>([]);
|
||||
const playSound = useContext(SoundContext);
|
||||
const { openScreen } = useIntermediate();
|
||||
const client = useContext(AppContext);
|
||||
const translate = useTranslation();
|
||||
@@ -148,27 +146,18 @@ export default observer(({ channel }: Props) => {
|
||||
);
|
||||
}
|
||||
|
||||
// Push message content to draft.
|
||||
const setMessage = useCallback(
|
||||
(content?: string) => {
|
||||
setDraft(content ?? "");
|
||||
|
||||
if (content) {
|
||||
dispatch({
|
||||
type: "SET_DRAFT",
|
||||
channel: channel._id,
|
||||
content,
|
||||
});
|
||||
} else {
|
||||
dispatch({
|
||||
type: "CLEAR_DRAFT",
|
||||
channel: channel._id,
|
||||
});
|
||||
}
|
||||
},
|
||||
[channel._id],
|
||||
(content?: string) => state.draft.set(channel._id, content),
|
||||
[state.draft, channel._id],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
*
|
||||
* @param content
|
||||
* @param action
|
||||
*/
|
||||
function append(content: string, action: "quote" | "mention") {
|
||||
const text =
|
||||
action === "quote"
|
||||
@@ -178,10 +167,10 @@ export default observer(({ channel }: Props) => {
|
||||
.join("\n")}\n\n`
|
||||
: `${content} `;
|
||||
|
||||
if (!draft || draft.length === 0) {
|
||||
if (!state.draft.has(channel._id)) {
|
||||
setMessage(text);
|
||||
} else {
|
||||
setMessage(`${draft}\n${text}`);
|
||||
setMessage(`${state.draft.get(channel._id)}\n${text}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,13 +179,16 @@ export default observer(({ channel }: Props) => {
|
||||
"append",
|
||||
append as (...args: unknown[]) => void,
|
||||
);
|
||||
}, [draft, setMessage]);
|
||||
}, [state.draft, channel._id, setMessage]);
|
||||
|
||||
/**
|
||||
* Trigger send message.
|
||||
*/
|
||||
async function send() {
|
||||
if (uploadState.type === "uploading" || uploadState.type === "sending")
|
||||
return;
|
||||
|
||||
const content = draft?.trim() ?? "";
|
||||
const content = state.draft.get(channel._id)?.trim() ?? "";
|
||||
if (uploadState.type === "attached") return sendFile(content);
|
||||
if (content.length === 0) return;
|
||||
|
||||
@@ -247,20 +239,15 @@ export default observer(({ channel }: Props) => {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
playSound("outbound");
|
||||
state.settings.sounds.playSound("outbound");
|
||||
|
||||
dispatch({
|
||||
type: "QUEUE_ADD",
|
||||
nonce,
|
||||
state.queue.add(nonce, channel._id, {
|
||||
_id: nonce,
|
||||
channel: channel._id,
|
||||
message: {
|
||||
_id: nonce,
|
||||
channel: channel._id,
|
||||
author: client.user!._id,
|
||||
author: client.user!._id,
|
||||
|
||||
content,
|
||||
replies,
|
||||
},
|
||||
content,
|
||||
replies,
|
||||
});
|
||||
|
||||
defer(() => renderer.jumpToBottom(SMOOTH_SCROLL_ON_RECEIVE));
|
||||
@@ -272,15 +259,16 @@ export default observer(({ channel }: Props) => {
|
||||
replies,
|
||||
});
|
||||
} catch (error) {
|
||||
dispatch({
|
||||
type: "QUEUE_FAIL",
|
||||
error: takeError(error),
|
||||
nonce,
|
||||
});
|
||||
state.queue.fail(nonce, takeError(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param content
|
||||
* @returns
|
||||
*/
|
||||
async function sendFile(content: string) {
|
||||
if (uploadState.type !== "attached") return;
|
||||
const attachments: string[] = [];
|
||||
@@ -360,7 +348,7 @@ export default observer(({ channel }: Props) => {
|
||||
|
||||
setMessage();
|
||||
setReplies([]);
|
||||
playSound("outbound");
|
||||
state.settings.sounds.playSound("outbound");
|
||||
|
||||
if (files.length > CAN_UPLOAD_AT_ONCE) {
|
||||
setUploadState({
|
||||
@@ -372,6 +360,10 @@ export default observer(({ channel }: Props) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
function startTyping() {
|
||||
if (typeof typing === "number" && +new Date() < typing) return;
|
||||
|
||||
@@ -385,6 +377,10 @@ export default observer(({ channel }: Props) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param force
|
||||
*/
|
||||
function stopTyping(force?: boolean) {
|
||||
if (force || typing) {
|
||||
const ws = client.websocket;
|
||||
@@ -503,7 +499,7 @@ export default observer(({ channel }: Props) => {
|
||||
id="message"
|
||||
maxLength={2000}
|
||||
onKeyUp={onKeyUp}
|
||||
value={draft ?? ""}
|
||||
value={state.draft.get(channel._id) ?? ""}
|
||||
padding="var(--message-box-padding)"
|
||||
onKeyDown={(e) => {
|
||||
if (e.ctrlKey && e.key === "Enter") {
|
||||
@@ -515,7 +511,7 @@ export default observer(({ channel }: Props) => {
|
||||
|
||||
if (
|
||||
e.key === "ArrowUp" &&
|
||||
(!draft || draft.length === 0)
|
||||
!state.draft.has(channel._id)
|
||||
) {
|
||||
e.preventDefault();
|
||||
internalEmit("MessageRenderer", "edit_last");
|
||||
|
||||
@@ -10,8 +10,9 @@ import { StateUpdater, useEffect } from "preact/hooks";
|
||||
|
||||
import { internalSubscribe } from "../../../../lib/eventEmitter";
|
||||
|
||||
import { dispatch, getState } from "../../../../redux";
|
||||
import { Reply } from "../../../../redux/reducers/queue";
|
||||
import { useApplicationState } from "../../../../mobx/State";
|
||||
import { SECTION_MENTION } from "../../../../mobx/stores/Layout";
|
||||
import { Reply } from "../../../../mobx/stores/MessageQueue";
|
||||
|
||||
import IconButton from "../../../ui/IconButton";
|
||||
|
||||
@@ -81,6 +82,7 @@ const Base = styled.div`
|
||||
const MAX_REPLIES = 5;
|
||||
export default observer(({ channel, replies, setReplies }: Props) => {
|
||||
const client = channel.client;
|
||||
const layout = useApplicationState().layout;
|
||||
|
||||
// Event listener for adding new messages to reply bar.
|
||||
useEffect(() => {
|
||||
@@ -99,7 +101,7 @@ export default observer(({ channel, replies, setReplies }: Props) => {
|
||||
mention:
|
||||
message.author_id === client.user!._id
|
||||
? false
|
||||
: getState().sectionToggle.mention ?? false,
|
||||
: layout.getSectionState("SECTION_MENTION", false),
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -181,11 +183,11 @@ export default observer(({ channel, replies, setReplies }: Props) => {
|
||||
}),
|
||||
);
|
||||
|
||||
dispatch({
|
||||
type: "SECTION_TOGGLE_SET",
|
||||
id: "mention",
|
||||
layout.setSectionState(
|
||||
SECTION_MENTION,
|
||||
state,
|
||||
});
|
||||
false,
|
||||
);
|
||||
}}>
|
||||
<span class="toggle">
|
||||
<At size={15} />
|
||||
|
||||
@@ -10,8 +10,6 @@ import { useContext, useEffect, useState } from "preact/hooks";
|
||||
import { defer } from "../../../../lib/defer";
|
||||
import { isTouchscreenDevice } from "../../../../lib/isTouchscreenDevice";
|
||||
|
||||
import { dispatch } from "../../../../redux";
|
||||
|
||||
import {
|
||||
AppContext,
|
||||
ClientStatus,
|
||||
@@ -33,7 +31,7 @@ const EmbedInviteBase = styled.div`
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
margin-top: 2px;
|
||||
${() =>
|
||||
${() =>
|
||||
isTouchscreenDevice &&
|
||||
css`
|
||||
flex-wrap: wrap;
|
||||
@@ -44,19 +42,17 @@ const EmbedInviteBase = styled.div`
|
||||
> button {
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const EmbedInviteDetails = styled.div`
|
||||
flex-grow: 1;
|
||||
padding-left: 12px;
|
||||
${() =>
|
||||
${() =>
|
||||
isTouchscreenDevice &&
|
||||
css`
|
||||
width: calc(100% - 55px);
|
||||
`
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
const EmbedInviteName = styled.div`
|
||||
@@ -74,11 +70,10 @@ type Props = {
|
||||
code: string;
|
||||
};
|
||||
|
||||
export function EmbedInvite(props: Props) {
|
||||
export function EmbedInvite({ code }: Props) {
|
||||
const history = useHistory();
|
||||
const client = useContext(AppContext);
|
||||
const status = useContext(StatusContext);
|
||||
const code = props.code;
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const [error, setError] = useState<string | undefined>(undefined);
|
||||
const [joinError, setJoinError] = useState<string | undefined>(undefined);
|
||||
@@ -124,7 +119,8 @@ export function EmbedInvite(props: Props) {
|
||||
<EmbedInviteDetails>
|
||||
<EmbedInviteName>{invite.server_name}</EmbedInviteName>
|
||||
<EmbedInviteMemberCount>
|
||||
{invite.member_count.toLocaleString()} {invite.member_count === 1 ? "member" : "members"}
|
||||
{invite.member_count.toLocaleString()}{" "}
|
||||
{invite.member_count === 1 ? "member" : "members"}
|
||||
</EmbedInviteMemberCount>
|
||||
</EmbedInviteDetails>
|
||||
{processing ? (
|
||||
@@ -151,10 +147,9 @@ export function EmbedInvite(props: Props) {
|
||||
|
||||
defer(() => {
|
||||
if (server) {
|
||||
dispatch({
|
||||
type: "UNREADS_MARK_MULTIPLE_READ",
|
||||
channels: server.channel_ids,
|
||||
});
|
||||
client.unreads!.markMultipleRead(
|
||||
server.channel_ids,
|
||||
);
|
||||
|
||||
history.push(
|
||||
`/server/${server._id}/channel/${invite.channel_id}`,
|
||||
@@ -172,7 +167,9 @@ export function EmbedInvite(props: Props) {
|
||||
setProcessing(false);
|
||||
}
|
||||
}}>
|
||||
{client.servers.get(invite.server_id) ? "Joined" : "Join"}
|
||||
{client.servers.get(invite.server_id)
|
||||
? "Joined"
|
||||
: "Join"}
|
||||
</Button>
|
||||
)}
|
||||
</EmbedInviteBase>
|
||||
|
||||
@@ -5,12 +5,10 @@ import { useParams } from "react-router-dom";
|
||||
import { Masquerade } from "revolt-api/types/Channels";
|
||||
import { Presence } from "revolt-api/types/Users";
|
||||
import { User } from "revolt.js/dist/maps/Users";
|
||||
import { Nullable } from "revolt.js/dist/util/null";
|
||||
import styled, { css } from "styled-components";
|
||||
|
||||
import { useContext } from "preact/hooks";
|
||||
import { useApplicationState } from "../../../mobx/State";
|
||||
|
||||
import { ThemeContext } from "../../../context/Theme";
|
||||
import { useClient } from "../../../context/revoltjs/RevoltClient";
|
||||
|
||||
import fallback from "../assets/user.png";
|
||||
@@ -26,15 +24,15 @@ interface Props extends IconBaseProps<User> {
|
||||
}
|
||||
|
||||
export function useStatusColour(user?: User) {
|
||||
const theme = useContext(ThemeContext);
|
||||
const theme = useApplicationState().settings.theme;
|
||||
|
||||
return user?.online && user?.status?.presence !== Presence.Invisible
|
||||
? user?.status?.presence === Presence.Idle
|
||||
? theme["status-away"]
|
||||
? theme.getVariable("status-away")
|
||||
: user?.status?.presence === Presence.Busy
|
||||
? theme["status-busy"]
|
||||
: theme["status-online"]
|
||||
: theme["status-invisible"];
|
||||
? theme.getVariable("status-busy")
|
||||
: theme.getVariable("status-online")
|
||||
: theme.getVariable("status-invisible");
|
||||
}
|
||||
|
||||
const VoiceIndicator = styled.div<{ status: VoiceStatus }>`
|
||||
|
||||
Reference in New Issue
Block a user