mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-22 10:22:30 +01:00
Merge branch 'main' into feat/joinguild
This commit is contained in:
commit
0509aec987
@ -7,5 +7,6 @@
|
||||
"bracketSpacing": true,
|
||||
"quoteProps": "as-needed",
|
||||
"useTabs": true,
|
||||
"singleQuote": false
|
||||
"singleQuote": false,
|
||||
"printWidth": 120
|
||||
}
|
118
flake.lock
118
flake.lock
@ -1,61 +1,61 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1682258674,
|
||||
"narHash": "sha256-nSqXN+dBgw8+DeE3vBhdHB7C2p+2QBDsA27ZptjJz/Y=",
|
||||
"owner": "lilyinstarlight",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "47c77aa566f5114b4d759280702b9b910d3c7f0f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "lilyinstarlight",
|
||||
"ref": "unheck/nodejs",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1682258674,
|
||||
"narHash": "sha256-nSqXN+dBgw8+DeE3vBhdHB7C2p+2QBDsA27ZptjJz/Y=",
|
||||
"owner": "lilyinstarlight",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "47c77aa566f5114b4d759280702b9b910d3c7f0f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "lilyinstarlight",
|
||||
"ref": "unheck/nodejs",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
|
@ -38,6 +38,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-error-boundary": "^3.1.4",
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-loading-skeleton": "^3.3.1",
|
||||
"react-moment": "^1.1.3",
|
||||
"react-router-dom": "^6.11.1",
|
||||
@ -55,9 +56,8 @@
|
||||
"start": "craco start",
|
||||
"build": "craco build",
|
||||
"test": "craco test",
|
||||
"eject": "react-scripts eject",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "npx prettier . --write"
|
||||
"lint:fix": "pnpx prettier . --write"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
|
27053
pnpm-lock.yaml
27053
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
37
src/App.tsx
37
src/App.tsx
@ -32,17 +32,12 @@ function App() {
|
||||
app.setGatewayReady(false);
|
||||
app.gateway.connect(Globals.routeSettings.gateway);
|
||||
} else {
|
||||
logger.debug(
|
||||
"Gateway connect called but socket is not closed",
|
||||
);
|
||||
logger.debug("Gateway connect called but socket is not closed");
|
||||
}
|
||||
} else {
|
||||
logger.debug("user no longer authenticated");
|
||||
if (app.gateway.readyState === WebSocket.OPEN) {
|
||||
app.gateway.disconnect(
|
||||
1000,
|
||||
"user is no longer authenticated",
|
||||
);
|
||||
app.gateway.disconnect(1000, "user is no longer authenticated");
|
||||
}
|
||||
|
||||
navigate("/");
|
||||
@ -62,33 +57,15 @@ function App() {
|
||||
return (
|
||||
<Loader>
|
||||
<Routes>
|
||||
<Route
|
||||
index
|
||||
path="/"
|
||||
element={<AuthenticationGuard component={AppPage} />}
|
||||
/>
|
||||
<Route
|
||||
path="/app"
|
||||
element={<AuthenticationGuard component={AppPage} />}
|
||||
/>
|
||||
<Route index path="/" element={<AuthenticationGuard component={AppPage} />} />
|
||||
<Route path="/app" element={<AuthenticationGuard component={AppPage} />} />
|
||||
<Route
|
||||
path="/channels/:guildId/:channelId?"
|
||||
element={<AuthenticationGuard component={ChannelPage} />}
|
||||
/>
|
||||
<Route
|
||||
path="/login"
|
||||
element={<UnauthenticatedGuard component={LoginPage} />}
|
||||
/>
|
||||
<Route
|
||||
path="/register"
|
||||
element={
|
||||
<UnauthenticatedGuard component={RegistrationPage} />
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/logout"
|
||||
element={<AuthenticationGuard component={LogoutPage} />}
|
||||
/>
|
||||
<Route path="/login" element={<UnauthenticatedGuard component={LoginPage} />} />
|
||||
<Route path="/register" element={<UnauthenticatedGuard component={RegistrationPage} />} />
|
||||
<Route path="/logout" element={<AuthenticationGuard component={LogoutPage} />} />
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</Loader>
|
||||
|
@ -24,8 +24,7 @@ const FirstWrapper = styled.div<{ isCategory?: boolean; active?: boolean }>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding: 0 8px;
|
||||
background-color: ${(props) =>
|
||||
props.active ? "var(--background-primary-alt)" : "transparent"};
|
||||
background-color: ${(props) => (props.active ? "var(--background-primary-alt)" : "transparent")};
|
||||
|
||||
&:hover {
|
||||
background-color: var(--background-primary-alt);
|
||||
@ -66,14 +65,12 @@ function ChannelList() {
|
||||
<ListItem
|
||||
key={channel.id}
|
||||
isCategory={isCategory}
|
||||
onClick={() =>
|
||||
{
|
||||
// prevent navigating to non-text channels
|
||||
if (!channel.isTextChannel) return;
|
||||
onClick={() => {
|
||||
// prevent navigating to non-text channels
|
||||
if (!channel.isTextChannel) return;
|
||||
|
||||
navigate(`/channels/${guild.id}/${channel.id}`)
|
||||
}
|
||||
}
|
||||
navigate(`/channels/${guild.id}/${channel.id}`);
|
||||
}}
|
||||
>
|
||||
<FirstWrapper isCategory={isCategory} active={active}>
|
||||
{channel.channelIcon && (
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import React from "react";
|
||||
import InfiniteScroll from "react-infinite-scroll-component";
|
||||
import { useParams } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import useLogger from "../hooks/useLogger";
|
||||
import { useAppStore } from "../stores/AppStore";
|
||||
import { QueuedMessageStatus } from "../stores/MessageQueue";
|
||||
import ChatHeader from "./ChatHeader";
|
||||
@ -22,12 +24,6 @@ const MessageListWrapper = styled.div`
|
||||
flex-direction: column-reverse;
|
||||
`;
|
||||
|
||||
const List = styled.ul`
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -43,6 +39,7 @@ const Spacer = styled.div`
|
||||
|
||||
function Chat() {
|
||||
const app = useAppStore();
|
||||
const logger = useLogger("Chat");
|
||||
const { guildId, channelId } = useParams<{
|
||||
guildId: string;
|
||||
channelId: string;
|
||||
@ -65,48 +62,61 @@ function Chat() {
|
||||
);
|
||||
}
|
||||
|
||||
const messages = [
|
||||
...(channel.messages.messages ?? []),
|
||||
...(channel ? app.queue.get(channel.id) ?? [] : []),
|
||||
];
|
||||
const messages = [...(channel.messages.messages ?? []), ...(channel ? app.queue.get(channel.id) ?? [] : [])];
|
||||
|
||||
const fetchMore = async () => {
|
||||
if (!channel.messages.count) {
|
||||
return;
|
||||
}
|
||||
// get first message in the list to use as before
|
||||
const before = channel.messages.messages[0].id;
|
||||
logger.debug(`Fetching 50 messages before ${before} for channel ${channel.id}`);
|
||||
await channel.getMessages(app, false, 50, before);
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<ChatHeader channel={channel} />
|
||||
<Container>
|
||||
<MessageListWrapper>
|
||||
<List>
|
||||
{messages.map((message, index, arr) => {
|
||||
const t = 1 * 24 * 60 * 60 * 1000;
|
||||
<MessageListWrapper id="scrollable-div">
|
||||
<InfiniteScroll
|
||||
dataLength={messages.length}
|
||||
next={fetchMore}
|
||||
inverse={true}
|
||||
// TODO: change this to false when we have a fetch that returns less than 50 messages
|
||||
hasMore={true}
|
||||
loader={<h4>Loading...</h4>}
|
||||
scrollableTarget="scrollable-div"
|
||||
>
|
||||
{messages.map((message, index, arr) => {
|
||||
// calculate max ms between messages to determine if they should be grouped (if from same author). 7 minutes
|
||||
const maxTimeDifference = 1000 * 60 * 7;
|
||||
|
||||
const isHeader =
|
||||
index === 0 ||
|
||||
message.author.id !==
|
||||
arr[index - 1].author.id ||
|
||||
message.timestamp.getTime() -
|
||||
arr[index - 1].timestamp.getTime() >
|
||||
t;
|
||||
const isHeader =
|
||||
// always show header for first message
|
||||
index === 0 ||
|
||||
// show header if author is different from previous message
|
||||
message.author.id !== arr[index - 1].author.id ||
|
||||
// show header if time difference is greater than maxTimeDifference
|
||||
message.timestamp.getTime() - arr[index - 1].timestamp.getTime() >
|
||||
maxTimeDifference;
|
||||
|
||||
return (
|
||||
<Message
|
||||
key={message.id}
|
||||
message={message}
|
||||
isHeader={isHeader}
|
||||
isSending={
|
||||
"status" in message &&
|
||||
message.status ===
|
||||
QueuedMessageStatus.SENDING
|
||||
}
|
||||
isFailed={
|
||||
"status" in message &&
|
||||
message.status ===
|
||||
QueuedMessageStatus.FAILED
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<Spacer />
|
||||
</List>
|
||||
return (
|
||||
<Message
|
||||
key={message.id}
|
||||
message={message}
|
||||
isHeader={isHeader}
|
||||
isSending={
|
||||
"status" in message && message.status === QueuedMessageStatus.SENDING
|
||||
}
|
||||
isFailed={"status" in message && message.status === QueuedMessageStatus.FAILED}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<Spacer />
|
||||
</InfiniteScroll>
|
||||
</MessageListWrapper>
|
||||
</MessageListWrapper>
|
||||
<MessageInput channel={channel} />
|
||||
</Container>
|
||||
|
@ -48,8 +48,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.select-search-container:not(.select-search-is-multiple).select-search-has-focus
|
||||
.select-search-select {
|
||||
.select-search-container:not(.select-search-is-multiple).select-search-has-focus .select-search-select {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
@ -79,11 +79,7 @@ const MONTHS = [
|
||||
|
||||
interface Props {
|
||||
onChange: (value: string) => void;
|
||||
onErrorChange: (errors: {
|
||||
month?: string;
|
||||
day?: string;
|
||||
year?: string;
|
||||
}) => void;
|
||||
onErrorChange: (errors: { month?: string; day?: string; year?: string }) => void;
|
||||
error: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
@ -121,21 +117,34 @@ export class DOBInput extends Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
onInputChange =
|
||||
(type: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
onInputChange = (type: string) => (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
|
||||
// clear error for field
|
||||
this.setState(
|
||||
{
|
||||
...this.state,
|
||||
errors: { ...this.state.errors, [type]: undefined },
|
||||
},
|
||||
() => {
|
||||
// ensure only numbers
|
||||
if (isNaN(Number(value))) {
|
||||
// clear error for field
|
||||
this.setState(
|
||||
{
|
||||
...this.state,
|
||||
errors: { ...this.state.errors, [type]: undefined },
|
||||
},
|
||||
() => {
|
||||
// ensure only numbers
|
||||
if (isNaN(Number(value))) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
errors: {
|
||||
...this.state.errors,
|
||||
[type]: "Invalid Date",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "day") {
|
||||
// day should be a number between 1-31 and not more than 2 digits
|
||||
if (value !== "" && (value.length > 2 || Number(value) > 31 || Number(value) < 1)) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
day: value,
|
||||
errors: {
|
||||
...this.state.errors,
|
||||
[type]: "Invalid Date",
|
||||
@ -144,53 +153,32 @@ export class DOBInput extends Component<Props, State> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "day") {
|
||||
// day should be a number between 1-31 and not more than 2 digits
|
||||
if (
|
||||
value !== "" &&
|
||||
(value.length > 2 ||
|
||||
Number(value) > 31 ||
|
||||
Number(value) < 1)
|
||||
) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
day: value,
|
||||
errors: {
|
||||
...this.state.errors,
|
||||
[type]: "Invalid Date",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({ ...this.state, day: value });
|
||||
}
|
||||
|
||||
this.setState({ ...this.state, day: value });
|
||||
if (type === "year") {
|
||||
// year must be between now-min and now-max
|
||||
if (
|
||||
value.length === 4 &&
|
||||
(Number(value) > new Date().getFullYear() - MIN_AGE ||
|
||||
Number(value) < new Date().getFullYear() - MAX_AGE)
|
||||
) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
year: value,
|
||||
errors: {
|
||||
...this.state.errors,
|
||||
[type]: "Invalid Date",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === "year") {
|
||||
// year must be between now-min and now-max
|
||||
if (
|
||||
value.length === 4 &&
|
||||
(Number(value) >
|
||||
new Date().getFullYear() - MIN_AGE ||
|
||||
Number(value) <
|
||||
new Date().getFullYear() - MAX_AGE)
|
||||
) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
year: value,
|
||||
errors: {
|
||||
...this.state.errors,
|
||||
[type]: "Invalid Date",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ ...this.state, year: value });
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
this.setState({ ...this.state, year: value });
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
constructDate = (values: { month: string; day: string; year: string }) => {
|
||||
const { month, day, year } = values;
|
||||
@ -206,9 +194,7 @@ export class DOBInput extends Component<Props, State> {
|
||||
placeholder="Month"
|
||||
search
|
||||
options={MONTHS}
|
||||
onChange={(e) =>
|
||||
this.setState({ ...this.state, month: e as string })
|
||||
}
|
||||
onChange={(e) => this.setState({ ...this.state, month: e as string })}
|
||||
value={this.state.month}
|
||||
disabled={this.props.disabled}
|
||||
/>
|
||||
|
@ -1,8 +1,4 @@
|
||||
import {
|
||||
CDNRoutes,
|
||||
ChannelType,
|
||||
ImageFormat,
|
||||
} from "@spacebarchat/spacebar-api-types/v9";
|
||||
import { CDNRoutes, ChannelType, ImageFormat } from "@spacebarchat/spacebar-api-types/v9";
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
@ -21,18 +17,15 @@ const Wrapper = styled(Container)<{ active?: boolean; hasImage?: boolean }>`
|
||||
height: 48px;
|
||||
border-radius: ${(props) => (props.active ? "30%" : "50%")};
|
||||
background-color: ${(props) =>
|
||||
props.hasImage
|
||||
? "transparent"
|
||||
: props.active
|
||||
? "var(--primary)"
|
||||
: "var(--background-secondary)"};
|
||||
transition: border-radius 0.2s ease, background-color 0.2s ease;
|
||||
props.hasImage ? "transparent" : props.active ? "var(--primary)" : "var(--background-secondary)"};
|
||||
transition:
|
||||
border-radius 0.2s ease,
|
||||
background-color 0.2s ease;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
border-radius: 30%;
|
||||
background-color: ${(props) =>
|
||||
props.hasImage ? "transparent" : "var(--primary)"};
|
||||
background-color: ${(props) => (props.hasImage ? "transparent" : "var(--primary)")};
|
||||
}
|
||||
`;
|
||||
|
||||
@ -54,12 +47,8 @@ function GuildItem(props: Props) {
|
||||
if (!guild) return null;
|
||||
|
||||
const doNavigate = () => {
|
||||
const channel = guild.channels.mapped.find(
|
||||
(x) => x.type !== ChannelType.GuildCategory,
|
||||
);
|
||||
navigate(
|
||||
`/channels/${props.guildId}${channel ? `/${channel.id}` : ""}`,
|
||||
);
|
||||
const channel = guild.channels.mapped.find((x) => x.type !== ChannelType.GuildCategory);
|
||||
navigate(`/channels/${props.guildId}${channel ? `/${channel.id}` : ""}`);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -82,13 +71,7 @@ function GuildItem(props: Props) {
|
||||
>
|
||||
{guild.icon ? (
|
||||
<img
|
||||
src={REST.makeCDNUrl(
|
||||
CDNRoutes.guildIcon(
|
||||
props.guildId,
|
||||
guild?.icon,
|
||||
ImageFormat.PNG,
|
||||
),
|
||||
)}
|
||||
src={REST.makeCDNUrl(CDNRoutes.guildIcon(props.guildId, guild?.icon, ImageFormat.PNG))}
|
||||
width={48}
|
||||
height={48}
|
||||
/>
|
||||
|
@ -52,11 +52,7 @@ function GuildSidebar() {
|
||||
</GuildSidebarListItem>
|
||||
<div aria-label="Servers">
|
||||
{app.guilds.getAll().map((guild) => (
|
||||
<GuildItem
|
||||
key={guild.id}
|
||||
guildId={guild.id}
|
||||
active={guild.id === guildId}
|
||||
/>
|
||||
<GuildItem key={guild.id} guildId={guild.id} active={guild.id === guildId} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -66,7 +62,7 @@ function GuildSidebar() {
|
||||
icon={{
|
||||
icon: "mdiPlus",
|
||||
size: "24px",
|
||||
color: "var(--success)"
|
||||
color: "var(--success)",
|
||||
}}
|
||||
action={() => {
|
||||
openModal(AddServerModal);
|
||||
|
@ -12,9 +12,13 @@ const MessageListItem = styled.li``;
|
||||
const Container = styled.div<{ isHeader?: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 2px 12px;
|
||||
padding: ${(props) => (props.isHeader ? "4" : "2")}px 12px;
|
||||
align-items: center;
|
||||
margin-top: ${(props) => (props.isHeader ? "20px" : undefined)};
|
||||
|
||||
&:hover {
|
||||
background-color: var(--background-primary-highlight);
|
||||
}
|
||||
`;
|
||||
|
||||
const MessageContentContainer = styled.div<{ isHeader?: boolean }>`
|
||||
@ -71,15 +75,10 @@ function Message({ message, isHeader, isSending, isFailed }: Props) {
|
||||
<MessageContentContainer isHeader={isHeader}>
|
||||
{isHeader && (
|
||||
<MessageHeader>
|
||||
<MessageAuthor>
|
||||
{message.author.username}
|
||||
</MessageAuthor>
|
||||
<MessageAuthor>{message.author.username}</MessageAuthor>
|
||||
|
||||
<MessageTimestamp>
|
||||
<Moment
|
||||
calendar={calendarStrings}
|
||||
date={new Date(message.timestamp)}
|
||||
/>
|
||||
<Moment calendar={calendarStrings} date={new Date(message.timestamp)} />
|
||||
</MessageTimestamp>
|
||||
</MessageHeader>
|
||||
)}
|
||||
|
@ -52,8 +52,7 @@ function MessageInput(props: Props) {
|
||||
}
|
||||
|
||||
// controls the placeholder visibility
|
||||
if (!content.length)
|
||||
placeholderRef.current!.style.setProperty("display", "block");
|
||||
if (!content.length) placeholderRef.current!.style.setProperty("display", "block");
|
||||
else placeholderRef.current!.style.setProperty("display", "none");
|
||||
|
||||
// update the input content
|
||||
@ -78,8 +77,7 @@ function MessageInput(props: Props) {
|
||||
if (!wrapperRef.current) return;
|
||||
|
||||
wrapperRef.current.style.height = "44px";
|
||||
wrapperRef.current.style.height =
|
||||
wrapperRef.current.scrollHeight + "px";
|
||||
wrapperRef.current.style.height = wrapperRef.current.scrollHeight + "px";
|
||||
}
|
||||
|
||||
function resetInput() {
|
||||
@ -103,14 +101,8 @@ function MessageInput(props: Props) {
|
||||
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const shouldFail = app.experiments.isTreatmentEnabled(
|
||||
"message_queue",
|
||||
2,
|
||||
);
|
||||
const shouldSend = !app.experiments.isTreatmentEnabled(
|
||||
"message_queue",
|
||||
1,
|
||||
);
|
||||
const shouldFail = app.experiments.isTreatmentEnabled("message_queue", 2);
|
||||
const shouldSend = !app.experiments.isTreatmentEnabled("message_queue", 1);
|
||||
|
||||
if (!props.channel.canSendMessage(content) && !shouldFail) return;
|
||||
|
||||
|
@ -16,8 +16,7 @@ const Wrapper = styled(Container)<{
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: ${(props) => (props.active ? "30%" : "50%")};
|
||||
background-color: ${(props) =>
|
||||
props.active ? "var(--primary)" : "var(--background-secondary)"};
|
||||
background-color: ${(props) => (props.active ? "var(--primary)" : "var(--background-secondary)")};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -25,10 +24,7 @@ const Wrapper = styled(Container)<{
|
||||
|
||||
&:hover {
|
||||
border-radius: 30%;
|
||||
background-color: ${(props) =>
|
||||
props.useGreenColorScheme
|
||||
? "var(--success)"
|
||||
: "var(--primary)"};
|
||||
background-color: ${(props) => (props.useGreenColorScheme ? "var(--success)" : "var(--primary)")};
|
||||
|
||||
}
|
||||
`;
|
||||
@ -36,10 +32,7 @@ const Wrapper = styled(Container)<{
|
||||
interface Props {
|
||||
tooltip?: string;
|
||||
action?: () => void;
|
||||
image?: React.DetailedHTMLProps<
|
||||
React.ImgHTMLAttributes<HTMLImageElement>,
|
||||
HTMLImageElement
|
||||
>;
|
||||
image?: React.DetailedHTMLProps<React.ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>;
|
||||
icon?: IconProps;
|
||||
label?: string;
|
||||
margin?: boolean;
|
||||
@ -50,9 +43,7 @@ interface Props {
|
||||
|
||||
function SidebarAction(props: Props) {
|
||||
if (props.image && props.icon && props.label)
|
||||
throw new Error(
|
||||
"SidebarAction can only have one of image, icon, or label",
|
||||
);
|
||||
throw new Error("SidebarAction can only have one of image, icon, or label");
|
||||
|
||||
const [pillType, setPillType] = React.useState<PillType>("none");
|
||||
const [isHovered, setHovered] = React.useState(false);
|
||||
@ -79,9 +70,12 @@ function SidebarAction(props: Props) {
|
||||
useGreenColorScheme={props.useGreenColorScheme}
|
||||
>
|
||||
{props.image && <img {...props.image} />}
|
||||
{props.icon && <Icon {...props.icon} color={
|
||||
isHovered && props.useGreenColorScheme ? "#fff" : props.icon.color
|
||||
} />}
|
||||
{props.icon && (
|
||||
<Icon
|
||||
{...props.icon}
|
||||
color={isHovered && props.useGreenColorScheme ? "#fff" : props.icon.color}
|
||||
/>
|
||||
)}
|
||||
{props.label && <span>{props.label}</span>}
|
||||
</Wrapper>
|
||||
</Tooltip>
|
||||
|
@ -1,7 +1,4 @@
|
||||
import MuiTooltip, {
|
||||
TooltipProps as MuiTooltipProps,
|
||||
tooltipClasses,
|
||||
} from "@mui/material/Tooltip";
|
||||
import MuiTooltip, { TooltipProps as MuiTooltipProps, tooltipClasses } from "@mui/material/Tooltip";
|
||||
import styled from "styled-components";
|
||||
|
||||
export default styled(({ className, ...props }: MuiTooltipProps) => (
|
||||
|
@ -67,20 +67,14 @@ function UserPanel() {
|
||||
<div>
|
||||
<Username>{app.account?.username}</Username>
|
||||
</div>
|
||||
<Discriminator>
|
||||
#{app.account?.discriminator}
|
||||
</Discriminator>
|
||||
<Discriminator>#{app.account?.discriminator}</Discriminator>
|
||||
</Name>
|
||||
</AvatarWrapper>
|
||||
|
||||
<ActionsWrapper>
|
||||
<Tooltip title="Settings">
|
||||
<span>
|
||||
<IconButton
|
||||
aria-label="settings"
|
||||
disabled
|
||||
color="#fff"
|
||||
>
|
||||
<IconButton aria-label="settings" disabled color="#fff">
|
||||
<Icon icon="mdiCog" size="20px" />
|
||||
</IconButton>
|
||||
</span>
|
||||
|
@ -22,12 +22,7 @@ function HCaptchaModal({ open, siteKey, onVerify }: Props) {
|
||||
|
||||
return open ? (
|
||||
<Wrapper>
|
||||
<HCaptchaLib
|
||||
sitekey={siteKey}
|
||||
onLoad={onLoad}
|
||||
onVerify={onVerify}
|
||||
ref={ref}
|
||||
/>
|
||||
<HCaptchaLib sitekey={siteKey} onLoad={onLoad} onVerify={onVerify} ref={ref} />
|
||||
</Wrapper>
|
||||
) : null;
|
||||
}
|
||||
|
@ -69,9 +69,7 @@ function AddServerModal() {
|
||||
|
||||
<ModalHeader>
|
||||
<ModalHeaderText>Add a Guild</ModalHeaderText>
|
||||
<ModalSubHeaderText>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
</ModalSubHeaderText>
|
||||
<ModalSubHeaderText>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</ModalSubHeaderText>
|
||||
</ModalHeader>
|
||||
|
||||
<ModelContentContainer>
|
||||
|
@ -7,13 +7,7 @@ import styled from "styled-components";
|
||||
import useLogger from "../../hooks/useLogger";
|
||||
import { useAppStore } from "../../stores/AppStore";
|
||||
import { messageFromFieldError } from "../../utils/messageFromFieldError";
|
||||
import {
|
||||
Input,
|
||||
InputErrorText,
|
||||
InputLabel,
|
||||
InputWrapper,
|
||||
LabelWrapper,
|
||||
} from "../AuthComponents";
|
||||
import { Input, InputErrorText, InputLabel, InputWrapper, LabelWrapper } from "../AuthComponents";
|
||||
import { Divider } from "../Divider";
|
||||
import Icon from "../Icon";
|
||||
import AddServerModal from "./AddServerModal";
|
||||
@ -169,20 +163,14 @@ function CreateServerModal() {
|
||||
<ModalHeader>
|
||||
<ModalHeaderText>Customize your guild</ModalHeaderText>
|
||||
<ModalSubHeaderText>
|
||||
Give your new guild a personality with a name and an
|
||||
icon. You can always change it later.
|
||||
Give your new guild a personality with a name and an icon. You can always change it later.
|
||||
</ModalSubHeaderText>
|
||||
</ModalHeader>
|
||||
|
||||
<ModelContentContainer>
|
||||
<UploadIcon>
|
||||
<IconContainer>
|
||||
<svg
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 80 80"
|
||||
fill="none"
|
||||
>
|
||||
<svg width="80" height="80" viewBox="0 0 80 80" fill="none">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
@ -244,12 +232,7 @@ function CreateServerModal() {
|
||||
</ModelContentContainer>
|
||||
|
||||
<ModalFooter>
|
||||
<ModalActionItem
|
||||
variant="filled"
|
||||
size="med"
|
||||
onClick={onSubmit}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<ModalActionItem variant="filled" size="med" onClick={onSubmit} disabled={isLoading}>
|
||||
Create
|
||||
</ModalActionItem>
|
||||
|
||||
|
@ -59,17 +59,12 @@ function ForgotPasswordModal() {
|
||||
</ModalHeader>
|
||||
|
||||
<ModelContentContainer>
|
||||
We sent instructions to change your password to
|
||||
user@example.com, please check both your inbox and spam
|
||||
folder.
|
||||
We sent instructions to change your password to user@example.com, please check both your inbox and
|
||||
spam folder.
|
||||
</ModelContentContainer>
|
||||
|
||||
<ModalFooter>
|
||||
<SubmitButton
|
||||
variant="filled"
|
||||
size="med"
|
||||
onClick={closeModal}
|
||||
>
|
||||
<SubmitButton variant="filled" size="med" onClick={closeModal}>
|
||||
Okay
|
||||
</SubmitButton>
|
||||
</ModalFooter>
|
||||
|
@ -139,9 +139,7 @@ function JoinServerModal() {
|
||||
|
||||
<ModalHeader>
|
||||
<ModalHeaderText>Join a Guild</ModalHeaderText>
|
||||
<ModalSubHeaderText>
|
||||
Enter an invite below to join an existing guild.
|
||||
</ModalSubHeaderText>
|
||||
<ModalSubHeaderText>Enter an invite below to join an existing guild.</ModalSubHeaderText>
|
||||
</ModalHeader>
|
||||
|
||||
<ModelContentContainer>
|
||||
|
@ -25,7 +25,8 @@ export const ModalWrapper = styled.div`
|
||||
width: 440px;
|
||||
border-radius: 10px;
|
||||
background-color: var(--background-secondary);
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05),
|
||||
box-shadow:
|
||||
0 1px 2px 0 rgba(0, 0, 0, 0.05),
|
||||
0 1px 3px 1px rgba(0, 0, 0, 0.05);
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
@ -5,6 +5,7 @@ import { useAppStore } from "../stores/AppStore";
|
||||
export type ThemeVariables =
|
||||
| "backgroundPrimary"
|
||||
| "backgroundPrimaryAlt"
|
||||
| "backgroundPrimaryHighlight"
|
||||
| "backgroundSecondary"
|
||||
| "backgroundSecondaryAlt"
|
||||
| "backgroundSecondaryHighlight"
|
||||
@ -52,6 +53,7 @@ export const ThemePresets: Record<string, Theme> = {
|
||||
light: {
|
||||
backgroundPrimary: "#ffffff",
|
||||
backgroundPrimaryAlt: "",
|
||||
backgroundPrimaryHighlight: "",
|
||||
backgroundSecondary: "#ebe5e4",
|
||||
backgroundSecondaryAlt: "#ebe5e4",
|
||||
backgroundSecondaryHighlight: "#ebe5e4",
|
||||
@ -90,6 +92,7 @@ export const ThemePresets: Record<string, Theme> = {
|
||||
dark: {
|
||||
backgroundPrimary: "#2e2e2e",
|
||||
backgroundPrimaryAlt: "#2a2a2a",
|
||||
backgroundPrimaryHighlight: "#262626",
|
||||
backgroundSecondary: "#232323",
|
||||
backgroundSecondaryAlt: "#1e1e1e",
|
||||
backgroundSecondaryHighlight: "#383838",
|
||||
@ -133,8 +136,7 @@ const GlobalTheme = createGlobalStyle<{ theme: Theme }>`
|
||||
}
|
||||
`;
|
||||
|
||||
const toDashed = (str: string) =>
|
||||
str.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
|
||||
const toDashed = (str: string) => str.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
|
||||
|
||||
export const generateVariables = (theme: Theme) => {
|
||||
return (Object.keys(theme) as ThemeVariables[]).map((key) => {
|
||||
@ -143,9 +145,7 @@ export const generateVariables = (theme: Theme) => {
|
||||
const r = parseInt(colour.substring(1, 3), 16);
|
||||
const g = parseInt(colour.substring(3, 5), 16);
|
||||
const b = parseInt(colour.substring(5, 7), 16);
|
||||
return `--${toDashed(key)}: ${theme[key]}; --${toDashed(
|
||||
key,
|
||||
)}-rgb: rgb(${r}, ${g}, ${b});`;
|
||||
return `--${toDashed(key)}: ${theme[key]}; --${toDashed(key)}-rgb: rgb(${r}, ${g}, ${b});`;
|
||||
} catch {
|
||||
return `--${toDashed(key)}: ${theme[key]};`;
|
||||
}
|
||||
|
@ -7,9 +7,7 @@ import App from "./App";
|
||||
import Theme from "./contexts/Theme";
|
||||
import "./index.css";
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById("root") as HTMLElement,
|
||||
);
|
||||
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
|
||||
root.render(
|
||||
<BrowserRouter>
|
||||
<ModalStack>
|
||||
|
@ -51,8 +51,7 @@ function LoginPage() {
|
||||
const navigate = useNavigate();
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [captchaSiteKey, setCaptchaSiteKey] = React.useState<string>();
|
||||
const [mfaData, setMfaData] =
|
||||
React.useState<IAPILoginResponseMFARequired>();
|
||||
const [mfaData, setMfaData] = React.useState<IAPILoginResponseMFARequired>();
|
||||
const captchaRef = React.useRef<HCaptchaLib>(null);
|
||||
const [debounce, setDebounce] = React.useState<NodeJS.Timeout | null>(null);
|
||||
const [isCheckingInstance, setCheckingInstance] = React.useState(false);
|
||||
@ -196,9 +195,7 @@ function LoginPage() {
|
||||
type: "manual",
|
||||
message:
|
||||
(e instanceof Error &&
|
||||
(e?.message?.length > 60
|
||||
? e.message.slice(0, 60) + "..."
|
||||
: e.message)) ||
|
||||
(e?.message?.length > 60 ? e.message.slice(0, 60) + "..." : e.message)) ||
|
||||
"Instance could not be resolved",
|
||||
});
|
||||
}
|
||||
@ -217,13 +214,7 @@ function LoginPage() {
|
||||
};
|
||||
|
||||
if (captchaSiteKey) {
|
||||
return (
|
||||
<HCaptcha
|
||||
captchaRef={captchaRef}
|
||||
sitekey={captchaSiteKey}
|
||||
onVerify={onCaptchaVerify}
|
||||
/>
|
||||
);
|
||||
return <HCaptcha captchaRef={captchaRef} sitekey={captchaSiteKey} onVerify={onCaptchaVerify} />;
|
||||
}
|
||||
|
||||
if (mfaData) {
|
||||
@ -247,10 +238,7 @@ function LoginPage() {
|
||||
</HeaderContainer>
|
||||
|
||||
<FormContainer onSubmit={onSubmit}>
|
||||
<InputContainer
|
||||
marginBottom={true}
|
||||
style={{ marginTop: 0 }}
|
||||
>
|
||||
<InputContainer marginBottom={true} style={{ marginTop: 0 }}>
|
||||
<LabelWrapper error={!!errors.instance}>
|
||||
<InputLabel>Instance</InputLabel>
|
||||
{isCheckingInstance != false && (
|
||||
@ -337,18 +325,12 @@ function LoginPage() {
|
||||
Forgot your password?
|
||||
</PasswordResetLink> */}
|
||||
|
||||
<SubmitButton
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
<SubmitButton variant="primary" type="submit" disabled={loading}>
|
||||
Login
|
||||
</SubmitButton>
|
||||
|
||||
<AuthSwitchPageContainer>
|
||||
<AuthSwitchPageLabel>
|
||||
New to Spacebar?
|
||||
</AuthSwitchPageLabel>
|
||||
<AuthSwitchPageLabel>New to Spacebar? </AuthSwitchPageLabel>
|
||||
<AuthSwitchPageLink
|
||||
onClick={() => {
|
||||
navigate("/register");
|
||||
|
@ -26,11 +26,7 @@ import { Divider } from "../components/Divider";
|
||||
import HCaptcha from "../components/HCaptcha";
|
||||
import useLogger from "../hooks/useLogger";
|
||||
import { AUTH_NO_BRANDING, useAppStore } from "../stores/AppStore";
|
||||
import {
|
||||
IAPILoginResponseSuccess,
|
||||
IAPIRegisterRequest,
|
||||
IAPIRegisterResponseError,
|
||||
} from "../utils/interfaces/api";
|
||||
import { IAPILoginResponseSuccess, IAPIRegisterRequest, IAPIRegisterResponseError } from "../utils/interfaces/api";
|
||||
import { messageFromFieldError } from "../utils/messageFromFieldError";
|
||||
|
||||
type FormValues = {
|
||||
@ -77,13 +73,10 @@ function RegistrationPage() {
|
||||
setValue("captcha_key", undefined);
|
||||
|
||||
app.rest
|
||||
.post<IAPIRegisterRequest, IAPILoginResponseSuccess>(
|
||||
Routes.register(),
|
||||
{
|
||||
...data,
|
||||
consent: true,
|
||||
},
|
||||
)
|
||||
.post<IAPIRegisterRequest, IAPILoginResponseSuccess>(Routes.register(), {
|
||||
...data,
|
||||
consent: true,
|
||||
})
|
||||
.then((r) => {
|
||||
if ("token" in r) {
|
||||
// success
|
||||
@ -163,13 +156,7 @@ function RegistrationPage() {
|
||||
};
|
||||
|
||||
if (captchaSiteKey) {
|
||||
return (
|
||||
<HCaptcha
|
||||
captchaRef={captchaRef}
|
||||
sitekey={captchaSiteKey}
|
||||
onVerify={onCaptchaVerify}
|
||||
/>
|
||||
);
|
||||
return <HCaptcha captchaRef={captchaRef} sitekey={captchaSiteKey} onVerify={onCaptchaVerify} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@ -187,10 +174,7 @@ function RegistrationPage() {
|
||||
)}
|
||||
|
||||
<FormContainer onSubmit={onSubmit}>
|
||||
<InputContainer
|
||||
marginBottom={true}
|
||||
style={{ marginTop: 0 }}
|
||||
>
|
||||
<InputContainer marginBottom={true} style={{ marginTop: 0 }}>
|
||||
<LabelWrapper error={!!errors.email}>
|
||||
<InputLabel>Email</InputLabel>
|
||||
{errors.email && (
|
||||
@ -214,10 +198,7 @@ function RegistrationPage() {
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<InputContainer
|
||||
marginBottom={true}
|
||||
style={{ marginTop: 0 }}
|
||||
>
|
||||
<InputContainer marginBottom={true} style={{ marginTop: 0 }}>
|
||||
<LabelWrapper error={!!errors.username}>
|
||||
<InputLabel>Username</InputLabel>
|
||||
{errors.username && (
|
||||
@ -277,20 +258,14 @@ function RegistrationPage() {
|
||||
|
||||
<InputWrapper>
|
||||
<DOBInput
|
||||
onChange={(value) =>
|
||||
setValue("date_of_birth", value)
|
||||
}
|
||||
onChange={(value) => setValue("date_of_birth", value)}
|
||||
onErrorChange={(errors) => {
|
||||
const hasError = Object.values(errors).some(
|
||||
(error) => error,
|
||||
);
|
||||
const hasError = Object.values(errors).some((error) => error);
|
||||
if (hasError) {
|
||||
// set to first error
|
||||
setError("date_of_birth", {
|
||||
type: "manual",
|
||||
message: Object.values(
|
||||
errors,
|
||||
).filter((x) => x)[0],
|
||||
message: Object.values(errors).filter((x) => x)[0],
|
||||
});
|
||||
} else clearErrors("date_of_birth");
|
||||
}}
|
||||
@ -300,18 +275,12 @@ function RegistrationPage() {
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<SubmitButton
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
<SubmitButton variant="primary" type="submit" disabled={loading}>
|
||||
Create Account
|
||||
</SubmitButton>
|
||||
|
||||
<AuthSwitchPageContainer>
|
||||
<AuthSwitchPageLabel>
|
||||
Already have an account?
|
||||
</AuthSwitchPageLabel>
|
||||
<AuthSwitchPageLabel>Already have an account? </AuthSwitchPageLabel>
|
||||
<AuthSwitchPageLink
|
||||
onClick={() => {
|
||||
navigate("/login");
|
||||
|
@ -99,16 +99,10 @@ function MFA(props: IAPILoginResponseMFARequired) {
|
||||
<HeaderContainer>
|
||||
<SpacebarLogoBlue height={48} width="auto" />
|
||||
<Header>Two-factor authentication</Header>
|
||||
<SubHeader>
|
||||
You can use a backup code or your two-factor
|
||||
authentication mobile app.
|
||||
</SubHeader>
|
||||
<SubHeader>You can use a backup code or your two-factor authentication mobile app.</SubHeader>
|
||||
|
||||
<FormContainer onSubmit={onSubmit}>
|
||||
<InputContainer
|
||||
marginBottom={true}
|
||||
style={{ marginTop: 0 }}
|
||||
>
|
||||
<InputContainer marginBottom={true} style={{ marginTop: 0 }}>
|
||||
<LabelWrapper error={!!errors.code}>
|
||||
<InputLabel>Enter 2FA/Backup Code</InputLabel>
|
||||
{errors.code && (
|
||||
@ -132,11 +126,7 @@ function MFA(props: IAPILoginResponseMFARequired) {
|
||||
</InputWrapper>
|
||||
</InputContainer>
|
||||
|
||||
<SubmitButton
|
||||
variant="primary"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
<SubmitButton variant="primary" type="submit" disabled={loading}>
|
||||
Log In
|
||||
</SubmitButton>
|
||||
|
||||
@ -154,10 +144,7 @@ function MFA(props: IAPILoginResponseMFARequired) {
|
||||
|
||||
<Link
|
||||
onClick={() => {
|
||||
window.open(
|
||||
"https://youtu.be/dQw4w9WgXcQ",
|
||||
"_blank",
|
||||
);
|
||||
window.open("https://youtu.be/dQw4w9WgXcQ", "_blank");
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
|
@ -20,10 +20,7 @@ export default class AccountStore {
|
||||
@observable bot = false;
|
||||
@observable system = false;
|
||||
@observable mfaEnabled = false;
|
||||
@observable premiumType?:
|
||||
| UserPremiumType.NitroClassic
|
||||
| UserPremiumType.Nitro
|
||||
| UserPremiumType.NitroBasic;
|
||||
@observable premiumType?: UserPremiumType.NitroClassic | UserPremiumType.Nitro | UserPremiumType.NitroBasic;
|
||||
@observable flags?: UserFlags;
|
||||
@observable publicFlags?: UserFlags;
|
||||
// phone: string | null;
|
||||
@ -69,9 +66,7 @@ export default class AccountStore {
|
||||
*/
|
||||
get defaultAvatarUrl(): string {
|
||||
return REST.makeCDNUrl(
|
||||
CDNRoutes.defaultUserAvatar(
|
||||
(Number(this.discriminator) % 5) as DefaultUserAvatarAssets,
|
||||
),
|
||||
CDNRoutes.defaultUserAvatar((Number(this.discriminator) % 5) as DefaultUserAvatarAssets),
|
||||
);
|
||||
}
|
||||
|
||||
@ -80,10 +75,7 @@ export default class AccountStore {
|
||||
* @returns The URL to the user's avatar or the default avatar if they don't have one.
|
||||
*/
|
||||
get avatarUrl(): string {
|
||||
if (this.avatar)
|
||||
return REST.makeCDNUrl(
|
||||
CDNRoutes.userAvatar(this.id, this.avatar, ImageFormat.PNG),
|
||||
);
|
||||
if (this.avatar) return REST.makeCDNUrl(CDNRoutes.userAvatar(this.id, this.avatar, ImageFormat.PNG));
|
||||
else return this.defaultAvatarUrl;
|
||||
}
|
||||
}
|
||||
|
@ -96,9 +96,7 @@ export default class AppStore {
|
||||
* Whether the app is done loading and ready to be displayed
|
||||
*/
|
||||
get isReady() {
|
||||
return (
|
||||
!this.isAppLoading && this.isGatewayReady && this.isNetworkConnected
|
||||
);
|
||||
return !this.isAppLoading && this.isGatewayReady && this.isNetworkConnected;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,28 +55,17 @@ export default class ChannelStore {
|
||||
category: Channel | null;
|
||||
}[] = [];
|
||||
|
||||
const categories = this.sortPosition(
|
||||
channels.filter((x) => x.type === ChannelType.GuildCategory),
|
||||
);
|
||||
const categorizedChannels = channels.filter(
|
||||
(x) => x.type !== ChannelType.GuildCategory && x.parentId !== null,
|
||||
);
|
||||
const categories = this.sortPosition(channels.filter((x) => x.type === ChannelType.GuildCategory));
|
||||
const categorizedChannels = channels.filter((x) => x.type !== ChannelType.GuildCategory && x.parentId !== null);
|
||||
const uncategorizedChannels = this.sortPosition(
|
||||
channels.filter(
|
||||
(x) =>
|
||||
x.type !== ChannelType.GuildCategory && x.parentId === null,
|
||||
),
|
||||
channels.filter((x) => x.type !== ChannelType.GuildCategory && x.parentId === null),
|
||||
);
|
||||
|
||||
// for each category, add an object containing the category and its children
|
||||
categories.forEach((category) => {
|
||||
result.push({
|
||||
id: category.id,
|
||||
children: this.sortPosition(
|
||||
categorizedChannels.filter(
|
||||
(x) => x.parentId === category.id,
|
||||
),
|
||||
),
|
||||
children: this.sortPosition(categorizedChannels.filter((x) => x.parentId === category.id)),
|
||||
category: category,
|
||||
});
|
||||
});
|
||||
|
@ -96,61 +96,24 @@ export default class GatewayConnectionStore {
|
||||
|
||||
private setupDispatchHandler() {
|
||||
this.dispatchHandlers.set(GatewayDispatchEvents.Ready, this.onReady);
|
||||
this.dispatchHandlers.set(
|
||||
GatewayDispatchEvents.Resumed,
|
||||
this.onResumed,
|
||||
);
|
||||
this.dispatchHandlers.set(
|
||||
GatewayDispatchEvents.GuildCreate,
|
||||
this.onGuildCreate,
|
||||
);
|
||||
this.dispatchHandlers.set(
|
||||
GatewayDispatchEvents.GuildUpdate,
|
||||
this.onGuildUpdate,
|
||||
);
|
||||
this.dispatchHandlers.set(
|
||||
GatewayDispatchEvents.GuildDelete,
|
||||
this.onGuildDelete,
|
||||
);
|
||||
this.dispatchHandlers.set(
|
||||
GatewayDispatchEvents.GuildMemberListUpdate,
|
||||
this.onGuildMemberListUpdate,
|
||||
);
|
||||
this.dispatchHandlers.set(GatewayDispatchEvents.Resumed, this.onResumed);
|
||||
this.dispatchHandlers.set(GatewayDispatchEvents.GuildCreate, this.onGuildCreate);
|
||||
this.dispatchHandlers.set(GatewayDispatchEvents.GuildUpdate, this.onGuildUpdate);
|
||||
this.dispatchHandlers.set(GatewayDispatchEvents.GuildDelete, this.onGuildDelete);
|
||||
this.dispatchHandlers.set(GatewayDispatchEvents.GuildMemberListUpdate, this.onGuildMemberListUpdate);
|
||||
|
||||
this.dispatchHandlers.set(
|
||||
GatewayDispatchEvents.ChannelCreate,
|
||||
this.onChannelCreate,
|
||||
);
|
||||
this.dispatchHandlers.set(
|
||||
GatewayDispatchEvents.ChannelDelete,
|
||||
this.onChannelDelete,
|
||||
);
|
||||
this.dispatchHandlers.set(GatewayDispatchEvents.ChannelCreate, this.onChannelCreate);
|
||||
this.dispatchHandlers.set(GatewayDispatchEvents.ChannelDelete, this.onChannelDelete);
|
||||
|
||||
this.dispatchHandlers.set(
|
||||
GatewayDispatchEvents.MessageCreate,
|
||||
this.onMessageCreate,
|
||||
);
|
||||
this.dispatchHandlers.set(
|
||||
GatewayDispatchEvents.MessageUpdate,
|
||||
this.onMessageUpdate,
|
||||
);
|
||||
this.dispatchHandlers.set(
|
||||
GatewayDispatchEvents.MessageDelete,
|
||||
this.onMessageDelete,
|
||||
);
|
||||
this.dispatchHandlers.set(GatewayDispatchEvents.MessageCreate, this.onMessageCreate);
|
||||
this.dispatchHandlers.set(GatewayDispatchEvents.MessageUpdate, this.onMessageUpdate);
|
||||
this.dispatchHandlers.set(GatewayDispatchEvents.MessageDelete, this.onMessageDelete);
|
||||
|
||||
this.dispatchHandlers.set(
|
||||
GatewayDispatchEvents.PresenceUpdate,
|
||||
this.onPresenceUpdate,
|
||||
);
|
||||
this.dispatchHandlers.set(GatewayDispatchEvents.PresenceUpdate, this.onPresenceUpdate);
|
||||
}
|
||||
|
||||
private onopen = () => {
|
||||
this.logger.debug(
|
||||
`[Connected] ${this.url} (took ${
|
||||
Date.now() - this.connectionStartTime!
|
||||
}ms)`,
|
||||
);
|
||||
this.logger.debug(`[Connected] ${this.url} (took ${Date.now() - this.connectionStartTime!}ms)`);
|
||||
this.readyState = WebSocket.OPEN;
|
||||
|
||||
this.handleIdentify();
|
||||
@ -204,9 +167,7 @@ export default class GatewayConnectionStore {
|
||||
}
|
||||
|
||||
if (this.socket.readyState !== WebSocket.OPEN) {
|
||||
this.logger.error(
|
||||
`Socket is not open; readyState: ${this.socket.readyState}`,
|
||||
);
|
||||
this.logger.error(`Socket is not open; readyState: ${this.socket.readyState}`);
|
||||
return;
|
||||
}
|
||||
this.logger.debug(`[Gateway] <- ${payload.op}`, payload);
|
||||
@ -287,9 +248,7 @@ export default class GatewayConnectionStore {
|
||||
private handleHello = (data: GatewayHelloData) => {
|
||||
this.heartbeatInterval = data.heartbeat_interval;
|
||||
this.logger.info(
|
||||
`[Hello] heartbeat interval: ${data.heartbeat_interval} (took ${
|
||||
Date.now() - this.connectionStartTime!
|
||||
}ms)`,
|
||||
`[Hello] heartbeat interval: ${data.heartbeat_interval} (took ${Date.now() - this.connectionStartTime!}ms)`,
|
||||
);
|
||||
this.startHeartbeater();
|
||||
};
|
||||
@ -355,14 +314,14 @@ export default class GatewayConnectionStore {
|
||||
}
|
||||
};
|
||||
|
||||
this.initialHeartbeatTimeout = setTimeout(() => {
|
||||
this.initialHeartbeatTimeout = null;
|
||||
this.heartbeater = setInterval(
|
||||
heartbeaterFn,
|
||||
this.heartbeatInterval!,
|
||||
);
|
||||
heartbeaterFn();
|
||||
}, Math.floor(Math.random() * this.heartbeatInterval!));
|
||||
this.initialHeartbeatTimeout = setTimeout(
|
||||
() => {
|
||||
this.initialHeartbeatTimeout = null;
|
||||
this.heartbeater = setInterval(heartbeaterFn, this.heartbeatInterval!);
|
||||
heartbeaterFn();
|
||||
},
|
||||
Math.floor(Math.random() * this.heartbeatInterval!),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -385,9 +344,7 @@ export default class GatewayConnectionStore {
|
||||
*/
|
||||
private handleHeartbeatTimeout = () => {
|
||||
this.logger.warn(
|
||||
`[Heartbeat ACK Timeout] should reconnect in ${(
|
||||
this.heartbeatInterval! / 1000
|
||||
).toFixed(2)} seconds`,
|
||||
`[Heartbeat ACK Timeout] should reconnect in ${(this.heartbeatInterval! / 1000).toFixed(2)} seconds`,
|
||||
);
|
||||
|
||||
this.socket?.close(4009);
|
||||
@ -453,9 +410,7 @@ export default class GatewayConnectionStore {
|
||||
* Processes a ready dispatch event
|
||||
*/
|
||||
private onReady = (data: GatewayReadyDispatchData) => {
|
||||
this.logger.info(
|
||||
`[Ready] took ${Date.now() - this.connectionStartTime!}ms`,
|
||||
);
|
||||
this.logger.info(`[Ready] took ${Date.now() - this.connectionStartTime!}ms`);
|
||||
const { session_id, guilds, users, user, private_channels } = data;
|
||||
this.sessionId = session_id;
|
||||
|
||||
@ -540,17 +495,13 @@ export default class GatewayConnectionStore {
|
||||
});
|
||||
};
|
||||
|
||||
private onGuildMemberListUpdate = (
|
||||
data: GatewayGuildMemberListUpdateDispatchData,
|
||||
) => {
|
||||
private onGuildMemberListUpdate = (data: GatewayGuildMemberListUpdateDispatchData) => {
|
||||
this.logger.debug("Received GuildMemberListUpdate event");
|
||||
const { guild_id } = data;
|
||||
const guild = this.app.guilds.get(guild_id);
|
||||
|
||||
if (!guild) {
|
||||
this.logger.warn(
|
||||
`[GuildMemberListUpdate] Guild ${guild_id} not found`,
|
||||
);
|
||||
this.logger.warn(`[GuildMemberListUpdate] Guild ${guild_id} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -565,9 +516,7 @@ export default class GatewayConnectionStore {
|
||||
|
||||
const guild = this.app.guilds.get(data.guild_id!);
|
||||
if (!guild) {
|
||||
this.logger.warn(
|
||||
`[ChannelCreate] Guild ${data.guild_id} not found for channel ${data.id}`,
|
||||
);
|
||||
this.logger.warn(`[ChannelCreate] Guild ${data.guild_id} not found for channel ${data.id}`);
|
||||
return;
|
||||
}
|
||||
guild.channels.add(data);
|
||||
@ -581,9 +530,7 @@ export default class GatewayConnectionStore {
|
||||
|
||||
const guild = this.app.guilds.get(data.guild_id!);
|
||||
if (!guild) {
|
||||
this.logger.warn(
|
||||
`[ChannelDelete] Guild ${data.guild_id} not found for channel ${data.id}`,
|
||||
);
|
||||
this.logger.warn(`[ChannelDelete] Guild ${data.guild_id} not found for channel ${data.id}`);
|
||||
return;
|
||||
}
|
||||
guild.channels.remove(data.id);
|
||||
@ -592,16 +539,12 @@ export default class GatewayConnectionStore {
|
||||
private onMessageCreate = (data: GatewayMessageCreateDispatchData) => {
|
||||
const guild = this.app.guilds.get(data.guild_id!);
|
||||
if (!guild) {
|
||||
this.logger.warn(
|
||||
`[MessageCreate] Guild ${data.guild_id} not found for channel ${data.id}`,
|
||||
);
|
||||
this.logger.warn(`[MessageCreate] Guild ${data.guild_id} not found for channel ${data.id}`);
|
||||
return;
|
||||
}
|
||||
const channel = guild.channels.get(data.channel_id);
|
||||
if (!channel) {
|
||||
this.logger.warn(
|
||||
`[MessageCreate] Channel ${data.channel_id} not found for message ${data.id}`,
|
||||
);
|
||||
this.logger.warn(`[MessageCreate] Channel ${data.channel_id} not found for message ${data.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -612,16 +555,12 @@ export default class GatewayConnectionStore {
|
||||
private onMessageUpdate = (data: GatewayMessageUpdateDispatchData) => {
|
||||
const guild = this.app.guilds.get(data.guild_id!);
|
||||
if (!guild) {
|
||||
this.logger.warn(
|
||||
`[MessageUpdate] Guild ${data.guild_id} not found for channel ${data.id}`,
|
||||
);
|
||||
this.logger.warn(`[MessageUpdate] Guild ${data.guild_id} not found for channel ${data.id}`);
|
||||
return;
|
||||
}
|
||||
const channel = guild.channels.get(data.channel_id);
|
||||
if (!channel) {
|
||||
this.logger.warn(
|
||||
`[MessageUpdate] Channel ${data.channel_id} not found for message ${data.id}`,
|
||||
);
|
||||
this.logger.warn(`[MessageUpdate] Channel ${data.channel_id} not found for message ${data.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -631,16 +570,12 @@ export default class GatewayConnectionStore {
|
||||
private onMessageDelete = (data: GatewayMessageDeleteDispatchData) => {
|
||||
const guild = this.app.guilds.get(data.guild_id!);
|
||||
if (!guild) {
|
||||
this.logger.warn(
|
||||
`[MessageDelete] Guild ${data.guild_id} not found for channel ${data.id}`,
|
||||
);
|
||||
this.logger.warn(`[MessageDelete] Guild ${data.guild_id} not found for channel ${data.id}`);
|
||||
return;
|
||||
}
|
||||
const channel = guild.channels.get(data.channel_id);
|
||||
if (!channel) {
|
||||
this.logger.warn(
|
||||
`[MessageDelete] Channel ${data.channel_id} not found for message ${data.id}`,
|
||||
);
|
||||
this.logger.warn(`[MessageDelete] Channel ${data.channel_id} not found for message ${data.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,7 @@ export default class GuildMemberListStore {
|
||||
@observable online_count: number;
|
||||
@observable list: (string | GuildMember)[] = [];
|
||||
|
||||
constructor(
|
||||
app: AppStore,
|
||||
guild: Guild,
|
||||
data: GatewayGuildMemberListUpdateDispatchData,
|
||||
) {
|
||||
constructor(app: AppStore, guild: Guild, data: GatewayGuildMemberListUpdateDispatchData) {
|
||||
this.app = app;
|
||||
this.guild = guild;
|
||||
|
||||
@ -48,9 +44,7 @@ export default class GuildMemberListStore {
|
||||
this.computeListData(data.ops);
|
||||
}
|
||||
|
||||
private computeListData(
|
||||
ops: GatewayGuildMemberListUpdateDispatchData["ops"],
|
||||
) {
|
||||
private computeListData(ops: GatewayGuildMemberListUpdateDispatchData["ops"]) {
|
||||
for (const i of ops) {
|
||||
const { op, items, range, item, index } = i;
|
||||
switch (op) {
|
||||
@ -65,17 +59,13 @@ export default class GuildMemberListStore {
|
||||
const role = this.guild.roles.get(item.group.id);
|
||||
|
||||
listData.push({
|
||||
title: `${(
|
||||
role?.name ?? item.group.id
|
||||
).toUpperCase()}`,
|
||||
title: `${(role?.name ?? item.group.id).toUpperCase()}`,
|
||||
data: [],
|
||||
});
|
||||
} else {
|
||||
// try to get the existing member
|
||||
if (item.member.user?.id) {
|
||||
const member = this.guild.members.get(
|
||||
item.member.user.id,
|
||||
);
|
||||
const member = this.guild.members.get(item.member.user.id);
|
||||
if (member) {
|
||||
listData[listData.length - 1].data.push({
|
||||
member,
|
||||
@ -85,11 +75,7 @@ export default class GuildMemberListStore {
|
||||
}
|
||||
}
|
||||
listData[listData.length - 1].data.push({
|
||||
member: new GuildMember(
|
||||
this.app,
|
||||
this.guild,
|
||||
item.member,
|
||||
),
|
||||
member: new GuildMember(this.app, this.guild, item.member),
|
||||
index: item.member.index,
|
||||
});
|
||||
}
|
||||
@ -105,11 +91,7 @@ export default class GuildMemberListStore {
|
||||
|
||||
// hide offline group if it has more than 100 members
|
||||
listData = listData.filter(
|
||||
(i) =>
|
||||
!(
|
||||
i.title.toLowerCase().startsWith("offline") &&
|
||||
i.data.length >= 100
|
||||
),
|
||||
(i) => !(i.title.toLowerCase().startsWith("offline") && i.data.length >= 100),
|
||||
);
|
||||
|
||||
// sort the list by the index
|
||||
@ -125,9 +107,7 @@ export default class GuildMemberListStore {
|
||||
const ua = a.member.user?.username;
|
||||
const ub = b.member.user?.username;
|
||||
if (ua && ub) {
|
||||
return ua.toLowerCase() > ub.toLowerCase()
|
||||
? 1
|
||||
: -1;
|
||||
return ua.toLowerCase() > ub.toLowerCase() ? 1 : -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -9,10 +9,7 @@ export default class GuildMemberStore {
|
||||
private readonly app: AppStore;
|
||||
private readonly guild: Guild;
|
||||
|
||||
@observable private readonly members = new ObservableMap<
|
||||
Snowflake,
|
||||
GuildMember
|
||||
>();
|
||||
@observable private readonly members = new ObservableMap<Snowflake, GuildMember>();
|
||||
|
||||
constructor(app: AppStore, guild: Guild) {
|
||||
this.app = app;
|
||||
@ -29,10 +26,7 @@ export default class GuildMemberStore {
|
||||
if (this.members.has(member.user.id)) {
|
||||
return;
|
||||
}
|
||||
this.members.set(
|
||||
member.user.id,
|
||||
new GuildMember(this.app, this.guild, member),
|
||||
);
|
||||
this.members.set(member.user.id, new GuildMember(this.app, this.guild, member));
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -17,19 +17,13 @@ export default class PresenceStore {
|
||||
Snowflake,
|
||||
Map<
|
||||
Snowflake,
|
||||
Pick<
|
||||
GatewayPresenceUpdate,
|
||||
"activities" | "client_status" | "status"
|
||||
> & {
|
||||
Pick<GatewayPresenceUpdate, "activities" | "client_status" | "status"> & {
|
||||
timestamp: number;
|
||||
}
|
||||
>
|
||||
>();
|
||||
@observable activities = observable.map<Snowflake, GatewayActivity[]>();
|
||||
@observable clientStatuses = observable.map<
|
||||
Snowflake,
|
||||
OneKeyFrom<GatewayPresenceClientStatus>
|
||||
>();
|
||||
@observable clientStatuses = observable.map<Snowflake, OneKeyFrom<GatewayPresenceClientStatus>>();
|
||||
|
||||
constructor(app: AppStore) {
|
||||
this.app = app;
|
||||
@ -38,11 +32,7 @@ export default class PresenceStore {
|
||||
}
|
||||
|
||||
@action
|
||||
add(
|
||||
presence:
|
||||
| GatewayPresenceUpdate
|
||||
| GatewayGuildMemberListUpdateMember["presence"],
|
||||
) {
|
||||
add(presence: GatewayPresenceUpdate | GatewayGuildMemberListUpdateMember["presence"]) {
|
||||
if (presence.status) {
|
||||
this.presences.set(presence.user.id, presence.status);
|
||||
}
|
||||
|
@ -86,8 +86,7 @@ export default class Channel {
|
||||
this.readStates = channel.read_states;
|
||||
this.webhooks = channel.webhooks;
|
||||
this.flags = channel.flags;
|
||||
this.defaultThreadRateLimitPerUser =
|
||||
channel.default_thread_rate_limit_per_user;
|
||||
this.defaultThreadRateLimitPerUser = channel.default_thread_rate_limit_per_user;
|
||||
|
||||
if (channel.messages) {
|
||||
this.messages.addAll(channel.messages);
|
||||
@ -174,10 +173,7 @@ export default class Channel {
|
||||
this.hasFetchedMessages = true;
|
||||
this.logger.info(`Fetching messags for ${this.id}`);
|
||||
app.rest
|
||||
.get<RESTGetAPIChannelMessagesResult | APIError>(
|
||||
Routes.channelMessages(this.id),
|
||||
opts,
|
||||
)
|
||||
.get<RESTGetAPIChannelMessagesResult | APIError>(Routes.channelMessages(this.id), opts)
|
||||
.then((res) => {
|
||||
if ("code" in res) {
|
||||
this.logger.error(res);
|
||||
@ -200,10 +196,10 @@ export default class Channel {
|
||||
@action
|
||||
async sendMessage(data: RESTPostAPIChannelMessageJSONBody) {
|
||||
// TODO: handle errors, highlight message as failed
|
||||
return this.app.rest.post<
|
||||
RESTPostAPIChannelMessageJSONBody,
|
||||
RESTPostAPIChannelMessageResult
|
||||
>(Routes.channelMessages(this.id), data);
|
||||
return this.app.rest.post<RESTPostAPIChannelMessageJSONBody, RESTPostAPIChannelMessageResult>(
|
||||
Routes.channelMessages(this.id),
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
canSendMessage(content: string) {
|
||||
|
@ -86,8 +86,7 @@ export default class Guild {
|
||||
this.systemChannelId = data.properties.system_channel_id;
|
||||
this.verificationLevel = data.properties.verification_level;
|
||||
this.explicitContentFilter = data.properties.explicit_content_filter;
|
||||
this.defaultMessageNotifications =
|
||||
data.properties.default_message_notifications;
|
||||
this.defaultMessageNotifications = data.properties.default_message_notifications;
|
||||
this.mfaLevel = data.properties.mfa_level;
|
||||
this.vanityUrlCode = data.properties.vanity_url_code;
|
||||
this.premiumTier = data.properties.premium_tier;
|
||||
@ -125,11 +124,7 @@ export default class Guild {
|
||||
if (this.memberListStore) {
|
||||
this.memberListStore.update(data);
|
||||
} else {
|
||||
this.memberListStore = new GuildMemberListStore(
|
||||
this.app,
|
||||
this,
|
||||
data,
|
||||
);
|
||||
this.memberListStore = new GuildMemberListStore(this.app, this, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,20 +25,14 @@ export default class GuildMember {
|
||||
@observable pending?: boolean | undefined;
|
||||
@observable communication_disabled_until?: string | null | undefined;
|
||||
|
||||
constructor(
|
||||
app: AppStore,
|
||||
guild: Guild,
|
||||
data: APIGuildMember | GatewayGuildMemberListUpdateMember,
|
||||
) {
|
||||
constructor(app: AppStore, guild: Guild, data: APIGuildMember | GatewayGuildMemberListUpdateMember) {
|
||||
this.app = app;
|
||||
this.guild = guild;
|
||||
|
||||
this.user = data.user;
|
||||
this.nick = data.nick;
|
||||
this.avatar = data.avatar;
|
||||
this.roles = data.roles
|
||||
.map((role) => guild.roles.get(role))
|
||||
.filter(Boolean) as Role[];
|
||||
this.roles = data.roles.map((role) => guild.roles.get(role)).filter(Boolean) as Role[];
|
||||
this.joined_at = data.joined_at;
|
||||
this.premium_since = data.premium_since;
|
||||
this.deaf = data.deaf;
|
||||
|
@ -238,9 +238,7 @@ export default class Message {
|
||||
// this.member = message.member ? new GuildMember(message.member) : undefined;
|
||||
this.content = message.content;
|
||||
this.timestamp = new Date(message.timestamp);
|
||||
this.edited_timestamp = message.edited_timestamp
|
||||
? new Date(message.edited_timestamp)
|
||||
: null;
|
||||
this.edited_timestamp = message.edited_timestamp ? new Date(message.edited_timestamp) : null;
|
||||
this.tts = message.tts;
|
||||
this.mention_everyone = message.mention_everyone;
|
||||
this.mentions = message.mentions; // TODO: user object?
|
||||
@ -280,8 +278,6 @@ export default class Message {
|
||||
Object.assign(this, message);
|
||||
|
||||
this.timestamp = new Date(message.timestamp);
|
||||
this.edited_timestamp = message.edited_timestamp
|
||||
? new Date(message.edited_timestamp)
|
||||
: null;
|
||||
this.edited_timestamp = message.edited_timestamp ? new Date(message.edited_timestamp) : null;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,5 @@
|
||||
import { Snowflake } from "@spacebarchat/spacebar-api-types/globals";
|
||||
import {
|
||||
APIUser,
|
||||
CDNRoutes,
|
||||
DefaultUserAvatarAssets,
|
||||
ImageFormat,
|
||||
} from "@spacebarchat/spacebar-api-types/v9";
|
||||
import { APIUser, CDNRoutes, DefaultUserAvatarAssets, ImageFormat } from "@spacebarchat/spacebar-api-types/v9";
|
||||
import { makeObservable, observable } from "mobx";
|
||||
import REST from "../../utils/REST";
|
||||
|
||||
@ -55,9 +50,7 @@ export default class User {
|
||||
*/
|
||||
get defaultAvatarUrl(): string {
|
||||
return REST.makeCDNUrl(
|
||||
CDNRoutes.defaultUserAvatar(
|
||||
(Number(this.discriminator) % 5) as DefaultUserAvatarAssets,
|
||||
),
|
||||
CDNRoutes.defaultUserAvatar((Number(this.discriminator) % 5) as DefaultUserAvatarAssets),
|
||||
);
|
||||
}
|
||||
|
||||
@ -66,10 +59,7 @@ export default class User {
|
||||
* @returns The URL to the user's avatar or the default avatar if they don't have one.
|
||||
*/
|
||||
get avatarUrl(): string {
|
||||
if (this.avatar)
|
||||
return REST.makeCDNUrl(
|
||||
CDNRoutes.userAvatar(this.id, this.avatar, ImageFormat.PNG),
|
||||
);
|
||||
if (this.avatar) return REST.makeCDNUrl(CDNRoutes.userAvatar(this.id, this.avatar, ImageFormat.PNG));
|
||||
else return this.defaultAvatarUrl;
|
||||
}
|
||||
}
|
||||
|
@ -33,10 +33,7 @@ export const Globals: {
|
||||
logger.info("Loaded route settings from storage");
|
||||
},
|
||||
save: () => {
|
||||
localStorage.setItem(
|
||||
"routeSettings",
|
||||
JSON.stringify(Globals.routeSettings),
|
||||
);
|
||||
localStorage.setItem("routeSettings", JSON.stringify(Globals.routeSettings));
|
||||
},
|
||||
routeSettings: DefaultRouteSettings,
|
||||
};
|
||||
|
@ -6,34 +6,18 @@ export default class Logger {
|
||||
}
|
||||
|
||||
debug(...args: any[]) {
|
||||
console.debug(
|
||||
`%c${new Date().toLocaleTimeString()} | ${this.name} | DEBUG |`,
|
||||
`color: LimeGreen`,
|
||||
...args,
|
||||
);
|
||||
console.debug(`%c${new Date().toLocaleTimeString()} | ${this.name} | DEBUG |`, `color: LimeGreen`, ...args);
|
||||
}
|
||||
|
||||
info(...args: any[]) {
|
||||
console.debug(
|
||||
`%c${new Date().toLocaleTimeString()} | ${this.name} | DEBUG |`,
|
||||
`color: DodgerBlue`,
|
||||
...args,
|
||||
);
|
||||
console.debug(`%c${new Date().toLocaleTimeString()} | ${this.name} | DEBUG |`, `color: DodgerBlue`, ...args);
|
||||
}
|
||||
|
||||
warn(...args: any[]) {
|
||||
console.debug(
|
||||
`%c${new Date().toLocaleTimeString()} | ${this.name} | DEBUG |`,
|
||||
`color: Tomato`,
|
||||
...args,
|
||||
);
|
||||
console.debug(`%c${new Date().toLocaleTimeString()} | ${this.name} | DEBUG |`, `color: Tomato`, ...args);
|
||||
}
|
||||
|
||||
error(...args: any[]) {
|
||||
console.debug(
|
||||
`%c${new Date().toLocaleTimeString()} | ${this.name} | DEBUG |`,
|
||||
`color: Red`,
|
||||
...args,
|
||||
);
|
||||
console.debug(`%c${new Date().toLocaleTimeString()} | ${this.name} | DEBUG |`, `color: Red`, ...args);
|
||||
}
|
||||
}
|
||||
|
@ -29,9 +29,7 @@ export default class REST {
|
||||
}
|
||||
}
|
||||
|
||||
public static async getEndpointsFromDomain(
|
||||
url: URL,
|
||||
): Promise<RouteSettings> {
|
||||
public static async getEndpointsFromDomain(url: URL): Promise<RouteSettings> {
|
||||
try {
|
||||
return await this.getInstanceDomains(url, url);
|
||||
} catch (e) {
|
||||
@ -47,14 +45,9 @@ export default class REST {
|
||||
return await this.getInstanceDomains(wellKnown, url);
|
||||
}
|
||||
|
||||
static async getInstanceDomains(
|
||||
url: URL,
|
||||
knownas: URL,
|
||||
): Promise<RouteSettings> {
|
||||
static async getInstanceDomains(url: URL, knownas: URL): Promise<RouteSettings> {
|
||||
const endpoints = await fetch(
|
||||
`${url.toString()}${
|
||||
url.pathname.includes("api") ? "" : "api"
|
||||
}/policies/instance/domains`,
|
||||
`${url.toString()}${url.pathname.includes("api") ? "" : "api"}/policies/instance/domains`,
|
||||
).then((x) => x.json());
|
||||
return {
|
||||
api: endpoints.apiEndpoint,
|
||||
@ -125,11 +118,7 @@ export default class REST {
|
||||
resolve(await res.json());
|
||||
} else {
|
||||
// reject with json if content type is json
|
||||
if (
|
||||
res.headers
|
||||
.get("content-type")
|
||||
?.includes("application/json")
|
||||
) {
|
||||
if (res.headers.get("content-type")?.includes("application/json")) {
|
||||
return reject(await res.json());
|
||||
}
|
||||
|
||||
|
@ -17,9 +17,7 @@ export default class Snowflake {
|
||||
static workerId = BigInt(0 % 31); // max 31
|
||||
|
||||
constructor() {
|
||||
throw new Error(
|
||||
`The ${this.constructor.name} class may not be instantiated.`,
|
||||
);
|
||||
throw new Error(`The ${this.constructor.name} class may not be instantiated.`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,9 +112,7 @@ export default class Snowflake {
|
||||
* @returns {DeconstructedSnowflake} Deconstructed snowflake
|
||||
*/
|
||||
static deconstruct(snowflake) {
|
||||
const BINARY = Snowflake.idToBinary(snowflake)
|
||||
.toString(2)
|
||||
.padStart(64, "0");
|
||||
const BINARY = Snowflake.idToBinary(snowflake).toString(2).padStart(64, "0");
|
||||
const res = {
|
||||
timestamp: parseInt(BINARY.substring(0, 42), 2) + Snowflake.EPOCH,
|
||||
workerID: parseInt(BINARY.substring(42, 47), 2),
|
||||
|
@ -60,14 +60,9 @@ export interface IAPIError {
|
||||
};
|
||||
}
|
||||
|
||||
export type IAPILoginResponse =
|
||||
| IAPILoginResponseSuccess
|
||||
| IAPILoginResponseMFARequired;
|
||||
export type IAPILoginResponse = IAPILoginResponseSuccess | IAPILoginResponseMFARequired;
|
||||
|
||||
export type IAPILoginResponseError =
|
||||
| IAPILoginResponseMFARequired
|
||||
| IAPIResponseCaptchaRequired
|
||||
| IAPIError;
|
||||
export type IAPILoginResponseError = IAPILoginResponseMFARequired | IAPIResponseCaptchaRequired | IAPIError;
|
||||
|
||||
export interface IAPILoginRequest {
|
||||
login: string;
|
||||
@ -118,9 +113,9 @@ export interface APIError {
|
||||
_errors: {
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// export type RESTAPIPostInviteResponse = {} | IAPIError;
|
||||
|
@ -4,9 +4,7 @@ export type OneKeyFrom<
|
||||
K extends keyof T = keyof T,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
> = K extends any
|
||||
? M &
|
||||
Pick<Required<T>, K> &
|
||||
Partial<Record<Exclude<keyof T, K>, never>> extends infer O
|
||||
? M & Pick<Required<T>, K> & Partial<Record<Exclude<keyof T, K>, never>> extends infer O
|
||||
? { [P in keyof O]: O[P] }
|
||||
: never
|
||||
: never;
|
||||
|
Loading…
Reference in New Issue
Block a user