1
0
mirror of https://github.com/spacebarchat/client.git synced 2024-11-22 02:12:38 +01:00

some refactoring

This commit is contained in:
Puyodead1 2024-10-15 15:28:03 -04:00
parent c480f86799
commit 7b82cc7b79
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
160 changed files with 1928 additions and 1069 deletions

10
.gitignore vendored
View File

@ -1,15 +1,15 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
node_modules
.pnp
.pnp.js
# testing
/coverage
coverage
# production
/dist
dist
# misc
.DS_Store
@ -22,3 +22,5 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
src-tauri/.cargo/config.toml
.swc/

View File

@ -1,2 +1,3 @@
{
"references.preferredLocation": "view"
}

View File

@ -14,7 +14,6 @@
"@mattjennings/react-modal-stack": "^1.0.4",
"@mdi/js": "^7.4.47",
"@mdi/react": "^1.6.1",
"@mui/material": "^6.1.3",
"@originjs/vite-plugin-commonjs": "^1.0.3",
"@rollup/plugin-replace": "^6.0.1",
"@spacebarchat/spacebar-api-types": "0.37.51",
@ -75,6 +74,7 @@
},
"devDependencies": {
"@craco/craco": "^7.1.0",
"@swc/plugin-styled-components": "^3.0.3",
"@tauri-apps/cli": "2.0.2",
"@types/jest": "^29.5.13",
"@types/loadable__component": "^5.13.9",
@ -88,10 +88,12 @@
"@typescript-eslint/eslint-plugin": "^8.7.0",
"@typescript-eslint/parser": "^8.7.0",
"@vitejs/plugin-react": "^4.3.2",
"@vitejs/plugin-react-swc": "^3.7.1",
"cross-env": "^7.0.3",
"eslint": "^8.57.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.12",
"source-map-explorer": "^2.5.3",
"typescript": "^5.6.3",
"vite": "^5.4.8",
"vite-plugin-chunk-split": "^0.5.0",
@ -108,12 +110,14 @@
"url": "git+https://github.com/spacebarchat/client.git"
},
"scripts": {
"dev": "vite --open",
"preview": "vite preview",
"analyze": "source-map-explorer 'dist/asset/*.js'",
"build": "tsc && vite build",
"build:dev": "tsc && cross-env VITE_ENV_DEV=true vite build",
"ci:prebuild": "node scripts/tauri-version.js",
"dev": "vite",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"lint:fix": "pnpx prettier . --write",
"preview": "vite preview",
"tauri:dev": "pnpm run ci:prebuild && tauri dev",
"tauri:build": "pnpm run ci:prebuild && tauri build",
"tauri:android:dev": "pnpm run ci:prebuild && tauri android dev",

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +1,26 @@
import { observer } from "mobx-react-lite";
import React from "react";
import { Route, Routes, useNavigate } from "react-router-dom";
import { AuthenticationGuard } from "./components/guards/AuthenticationGuard";
import LoginPage from "./pages/LoginPage";
import NotFoundPage from "./pages/NotFound";
import RegistrationPage from "./pages/RegistrationPage";
import { bannerController } from "@/controllers/banners";
import { getTauriVersion, getVersion } from "@tauri-apps/api/app";
import { arch, locale, platform, version } from "@tauri-apps/plugin-os";
import { useNetworkState } from "@uidotdev/usehooks";
import { reaction } from "mobx";
import { observer } from "mobx-react-lite";
import React from "react";
import { Route, Routes, useNavigate } from "react-router-dom";
import ErrorBoundary from "./components/ErrorBoundary";
import Loader from "./components/Loader";
import { UnauthenticatedGuard } from "./components/guards/UnauthenticatedGuard";
import useLogger from "./hooks/useLogger";
import AppPage from "./pages/AppPage";
import LoginPage from "./pages/LoginPage";
import LogoutPage from "./pages/LogoutPage";
import NotFoundPage from "./pages/NotFound";
import RegistrationPage from "./pages/RegistrationPage";
import ChannelPage from "./pages/subpages/ChannelPage";
import { Globals } from "./utils/Globals";
// @ts-expect-error no types
import FPSStats from "react-fps-stats";
import { bannerController } from "./controllers/banners";
import AuthenticationGuard from "./components/AuthenticationGuard";
import { useAppStore } from "./hooks/useAppStore";
import InvitePage from "./pages/InvitePage";
import { isTauri } from "./utils/Utils";
function App() {
@ -69,7 +68,7 @@ function App() {
};
};
isTauri && loadAsyncGlobals();
if (isTauri) loadAsyncGlobals();
Globals.load();
app.loadSettings();
@ -104,9 +103,16 @@ function App() {
path="/channels/:guildId/:channelId?"
element={<AuthenticationGuard component={ChannelPage} />}
/>
<Route path="/login" element={<UnauthenticatedGuard component={LoginPage} />} />
<Route path="/register" element={<UnauthenticatedGuard component={RegistrationPage} />} />
<Route
path="/login"
element={<AuthenticationGuard requireUnauthenticated component={LoginPage} />}
/>
<Route
path="/register"
element={<AuthenticationGuard requireUnauthenticated component={RegistrationPage} />}
/>
<Route path="/logout" element={<AuthenticationGuard component={LogoutPage} />} />
<Route path="/invite/:code" element={<InvitePage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</Loader>

View File

@ -1,6 +1,6 @@
import Container from "@components/Container";
import styled from "styled-components";
import Button from "./Button";
import Container from "./Container";
export const Wrapper = styled(Container)`
display: flex;

View File

@ -0,0 +1,29 @@
import { useAppStore } from "@hooks/useAppStore";
import { LoadingSuspense } from "@pages/LoadingPage";
import { Navigate } from "react-router-dom";
interface Props {
component: React.FC;
requireUnauthenticated?: boolean;
}
export default function AuthenticationGuard({ component, requireUnauthenticated }: Props) {
const app = useAppStore();
// if we need the user to be logged in, and there isn't a token, go to login page
if (!requireUnauthenticated && !app.token) {
return <Navigate to="/login" replace />;
}
// if we need the user to be logged out to access the page, but there is a token, go to the app page
if (requireUnauthenticated && app.token) {
return <Navigate to="/app" replace />;
}
const Component = component;
return (
<LoadingSuspense>
<Component />
</LoadingSuspense>
);
}

View File

@ -1,12 +1,11 @@
import Container from "@components/Container";
import { useAppStore } from "@hooks/useAppStore";
import { PresenceUpdateStatus } from "@spacebarchat/spacebar-api-types/v9";
import { AccountStore } from "@stores";
import { Presence, User } from "@structures";
import { observer } from "mobx-react-lite";
import React, { useRef } from "react";
import styled from "styled-components";
import { useAppStore } from "../hooks/useAppStore";
import AccountStore from "../stores/AccountStore";
import Presence from "../stores/objects/Presence";
import User from "../stores/objects/User";
import Container from "./Container";
const Wrapper = styled(Container)<{ size: number; hasClick?: boolean }>`
background-color: transparent;

View File

@ -1,11 +1,10 @@
import { Floating, FloatingTrigger } from "@components/floating";
import { useAppStore } from "@hooks/useAppStore";
import { observer } from "mobx-react-lite";
import React, { useEffect } from "react";
import styled from "styled-components";
import { useAppStore } from "../hooks/useAppStore";
import Icon, { IconProps } from "./Icon";
import { SectionHeader } from "./SectionHeader";
import Floating from "./floating/Floating";
import FloatingTrigger from "./floating/FloatingTrigger";
const Wrapper = styled(SectionHeader)`
background-color: var(--background-secondary);

View File

@ -1,8 +1,8 @@
import { useAppStore } from "@hooks/useAppStore";
import { ChannelType } from "@spacebarchat/spacebar-api-types/v9";
import { observer } from "mobx-react-lite";
import { AutoSizer, List, ListRowProps } from "react-virtualized";
import styled from "styled-components";
import { useAppStore } from "../../hooks/useAppStore";
import ChannelListItem from "./ChannelListItem";
const Container = styled.div`

View File

@ -1,16 +1,15 @@
import { modalController } from "@/controllers/modals";
import { Floating, FloatingTrigger } from "@components/floating";
import Icon from "@components/Icon";
import SidebarPill from "@components/SidebarPill";
import { ContextMenuContext } from "@contexts/ContextMenuContext";
import { useAppStore } from "@hooks/useAppStore";
import { Channel } from "@structures";
import { Permissions } from "@utils";
import { observer } from "mobx-react-lite";
import React, { useContext, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import { ContextMenuContext } from "../../contexts/ContextMenuContext";
import { modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import Channel from "../../stores/objects/Channel";
import { Permissions } from "../../utils/Permissions";
import Icon from "../Icon";
import SidebarPill from "../SidebarPill";
import Floating from "../floating/Floating";
import FloatingTrigger from "../floating/FloatingTrigger";
const ListItem = styled.div<{ isCategory?: boolean }>`
padding: ${(props) => (props.isCategory ? "16px 8px 0 0" : "1px 8px 0 0")};

View File

@ -1,12 +1,12 @@
import Container from "@components/Container";
import { useWindowSize } from "@uidotdev/usehooks";
import { isTouchscreenDevice } from "@utils";
import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
import { isDesktop } from "react-device-detect";
import styled from "styled-components";
import { isTouchscreenDevice } from "../utils/isTouchscreenDevice";
import ChannelHeader from "./ChannelHeader";
import ChannelList from "./ChannelList/ChannelList";
import Container from "./Container";
import UserPanel from "./UserPanel";
const Wrapper = styled(Container)`

View File

@ -1,18 +1,16 @@
import Container from "@components/Container";
import { Floating, FloatingTrigger } from "@components/floating";
import { ContextMenuContext } from "@contexts/ContextMenuContext";
import { useAppStore } from "@hooks/useAppStore";
import useLogger from "@hooks/useLogger";
import { CDNRoutes, ChannelType, ImageFormat } from "@spacebarchat/spacebar-api-types/v9";
import { Guild } from "@structures";
import { Permissions, REST } from "@utils";
import { observer } from "mobx-react-lite";
import React, { useContext } from "react";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import { ContextMenuContext } from "../contexts/ContextMenuContext";
import { useAppStore } from "../hooks/useAppStore";
import useLogger from "../hooks/useLogger";
import Guild from "../stores/objects/Guild";
import { Permissions } from "../utils/Permissions";
import REST from "../utils/REST";
import Container from "./Container";
import SidebarPill, { PillType } from "./SidebarPill";
import Floating from "./floating/Floating";
import FloatingTrigger from "./floating/FloatingTrigger";
export const GuildSidebarListItem = styled.div`
position: relative;

View File

@ -1,10 +1,10 @@
import { modalController } from "@/controllers/modals";
import { useAppStore } from "@hooks/useAppStore";
import { observer } from "mobx-react-lite";
import React from "react";
import { useNavigate } from "react-router-dom";
import { AutoSizer, List, ListRowProps } from "react-virtualized";
import styled from "styled-components";
import { modalController } from "../controllers/modals";
import { useAppStore } from "../hooks/useAppStore";
import GuildItem, { GuildSidebarListItem } from "./GuildItem";
import SidebarAction from "./SidebarAction";

View File

@ -0,0 +1,271 @@
import Container from "@components/Container";
import { useAppStore } from "@hooks/useAppStore";
import useLogger from "@hooks/useLogger";
import { APIInvite, CDNRoutes, ImageFormat, Routes } from "@spacebarchat/spacebar-api-types/v9";
import { asAcronym, REST } from "@utils";
import { useEffect, useState } from "react";
import styled from "styled-components";
import Button from "./Button";
const MainContainer = styled(Container)`
background-color: var(--background-secondary);
border-radius: 8px;
max-width: 430px;
min-width: 160px;
padding: 16px;
display: flex;
flex-direction: column;
justify-self: start;
align-self: start;
width: 100%;
`;
const Wrapper = styled.div``;
const Header = styled.h3`
margin: 0 0 12px 0;
font-size: 16px;
font-weight: var(--font-weight-medium);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
color: var(--text-secondary);
`;
const SplashWrapper = styled.div`
border-radius: 8px 8px 0 0;
height: 64px;
margin: -16px -16px 16px -16px;
overflow: hidden;
`;
const Splash = styled.img`
object-fit: cover;
width: 100%;
height: 100%;
`;
const ContentWrapper = styled.div`
display: flex;
flex-flow: row wrap;
gap: 16px;
`;
const GuildHeader = styled.div`
display: flex;
flex: 1000 0 auto;
align-items: center;
max-width: 100%;
gap: 16px;
`;
const GuildInfo = styled.div`
display: flex;
flex: 1;
min-width: 1px;
flex-direction: column;
justify-content: center;
align-items: stretch;
flex-wrap: nowrap;
`;
const GuildName = styled.h3`
margin: 0 0 2px 0;
font-size: 16px;
font-weight: var(--font-weight-medium);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
color: var(--text-header);
`;
const GuildDetailsWrapper = styled.div`
display: flex;
flex-flow: row wrap;
align-items: center;
column-gap: 12px;
`;
const StatusWrapper = styled.div`
display: flex;
flex: 0 1 auto;
align-items: center;
flex-flow: nowrap;
min-width: 0;
`;
const OnlineStatus = styled.div`
background-color: var(--success);
border-radius: 50%;
height: 8px;
width: 8px;
margin-right: 4px;
flex: 0 0 auto;
`;
const OfflineStatus = styled.div`
background-color: var(--status-offline);
border-radius: 50%;
height: 8px;
width: 8px;
margin-right: 4px;
flex: 0 0 auto;
`;
const StatusCount = styled.span`
margin-right: 0;
flex: 0 1 auto;
color: var(--text-header-secondary);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-size: 14px;
`;
const InvalidInviteHeader = styled(GuildName)`
color: var(--error);
`;
const InvalidInviteDetails = styled.span`
font-size: 14px;
color: var(--text-header-secondary);
font-weight: var(--font-weight-regular);
`;
const JoinButton = styled(Button)`
height: 40px;
align-self: center;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
flex: 1 0 auto;
width: auto;
`;
interface Props {
isSelf: boolean;
code: string;
}
function InviteEmbed({ isSelf, code }: Props) {
const app = useAppStore();
const logger = useLogger("InviteEmbed");
const [data, setData] = useState<APIInvite>();
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
useEffect(() => {
app.rest
.get<APIInvite>(Routes.invite(code), {
wuth_counts: true,
with_expiration: true,
})
.then((r) => {
setData(r);
setLoading(false);
logger.debug(`Resolved invite: `, r);
})
.catch((e) => {
logger.error(`Error fetching invite: `, e);
setError(true);
setLoading(false);
});
}, []);
return (
<MainContainer>
{loading && (
<Wrapper>
<Header>Resolving Invite</Header>
</Wrapper>
)}
{error && (
<Wrapper>
<Header>{isSelf ? "You sent an invite, but..." : "You received an invite, but..."}</Header>
<ContentWrapper>
<GuildHeader>
<div
style={{
backgroundColor: "var(--background-secondary-alt)",
flex: "0 0 auto",
width: "50px",
height: "50px",
borderRadius: "16px",
backgroundClip: "padding-box",
backgroundPosition: "center center",
backgroundSize: "100% 100%",
}}
/>
<GuildInfo>
<InvalidInviteHeader>Invalid Invite</InvalidInviteHeader>
<InvalidInviteDetails>Try sending a new invite!</InvalidInviteDetails>
</GuildInfo>
</GuildHeader>
</ContentWrapper>
</Wrapper>
)}
{data && (
<Wrapper>
{data.guild!.splash && (
<SplashWrapper>
<Splash
src={REST.makeCDNUrl(
CDNRoutes.guildSplash(data.guild!.id, data.guild!.splash, ImageFormat.PNG),
)}
alt="Guild Splash"
/>
</SplashWrapper>
)}
<Header>
{isSelf ? "You sent an invite to join a guild" : "You've been invited to join a guild"}
</Header>
<ContentWrapper>
<GuildHeader>
<div
style={{
backgroundImage: data.guild!.icon
? `url(${REST.makeCDNUrl(
CDNRoutes.guildIcon(data.guild!.id, data.guild!.icon, ImageFormat.PNG),
)})`
: asAcronym(data.guild!.name),
backgroundColor: "var(--background-secondary-alt)",
flex: "0 0 auto",
width: "50px",
height: "50px",
borderRadius: "16px",
backgroundClip: "padding-box",
backgroundPosition: "center center",
backgroundSize: "100% 100%",
}}
/>
<GuildInfo>
<GuildName>{data.guild!.name}</GuildName>
<GuildDetailsWrapper>
<StatusWrapper>
<OnlineStatus />
<StatusCount>
{/* @ts-expect-error the server is incorrect here */}
{(data.guild!.presence_count || 0).toLocaleString()} Online
</StatusCount>
</StatusWrapper>
<StatusWrapper>
<OfflineStatus />
<StatusCount>
{/* @ts-expect-error the server is incorrect here */}
{(data.guild!.member_count || 0).toLocaleString()} Members
</StatusCount>
</StatusWrapper>
</GuildDetailsWrapper>
</GuildInfo>
</GuildHeader>
<JoinButton palette="secondary">Join</JoinButton>
</ContentWrapper>
</Wrapper>
)}
</MainContainer>
);
}
export default InviteEmbed;

View File

@ -1,9 +1,9 @@
import { useAppStore } from "@hooks/useAppStore";
import LoadingPage from "@pages/LoadingPage";
import { invoke } from "@tauri-apps/api/core";
import { isTauri } from "@utils";
import { observer } from "mobx-react-lite";
import React from "react";
import { useAppStore } from "../hooks/useAppStore";
import LoadingPage from "../pages/LoadingPage";
import { isTauri } from "../utils/Utils";
interface Props {
children: React.ReactNode;

View File

@ -1,10 +1,10 @@
import ListSection from "@components/ListSection";
import { useAppStore } from "@hooks/useAppStore";
import { GuildMemberListStore } from "@stores";
import { autorun } from "mobx";
import { observer } from "mobx-react-lite";
import React from "react";
import styled from "styled-components";
import { useAppStore } from "../../hooks/useAppStore";
import GuildMemberListStore from "../../stores/GuildMemberListStore";
import ListSection from "../ListSection";
import MemberListItem from "./MemberListItem";
const Container = styled.div`

View File

@ -1,13 +1,12 @@
import Avatar from "@components/Avatar";
import { Floating, FloatingTrigger } from "@components/floating";
import { ContextMenuContext } from "@contexts/ContextMenuContext";
import { useAppStore } from "@hooks/useAppStore";
import { PresenceUpdateStatus } from "@spacebarchat/spacebar-api-types/v9";
import { GuildMember } from "@structures";
import { observer } from "mobx-react-lite";
import { useContext } from "react";
import styled from "styled-components";
import { ContextMenuContext } from "../../contexts/ContextMenuContext";
import { useAppStore } from "../../hooks/useAppStore";
import GuildMember from "../../stores/objects/GuildMember";
import Avatar from "../Avatar";
import Floating from "../floating/Floating";
import FloatingTrigger from "../floating/FloatingTrigger";
const ListItem = styled(FloatingTrigger)<{ isCategory?: boolean }>`
padding: ${(props) => (props.isCategory ? "16px 8px 0 0" : "1px 8px 0 0")};

View File

@ -13,6 +13,7 @@ const Text = styled.h2`
flex: 1;
`;
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface Props {}
function SectionTitle({ children }: React.PropsWithChildren<Props>) {

View File

@ -1,11 +1,10 @@
import Container from "@components/Container";
import { Floating, FloatingTrigger } from "@components/floating";
import { GuildSidebarListItem } from "@components/GuildItem";
import Icon, { IconProps } from "@components/Icon";
import SidebarPill, { PillType } from "@components/SidebarPill";
import React from "react";
import styled from "styled-components";
import Container from "./Container";
import { GuildSidebarListItem } from "./GuildItem";
import Icon, { IconProps } from "./Icon";
import SidebarPill, { PillType } from "./SidebarPill";
import Floating from "./floating/Floating";
import FloatingTrigger from "./floating/FloatingTrigger";
const Wrapper = styled(Container)<{
margin?: boolean;

View File

@ -1,5 +1,5 @@
import Container from "@components/Container";
import styled from "styled-components";
import Container from "./Container";
export type PillType = "none" | "unread" | "hover" | "active";

View File

@ -1,5 +1,5 @@
import { FloatingProps } from "@components/floating";
import styled from "styled-components";
import { FloatingProps } from "./floating/Floating";
const Container = styled.div`
background-color: var(--background-tertiary);

View File

@ -1,12 +1,11 @@
import { modalController } from "@/controllers/modals";
import { Floating, FloatingTrigger } from "@components/floating";
import { useAppStore } from "@hooks/useAppStore";
import { observer } from "mobx-react-lite";
import styled from "styled-components";
import { modalController } from "../controllers/modals";
import { useAppStore } from "../hooks/useAppStore";
import Avatar from "./Avatar";
import Icon from "./Icon";
import IconButton from "./IconButton";
import Floating from "./floating/Floating";
import FloatingTrigger from "./floating/FloatingTrigger";
const Section = styled.section`
flex: 0 0 auto;

View File

@ -1,5 +1,5 @@
import Icon from "@components/Icon";
import styled from "styled-components";
import Icon from "../Icon";
const Wrapper = styled.div`
display: flex;

View File

@ -0,0 +1 @@
export { default as OfflineBanner } from "./OfflineBanner";

View File

@ -1,7 +1,7 @@
// loosely based on https://github.com/revoltchat/frontend/blob/master/components/app/menus/UserContextMenu.tsx
import { modalController } from "../../controllers/modals";
import Channel from "../../stores/objects/Channel";
import { modalController } from "@/controllers/modals";
import Channel from "@structures/Channel";
import { ContextMenu, ContextMenuButton, ContextMenuDivider } from "./ContextMenu";
interface MenuProps {

View File

@ -1,6 +1,6 @@
// loosely based on https://github.com/revoltchat/frontend/blob/master/components/app/menus/UserContextMenu.tsx
import Channel from "../../stores/objects/Channel";
import Channel from "@structures/Channel";
import { ContextMenu, ContextMenuButton, ContextMenuDivider } from "./ContextMenu";
interface MenuProps {

View File

@ -1,9 +1,9 @@
// modified from https://github.com/revoltchat/frontend/blob/master/components/app/menus/ContextMenu.tsx
// changed some styling
import Icon, { IconProps } from "@components/Icon";
import { ComponentProps } from "react";
import styled from "styled-components";
import Icon, { IconProps } from "../Icon";
export const ContextMenu = styled.div`
display: flex;

View File

@ -1,10 +1,10 @@
// loosely based on https://github.com/revoltchat/frontend/blob/master/components/app/menus/UserContextMenu.tsx
import { modalController } from "@/controllers/modals";
import { useAppStore } from "@hooks/useAppStore";
import useLogger from "@hooks/useLogger";
import { ChannelType } from "@spacebarchat/spacebar-api-types/v9";
import { modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import useLogger from "../../hooks/useLogger";
import Guild from "../../stores/objects/Guild";
import { Guild } from "@structures";
import { ContextMenu, ContextMenuButton, ContextMenuDivider } from "./ContextMenu";
interface MenuProps {

View File

@ -1,6 +1,6 @@
import { modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import Message from "../../stores/objects/Message";
import { modalController } from "@/controllers/modals";
import { useAppStore } from "@hooks/useAppStore";
import { Message } from "@structures";
import { ContextMenu, ContextMenuButton, ContextMenuDivider } from "./ContextMenu";
interface MenuProps {

View File

@ -1,9 +1,8 @@
// loosely based on https://github.com/revoltchat/frontend/blob/master/components/app/menus/UserContextMenu.tsx
import { modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import GuildMember from "../../stores/objects/GuildMember";
import User from "../../stores/objects/User";
import { modalController } from "@/controllers/modals";
import { useAppStore } from "@hooks/useAppStore";
import { GuildMember, User } from "@structures";
import { ContextMenu, ContextMenuButton, ContextMenuDivider } from "./ContextMenu";
interface MenuProps {

View File

@ -0,0 +1,6 @@
export { default as ChannelContextMenu } from "./ChannelContextMenu";
export { default as ChannelMentionContextMenu } from "./ChannelMentionContextMenu";
export * from "./ContextMenu";
export { default as GuildContextMenu } from "./GuildContextMenu";
export { default as MessageContextMenu } from "./MessageContextMenu";
export { default as UserContextMenu } from "./UserContextMenu";

View File

@ -1,11 +1,9 @@
import Tooltip from "@components/Tooltip";
import { FloatingContext } from "@contexts/FloatingContext";
import { FloatingArrow, FloatingPortal, Placement } from "@floating-ui/react";
import useFloating from "@hooks/useFloating";
import { Guild, GuildMember, User } from "@structures";
import { motion } from "framer-motion";
import { FloatingContext } from "../../contexts/FloatingContext";
import useFloating from "../../hooks/useFloating";
import Guild from "../../stores/objects/Guild";
import GuildMember from "../../stores/objects/GuildMember";
import User from "../../stores/objects/User";
import Tooltip from "../Tooltip";
import GuildMenuPopout from "./GuildMenuPopout";
import UserProfilePopout from "./UserProfilePopout";

View File

@ -1,7 +1,7 @@
import { FloatingFocusManager, FloatingPortal, useMergeRefs } from "@floating-ui/react";
import useFloatingContext from "@hooks/useFloatingContext";
import { motion } from "framer-motion";
import React from "react";
import useFloatingContext from "../../hooks/useFloatingContext";
export default React.forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement>>(function PopoverContent(
{ style, ...props },

View File

@ -1,6 +1,6 @@
import { useMergeRefs } from "@floating-ui/react";
import useFloatingContext from "@hooks/useFloatingContext";
import React from "react";
import useFloatingContext from "../../hooks/useFloatingContext";
interface PopoverTriggerProps {
children: React.ReactNode;

View File

@ -1,11 +1,11 @@
import useLogger from "@hooks/useLogger";
import styled from "styled-components";
import useLogger from "../../hooks/useLogger";
import { modalController } from "@/controllers/modals";
import { ContextMenu, ContextMenuButton, ContextMenuDivider } from "@components/contextMenus/ContextMenu";
import { useAppStore } from "@hooks/useAppStore";
import { Permissions } from "@utils";
import React, { useEffect } from "react";
import { modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import { Permissions } from "../../utils/Permissions";
import { ContextMenu, ContextMenuButton, ContextMenuDivider } from "../contextMenus/ContextMenu";
const CustomContextMenu = styled(ContextMenu)`
width: 200px;

View File

@ -1,16 +1,14 @@
import Avatar from "@components/Avatar";
import { HorizontalDivider } from "@components/Divider";
import useLogger from "@hooks/useLogger";
import { GuildMember, User } from "@structures";
import { REST, Snowflake } from "@utils";
import styled from "styled-components";
import useLogger from "../../hooks/useLogger";
import GuildMember from "../../stores/objects/GuildMember";
import User from "../../stores/objects/User";
import Snowflake from "../../utils/Snowflake";
import Avatar from "../Avatar";
import { HorizontalDivider } from "../Divider";
import SpacebarLogoBlue from "@assets/images/logo/Spacebar_Icon.svg?react";
import { useAppStore } from "@hooks/useAppStore";
import { CDNRoutes, ImageFormat } from "@spacebarchat/spacebar-api-types/v9";
import dayjs from "dayjs";
import SpacebarLogoBlue from "../../assets/images/logo/Spacebar_Icon.svg?react";
import { useAppStore } from "../../hooks/useAppStore";
import REST from "../../utils/REST";
import Floating from "./Floating";
import FloatingTrigger from "./FloatingTrigger";

View File

@ -0,0 +1,6 @@
export * from "./Floating";
export { default as Floating } from "./Floating";
export { default as FloatingContent } from "./FloatingContent";
export { default as FloatingTrigger } from "./FloatingTrigger";
export { default as GuildMenuPopout } from "./GuildMenuPopout";
export { default as UserProfilePopout } from "./UserProfilePopout";

View File

@ -1,22 +0,0 @@
import { Navigate } from "react-router-dom";
import { useAppStore } from "../../hooks/useAppStore";
import { LoadingSuspense } from "../../pages/LoadingPage";
interface Props {
component: React.FC;
}
export const AuthenticationGuard = ({ component }: Props) => {
const app = useAppStore();
if (!app.token) {
return <Navigate to="/login" replace />;
}
const Component = component;
return (
<LoadingSuspense>
<Component />
</LoadingSuspense>
);
};

View File

@ -1,22 +0,0 @@
import { Navigate } from "react-router-dom";
import { useAppStore } from "../../hooks/useAppStore";
import { LoadingSuspense } from "../../pages/LoadingPage";
interface Props {
component: React.FC;
}
export const UnauthenticatedGuard = ({ component }: Props) => {
const app = useAppStore();
if (app.token) {
return <Navigate to="/app" replace />;
}
const Component = component;
return (
<LoadingSuspense>
<Component />
</LoadingSuspense>
);
};

31
src/components/index.ts Normal file
View File

@ -0,0 +1,31 @@
// export * from "./AuthComponents"
export { default as Avatar } from "./Avatar";
export { default as Button } from "./Button";
export { default as ChannelHeader } from "./ChannelHeader";
export { default as ChannelSidebar } from "./ChannelSidebar";
export { default as Container } from "./Container";
export * from "./Divider";
export { default as DOBInput } from "./DOBInput";
export { default as ErrorBoundary } from "./ErrorBoundary";
// export * from "./FormComponents";
export * from "./banners";
export * from "./contextMenus";
export * from "./floating";
export { default as GuildItem } from "./GuildItem";
export { default as HCaptcha } from "./HCaptcha";
export { default as Icon } from "./Icon";
export { default as IconButton } from "./IconButton";
export { default as InviteEmbed } from "./InviteEmbed";
export { default as Link } from "./Link";
export { default as ListSection } from "./ListSection";
export { default as Loader } from "./Loader";
export * from "./markdown";
export * from "./media";
export * from "./SectionHeader";
export { default as SectionTitle } from "./SectionTitle";
export { default as SidebarAction } from "./SidebarAction";
export { default as SidebarPill } from "./SidebarPill";
export { default as SwipeableLayout } from "./SwipeableLayout";
export { default as Text } from "./Text";
export { default as Tooltip } from "./Tooltip";
export { default as UserPanel } from "./UserPanel";

View File

@ -1,3 +1,4 @@
import Link from "@components/Link";
import { FormattingPatterns } from "@spacebarchat/spacebar-api-types/v9";
import Marked, { ReactRenderer } from "marked-react";
import React from "react";
@ -5,12 +6,11 @@ import reactStringReplace from "react-string-replace";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { materialDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import styled from "styled-components";
import CodeBlock from "../Codeblock";
import Link from "../Link";
import Spoiler from "../Spoiler";
import CodeBlock from "./components/Codeblock";
import Mention from "./components/Mention";
import Spoiler from "./components/Spoiler";
import Timestamp from "./components/Timestamp";
import { MarkdownProps } from "./Markdown";
import Mention from "./Mention";
import Timestamp from "./Timestamp";
const Container = styled.div`
// remove the excessive left padding, and margin in lists

View File

@ -1,10 +1,10 @@
// adapted from Revite
// https://github.com/revoltchat/revite/blob/fe63c6633f32b54aa1989cb34627e72bb3377efd/src/components/markdown/plugins/Codeblock.tsx
import Floating from "@components/floating/Floating";
import FloatingTrigger from "@components/floating/FloatingTrigger";
import React from "react";
import styled from "styled-components";
import Floating from "./floating/Floating";
import FloatingTrigger from "./floating/FloatingTrigger";
const Actions = styled.div`
position: absolute;
@ -47,7 +47,7 @@ function CodeBlock(props: Props) {
const onCopy = React.useCallback(() => {
const text = ref.current?.querySelector("code")?.innerText;
text && navigator.clipboard.writeText(text);
if (text) navigator.clipboard.writeText(text);
}, [ref]);
return (

View File

@ -1,14 +1,11 @@
import { Floating, FloatingTrigger } from "@components/floating";
import { ContextMenuContext } from "@contexts/ContextMenuContext";
import { useAppStore } from "@hooks/useAppStore";
import { Channel, Role, User } from "@structures";
import { hexToRGB, rgbToHsl } from "@utils";
import React, { memo } from "react";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import { ContextMenuContext } from "../../contexts/ContextMenuContext";
import { useAppStore } from "../../hooks/useAppStore";
import Channel from "../../stores/objects/Channel";
import Role from "../../stores/objects/Role";
import User from "../../stores/objects/User";
import { hexToRGB, rgbToHsl } from "../../utils/Utils";
import Floating from "../floating/Floating";
import FloatingTrigger from "../floating/FloatingTrigger";
const MentionText = styled.span<{ color?: string; withHover?: boolean }>`
padding: 0 2px;

View File

@ -1,8 +1,7 @@
import { Floating, FloatingTrigger } from "@components/floating";
import dayjs from "dayjs";
import { memo } from "react";
import styled from "styled-components";
import Floating from "../floating/Floating";
import FloatingTrigger from "../floating/FloatingTrigger";
const Container = styled.div`
background-color: hsl(var(--background-tertiary-hsl) / 0.3);

View File

@ -0,0 +1,4 @@
export * from "./Codeblock";
export * from "./Mention";
export * from "./Spoiler";
export * from "./Timestamp";

View File

@ -0,0 +1,4 @@
export * from "./components";
export { default as Markdown } from "./Markdown";
export type { MarkdownProps } from "./Markdown";
export { default as MarkdownRenderer } from "./MarkdownRenderer";

View File

@ -1,160 +0,0 @@
/*
* Synthwave '84 Theme originally by Robb Owen [@Robb0wen] for Visual Studio Code
* Demo: https://marc.dev/demo/prism-synthwave84
*
* Ported for PrismJS by Marc Backes [@themarcba]
*/
code[class*="language-"],
pre[class*="language-"] {
color: #f92aad;
text-shadow:
0 0 2px #100c0f,
0 0 5px #dc078e33,
0 0 10px #fff3;
background: none;
font-family: var(--font-family-code);
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background-color: transparent !important;
background-image: linear-gradient(to bottom, #2a2139 75%, #34294f);
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #8e8e8e;
}
.token.punctuation {
color: #ccc;
}
.token.tag,
.token.attr-name,
.token.namespace,
.token.number,
.token.unit,
.token.hexcode,
.token.deleted {
color: #e2777a;
}
.token.property,
.token.selector {
color: #72f1b8;
text-shadow:
0 0 2px #100c0f,
0 0 10px #257c5575,
0 0 35px #21272475;
}
.token.function-name {
color: #6196cc;
}
.token.boolean,
.token.selector .token.id,
.token.function {
color: #fdfdfd;
text-shadow:
0 0 2px #001716,
0 0 3px #03edf975,
0 0 5px #03edf975,
0 0 8px #03edf975;
}
.token.class-name {
color: #fff5f6;
text-shadow:
0 0 2px #000,
0 0 10px #fc1f2c75,
0 0 5px #fc1f2c75,
0 0 25px #fc1f2c75;
}
.token.constant,
.token.symbol {
color: #f92aad;
text-shadow:
0 0 2px #100c0f,
0 0 5px #dc078e33,
0 0 10px #fff3;
}
.token.important,
.token.atrule,
.token.keyword,
.token.selector .token.class,
.token.builtin {
color: #f4eee4;
text-shadow:
0 0 2px #393a33,
0 0 8px #f39f0575,
0 0 2px #f39f0575;
}
.token.string,
.token.char,
.token.attr-value,
.token.regex,
.token.variable {
color: #f87c32;
}
.token.operator,
.token.entity,
.token.url {
color: #67cdcc;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.token.inserted {
color: green;
}

View File

@ -1,8 +1,8 @@
import Icon from "@components/Icon";
import Link from "@components/Link";
import { APIAttachment } from "@spacebarchat/spacebar-api-types/v9";
import { bytesToSize } from "@utils";
import styled from "styled-components";
import { bytesToSize } from "../../utils/Utils";
import Icon from "../Icon";
import Link from "../Link";
const Container = styled.div`
margin-top: 10px;
@ -48,7 +48,7 @@ interface Props {
attachment: APIAttachment;
}
function Audio({ attachment }: Props) {
export function Audio({ attachment }: Props) {
const url = attachment.proxy_url && attachment.proxy_url.length > 0 ? attachment.proxy_url : attachment.url;
return (
@ -68,5 +68,3 @@ function Audio({ attachment }: Props) {
</Container>
);
}
export default Audio;

View File

@ -1,6 +1,6 @@
import { APIAttachment } from "@spacebarchat/spacebar-api-types/v9";
import { bytesToSize } from "@utils";
import styled from "styled-components";
import { bytesToSize } from "../../utils/Utils";
import Icon from "../Icon";
import Link from "../Link";
@ -48,7 +48,7 @@ interface Props {
attachment: APIAttachment;
}
function File({ attachment }: Props) {
export function File({ attachment }: Props) {
const url = attachment.proxy_url && attachment.proxy_url.length > 0 ? attachment.proxy_url : attachment.url;
return (
@ -65,5 +65,3 @@ function File({ attachment }: Props) {
</Container>
);
}
export default File;

View File

@ -1,8 +1,8 @@
import { APIAttachment } from "@spacebarchat/spacebar-api-types/v9";
import { calculateImageRatio, calculateScaledDimensions } from "@utils";
import React from "react";
import { PuffLoader } from "react-spinners";
import styled from "styled-components";
import { calculateImageRatio, calculateScaledDimensions } from "../../utils/Message";
const Container = styled.div`
display: flex;
@ -18,7 +18,7 @@ interface Props {
attachment: APIAttachment;
}
function Video({ attachment }: Props) {
export function Video({ attachment }: Props) {
const ref = React.useRef<HTMLVideoElement>(null);
const [isLoading, setLoading] = React.useState(true);
const [dimensions, setDimensions] = React.useState({ width: 0, height: 0 });
@ -72,5 +72,3 @@ function Video({ attachment }: Props) {
</>
);
}
export default Video;

View File

@ -0,0 +1,3 @@
export * from "./Audio";
export * from "./File";
export * from "./Video";

View File

@ -1,12 +1,11 @@
import MemberList from "@components/MemberList/MemberList";
import { useAppStore } from "@hooks/useAppStore";
import useLogger from "@hooks/useLogger";
import { Channel, Guild } from "@structures";
import { runInAction } from "mobx";
import { observer } from "mobx-react-lite";
import React, { useEffect } from "react";
import styled from "styled-components";
import { useAppStore } from "../../hooks/useAppStore";
import useLogger from "../../hooks/useLogger";
import Channel from "../../stores/objects/Channel";
import Guild from "../../stores/objects/Guild";
import MemberList from "../MemberList/MemberList";
import ChatHeader from "./ChatHeader";
import MessageInput from "./MessageInput";
import MessageList from "./MessageList";

View File

@ -1,13 +1,12 @@
import { Floating, FloatingTrigger } from "@components/floating";
import Icon from "@components/Icon";
import { SectionHeader } from "@components/SectionHeader";
import { useAppStore } from "@hooks/useAppStore";
import useLogger from "@hooks/useLogger";
import * as Icons from "@mdi/js";
import { Channel } from "@structures";
import { observer } from "mobx-react-lite";
import styled from "styled-components";
import { useAppStore } from "../../hooks/useAppStore";
import useLogger from "../../hooks/useLogger";
import Channel from "../../stores/objects/Channel";
import Icon from "../Icon";
import { SectionHeader } from "../SectionHeader";
import Floating from "../floating/Floating";
import FloatingTrigger from "../floating/FloatingTrigger";
const IconButton = styled.button`
margin: 0;

View File

@ -1,9 +1,9 @@
// adapted from Revite
// https://github.com/revoltchat/revite/blob/master/src/components/common/messaging/embed/Embed.tsx
import { modalController } from "@/controllers/modals";
import Icon from "@components/Icon";
import { APIEmbed, EmbedType } from "@spacebarchat/spacebar-api-types/v9";
import { modalController } from "../../controllers/modals";
import Icon from "../Icon";
import styles from "./Embed.module.css";
function getScaledDimensions(originalWidth: number, originalHeight: number, maxWidth: number, maxHeight: number) {

View File

@ -1,11 +1,11 @@
import Avatar from "@components/Avatar";
import InviteEmbed from "@components/InviteEmbed";
import Markdown from "@components/markdown/MarkdownRenderer";
import { ContextMenuContext } from "@contexts/ContextMenuContext";
import { useAppStore } from "@hooks/useAppStore";
import { MessageLike, QueuedMessageStatus } from "@structures";
import { observer } from "mobx-react-lite";
import { memo, useContext } from "react";
import { ContextMenuContext } from "../../contexts/ContextMenuContext";
import { useAppStore } from "../../hooks/useAppStore";
import { MessageLike } from "../../stores/objects/Message";
import { QueuedMessageStatus } from "../../stores/objects/QueuedMessage";
import Avatar from "../Avatar";
import Markdown from "../markdown/MarkdownRenderer";
import MessageAttachment from "./MessageAttachment";
import MessageAuthor from "./MessageAuthor";
import MessageBase, { MessageContent, MessageContentText, MessageDetails, MessageInfo } from "./MessageBase";
@ -69,6 +69,10 @@ function Message({ message, header }: Props) {
{"embeds" in message &&
message.embeds?.map((embed, index) => <MessageEmbed key={index} embed={embed} />)}
{"files" in message && message.files?.length !== 0 && <AttachmentUploadProgress message={message} />}
{"invites" in message &&
message.invites.map((code, index) => (
<InviteEmbed key={index} code={code} isSelf={message.author.id === app.account!.id} />
))}
</MessageContent>
</MessageBase>
);

View File

@ -1,11 +1,9 @@
import { modalController } from "@/controllers/modals";
import { Audio, File, Video } from "@components/media";
import useLogger from "@hooks/useLogger";
import { APIAttachment } from "@spacebarchat/spacebar-api-types/v9";
import { getFileDetails, zoomFit } from "@utils";
import styled from "styled-components";
import { modalController } from "../../controllers/modals";
import useLogger from "../../hooks/useLogger";
import { getFileDetails, zoomFit } from "../../utils/Utils";
import Audio from "../media/Audio";
import File from "../media/File";
import Video from "../media/Video";
const MAX_ATTACHMENT_HEIGHT = 350;

View File

@ -1,14 +1,11 @@
import { Floating, FloatingTrigger } from "@components/floating";
import { ContextMenuContext } from "@contexts/ContextMenuContext";
import { useAppStore } from "@hooks/useAppStore";
import useLogger from "@hooks/useLogger";
import { Guild, GuildMember, MessageLike } from "@structures";
import { observer } from "mobx-react-lite";
import React, { useContext } from "react";
import styled from "styled-components";
import { ContextMenuContext } from "../../contexts/ContextMenuContext";
import { useAppStore } from "../../hooks/useAppStore";
import useLogger from "../../hooks/useLogger";
import Guild from "../../stores/objects/Guild";
import GuildMember from "../../stores/objects/GuildMember";
import { MessageLike } from "../../stores/objects/Message";
import Floating from "../floating/Floating";
import FloatingTrigger from "../floating/FloatingTrigger";
const Container = styled.div`
font-size: 16px;

View File

@ -1,10 +1,9 @@
import { Floating, FloatingTrigger } from "@components/floating";
import { Message, MessageLike } from "@structures";
import { calendarStrings } from "@utils";
import dayjs from "dayjs";
import { observer } from "mobx-react-lite";
import styled from "styled-components";
import Message, { MessageLike } from "../../stores/objects/Message";
import { calendarStrings } from "../../utils/i18n";
import Floating from "../floating/Floating";
import FloatingTrigger from "../floating/FloatingTrigger";
interface Props {
header?: boolean;
@ -71,6 +70,7 @@ export const MessageContent = styled.div`
justify-content: center;
padding-right: 48px;
word-wrap: anywhere;
flex: 1;
`;
export const MessageContentText = styled.div<{ sending?: boolean; failed?: boolean }>`

View File

@ -1,16 +1,14 @@
// adapted from Revite
// https://github.com/revoltchat/revite/blob/master/src/components/common/messaging/embed/Embed.tsx
import { Markdown, MarkdownRenderer } from "@components/markdown";
import { APIEmbed, EmbedType } from "@spacebarchat/spacebar-api-types/v9";
import { decimalColorToHex } from "@utils";
import classNames from "classnames";
import React from "react";
import { decimalColorToHex } from "../../utils/Utils";
import Markdown from "../markdown/Markdown";
import MarkdownRenderer from "../markdown/MarkdownRenderer";
import styles from "./Embed.module.css";
import EmbedMedia from "./EmbedMedia";
import { MessageAreaWidthContext } from "./MessageList";
const LINK_EMBED_MAX_WIDTH = 516;
const RICH_EMBED_MAX_WIDTH = 428;
const CONTAINER_PADDING = 24;
@ -60,7 +58,7 @@ function MessageEmbed({ embed }: Props) {
className={classNames(styles.embed, styles.website)}
style={{
borderInlineStartColor: embed.color ? decimalColorToHex(embed.color) : "var(--background-tertiary)",
maxWidth: 432,
maxWidth: 430,
}}
>
<div

View File

@ -1,6 +1,6 @@
import { MessageType } from "@spacebarchat/spacebar-api-types/v9";
import { MessageGroup as MessageGroupType } from "@stores/MessageStore";
import { observer } from "mobx-react-lite";
import { MessageGroup as MessageGroupType } from "../../stores/MessageStore";
import Message from "./Message";
import SystemMessage from "./SystemMessage";

View File

@ -1,17 +1,16 @@
import Channel from "../../stores/objects/Channel";
import Channel from "@structures/Channel";
import { ChannelType, MessageType, RESTPostAPIChannelMessageJSONBody } from "@spacebarchat/spacebar-api-types/v9";
import { observer } from "mobx-react-lite";
import React from "react";
import styled from "styled-components";
import { modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import useLogger from "../../hooks/useLogger";
import Guild from "../../stores/objects/Guild";
import Snowflake from "../../utils/Snowflake";
import { MAX_ATTACHMENTS } from "../../utils/constants";
import { debounce } from "../../utils/debounce";
import { modalController } from "@/controllers/modals";
import { useAppStore } from "@hooks/useAppStore";
import useLogger from "@hooks/useLogger";
import Guild from "@structures/Guild";
import { MAX_ATTACHMENTS, Snowflake } from "@utils";
import debounce from "@utils/debounce";
import MessageTextArea from "./MessageTextArea";
import AttachmentUpload from "./attachments/AttachmentUpload";
import AttachmentUploadList from "./attachments/AttachmentUploadPreview";

View File

@ -1,16 +1,15 @@
import { HorizontalDivider } from "@components/Divider";
import { useAppStore } from "@hooks/useAppStore";
import useLogger from "@hooks/useLogger";
import { MessageGroup as MessageGroupType } from "@stores/MessageStore";
import { Channel, Guild } from "@structures";
import { Permissions } from "@utils";
import { observer } from "mobx-react-lite";
import React from "react";
import InfiniteScroll from "react-infinite-scroll-component";
import PulseLoader from "react-spinners/PulseLoader";
import styled from "styled-components";
import useResizeObserver from "use-resize-observer";
import { useAppStore } from "../../hooks/useAppStore";
import useLogger from "../../hooks/useLogger";
import { MessageGroup as MessageGroupType } from "../../stores/MessageStore";
import Channel from "../../stores/objects/Channel";
import Guild from "../../stores/objects/Guild";
import { Permissions } from "../../utils/Permissions";
import { HorizontalDivider } from "../Divider";
import MessageGroup from "./MessageGroup";
export const MessageAreaWidthContext = React.createContext(0);

View File

@ -1,7 +1,8 @@
import { TextareaAutosize, TextareaAutosizeProps } from "@mui/material";
// import { TextareaAutosize, TextareaAutosizeProps } from "@mui/material";
import { isTouchscreenDevice } from "@utils";
import React from "react";
import styled from "styled-components";
import { isTouchscreenDevice } from "../../utils/isTouchscreenDevice";
import { TextareaAutosize, TextareaAutosizeProps } from "./TextareaAutosize";
const Container = styled.div`
flex: 1;
@ -34,7 +35,7 @@ function MessageTextArea(props: TextareaAutosizeProps) {
React.useEffect(() => {
if (isTouchscreenDevice) return;
ref.current && ref.current.focus();
if (ref.current) ref.current.focus();
}, [props.value]);
const inputSelected = () => ["TEXTAREA", "INPUT"].includes(document.activeElement?.nodeName ?? "");

View File

@ -1,11 +1,11 @@
import Icon from "@components/Icon";
import * as Icons from "@mdi/js";
import { MessageType } from "@spacebarchat/spacebar-api-types/v9";
import { MessageLike } from "@structures";
import { observer } from "mobx-react-lite";
import ReactMarkdown from "react-markdown";
import reactStringReplace from "react-string-replace";
import styled from "styled-components";
import { MessageLike } from "../../stores/objects/Message";
import Icon from "../Icon";
import MessageBase, { MessageDetails, MessageInfo } from "./MessageBase";
const SystemContent = styled.div`

View File

@ -0,0 +1,276 @@
import { muiDebounce as debounce, ownerWindow, useEnhancedEffect, useForkRef } from "@utils/mui";
import * as React from "react";
// import PropTypes from 'prop-types';
// import {
// unstable_debounce as debounce,
// unstable_useForkRef as useForkRef,
// unstable_useEnhancedEffect as useEnhancedEffect,
// unstable_ownerWindow as ownerWindow,
// } from '@mui/utils';
export interface TextareaAutosizeProps
extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "children" | "rows"> {
ref?: React.Ref<HTMLTextAreaElement>;
/**
* Maximum number of rows to display.
*/
maxRows?: string | number;
/**
* Minimum number of rows to display.
* @default 1
*/
minRows?: string | number;
}
function getStyleValue(value: string) {
return parseInt(value, 10) || 0;
}
const styles: {
shadow: React.CSSProperties;
} = {
shadow: {
// Visibility needed to hide the extra text area on iPads
visibility: "hidden",
// Remove from the content flow
position: "absolute",
// Ignore the scrollbar width
overflow: "hidden",
height: 0,
top: 0,
left: 0,
// Create a new layer, increase the isolation of the computed values
transform: "translateZ(0)",
},
};
type TextareaStyles = {
outerHeightStyle: number;
overflowing: boolean;
};
function isEmpty(obj: TextareaStyles) {
return (
obj === undefined ||
obj === null ||
Object.keys(obj).length === 0 ||
(obj.outerHeightStyle === 0 && !obj.overflowing)
);
}
/**
*
* Demos:
*
* - [Textarea Autosize](https://mui.com/base-ui/react-textarea-autosize/)
* - [Textarea Autosize](https://mui.com/material-ui/react-textarea-autosize/)
*
* API:
*
* - [TextareaAutosize API](https://mui.com/base-ui/react-textarea-autosize/components-api/#textarea-autosize)
*/
const TextareaAutosize = React.forwardRef(function TextareaAutosize(
props: TextareaAutosizeProps,
forwardedRef: React.ForwardedRef<Element>,
) {
const { onChange, maxRows, minRows = 1, style, value, ...other } = props;
const { current: isControlled } = React.useRef(value != null);
const inputRef = React.useRef<HTMLTextAreaElement>(null);
const handleRef = useForkRef(forwardedRef, inputRef);
const heightRef = React.useRef<number | null>(null);
const shadowRef = React.useRef<HTMLTextAreaElement>(null);
const calculateTextareaStyles = React.useCallback(() => {
const input = inputRef.current!;
const containerWindow = ownerWindow(input);
const computedStyle = containerWindow.getComputedStyle(input);
// If input's width is shrunk and it's not visible, don't sync height.
if (computedStyle.width === "0px") {
return {
outerHeightStyle: 0,
overflowing: false,
};
}
const inputShallow = shadowRef.current!;
inputShallow.style.width = computedStyle.width;
inputShallow.value = input.value || props.placeholder || "x";
if (inputShallow.value.slice(-1) === "\n") {
// Certain fonts which overflow the line height will cause the textarea
// to report a different scrollHeight depending on whether the last line
// is empty. Make it non-empty to avoid this issue.
inputShallow.value += " ";
}
const boxSizing = computedStyle.boxSizing;
const padding = getStyleValue(computedStyle.paddingBottom) + getStyleValue(computedStyle.paddingTop);
const border = getStyleValue(computedStyle.borderBottomWidth) + getStyleValue(computedStyle.borderTopWidth);
// The height of the inner content
const innerHeight = inputShallow.scrollHeight;
// Measure height of a textarea with a single row
inputShallow.value = "x";
const singleRowHeight = inputShallow.scrollHeight;
// The height of the outer content
let outerHeight = innerHeight;
if (minRows) {
outerHeight = Math.max(Number(minRows) * singleRowHeight, outerHeight);
}
if (maxRows) {
outerHeight = Math.min(Number(maxRows) * singleRowHeight, outerHeight);
}
outerHeight = Math.max(outerHeight, singleRowHeight);
// Take the box sizing into account for applying this value as a style.
const outerHeightStyle = outerHeight + (boxSizing === "border-box" ? padding + border : 0);
const overflowing = Math.abs(outerHeight - innerHeight) <= 1;
return { outerHeightStyle, overflowing };
}, [maxRows, minRows, props.placeholder]);
const syncHeight = React.useCallback(() => {
const textareaStyles = calculateTextareaStyles();
if (isEmpty(textareaStyles)) {
return;
}
const outerHeightStyle = textareaStyles.outerHeightStyle;
const input = inputRef.current!;
if (heightRef.current !== outerHeightStyle) {
heightRef.current = outerHeightStyle;
input.style.height = `${outerHeightStyle}px`;
}
input.style.overflow = textareaStyles.overflowing ? "hidden" : "";
}, [calculateTextareaStyles]);
useEnhancedEffect(() => {
const handleResize = () => {
syncHeight();
};
// Workaround a "ResizeObserver loop completed with undelivered notifications" error
// in test.
// Note that we might need to use this logic in production per https://github.com/WICG/resize-observer/issues/38
// Also see https://github.com/mui/mui-x/issues/8733
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let rAF: any;
const rAFHandleResize = () => {
cancelAnimationFrame(rAF);
rAF = requestAnimationFrame(() => {
handleResize();
});
};
const debounceHandleResize = debounce(handleResize);
const input = inputRef.current!;
const containerWindow = ownerWindow(input);
containerWindow.addEventListener("resize", debounceHandleResize);
let resizeObserver: ResizeObserver;
if (typeof ResizeObserver !== "undefined") {
resizeObserver = new ResizeObserver(process.env.NODE_ENV === "test" ? rAFHandleResize : handleResize);
resizeObserver.observe(input);
}
return () => {
debounceHandleResize.clear();
cancelAnimationFrame(rAF);
containerWindow.removeEventListener("resize", debounceHandleResize);
if (resizeObserver) {
resizeObserver.disconnect();
}
};
}, [calculateTextareaStyles, syncHeight]);
useEnhancedEffect(() => {
syncHeight();
});
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
if (!isControlled) {
syncHeight();
}
if (onChange) {
onChange(event);
}
};
return (
<React.Fragment>
<textarea
value={value}
onChange={handleChange}
ref={handleRef}
// Apply the rows prop to get a "correct" first SSR paint
rows={minRows as number}
style={style}
{...other}
/>
<textarea
aria-hidden
className={props.className}
readOnly
ref={shadowRef}
tabIndex={-1}
style={{
...styles.shadow,
...style,
paddingTop: 0,
paddingBottom: 0,
}}
/>
</React.Fragment>
);
}) as React.ForwardRefExoticComponent<TextareaAutosizeProps & React.RefAttributes<Element>>;
// TextareaAutosize.propTypes /* remove-proptypes */ = {
// // ┌────────────────────────────── Warning ──────────────────────────────┐
// // │ These PropTypes are generated from the TypeScript type definitions. │
// // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
// // └─────────────────────────────────────────────────────────────────────┘
// /**
// * @ignore
// */
// className: PropTypes.string,
// /**
// * Maximum number of rows to display.
// */
// maxRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
// /**
// * Minimum number of rows to display.
// * @default 1
// */
// minRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
// /**
// * @ignore
// */
// onChange: PropTypes.func,
// /**
// * @ignore
// */
// placeholder: PropTypes.string,
// /**
// * @ignore
// */
// style: PropTypes.object,
// /**
// * @ignore
// */
// value: PropTypes.oneOfType([
// PropTypes.arrayOf(PropTypes.string),
// PropTypes.number,
// PropTypes.string,
// ]),
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// } as any;
export { TextareaAutosize };

View File

@ -1,8 +1,8 @@
import { Channel } from "@structures";
import { observer } from "mobx-react-lite";
import { Fragment } from "react";
import { PulseLoader } from "react-spinners";
import styled from "styled-components";
import Channel from "../../stores/objects/Channel";
const Container = styled.div`
overflow: visible;

View File

@ -1,11 +1,10 @@
import { modalController } from "@/controllers/modals/ModalController";
import Icon from "@components/Icon";
import useLogger from "@hooks/useLogger";
import { MAX_UPLOAD_SIZE, bytesToSize } from "@utils";
import { observer } from "mobx-react-lite";
import React from "react";
import styled from "styled-components";
import { modalController } from "../../../controllers/modals/ModalController";
import useLogger from "../../../hooks/useLogger";
import { bytesToSize } from "../../../utils/Utils";
import { MAX_UPLOAD_SIZE } from "../../../utils/constants";
import Icon from "../../Icon";
const Container = styled.button`
height: 45px;

View File

@ -1,10 +1,10 @@
import { HorizontalDivider } from "@components/Divider";
import Icon from "@components/Icon";
import IconButton from "@components/IconButton";
import { bytesToSize, getFileDetails, getFileIcon } from "@utils";
import { observer } from "mobx-react-lite";
import React, { Fragment } from "react";
import styled from "styled-components";
import { bytesToSize, getFileDetails, getFileIcon } from "../../../utils/Utils";
import { HorizontalDivider } from "../../Divider";
import Icon from "../../Icon";
import IconButton from "../../IconButton";
const Container = styled.ul`
display: flex;
gap: 8px;

View File

@ -1,10 +1,10 @@
import Icon from "@components/Icon";
import IconButton from "@components/IconButton";
import { useAppStore } from "@hooks/useAppStore";
import { QueuedMessage } from "@structures";
import { bytesToSize } from "@utils";
import { observer } from "mobx-react-lite";
import styled from "styled-components";
import { useAppStore } from "../../../hooks/useAppStore";
import QueuedMessage from "../../../stores/objects/QueuedMessage";
import { bytesToSize } from "../../../utils/Utils";
import Icon from "../../Icon";
import IconButton from "../../IconButton";
const Container = styled.div`
width: 520px;

View File

@ -1,6 +1,6 @@
import { ModalProps, modalController } from "@/controllers/modals";
import Button from "@components/Button";
import styled from "styled-components";
import { ModalProps, modalController } from "../../controllers/modals";
import Button from "../Button";
import { Modal } from "./ModalComponents";
const ActionWrapper = styled.div`

View File

@ -1,10 +1,10 @@
import { ModalProps, modalController } from "@/controllers/modals";
import { yupResolver } from "@hookform/resolvers/yup";
import { useAppStore } from "@hooks/useAppStore";
import { Routes } from "@spacebarchat/spacebar-api-types/v9";
import { useForm } from "react-hook-form";
import styled from "styled-components";
import * as yup from "yup";
import { ModalProps, modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import { Modal } from "./ModalComponents";
const DescriptionText = styled.p`

View File

@ -1,21 +1,21 @@
import { ModalProps, modalController } from "@/controllers/modals";
import { Input, InputErrorText } from "@components/AuthComponents";
import { TextDivider } from "@components/Divider";
import Icon, { IconType } from "@components/Icon";
import { yupResolver } from "@hookform/resolvers/yup";
import { useAppStore } from "@hooks/useAppStore";
import {
ChannelType,
RESTPostAPIGuildChannelJSONBody,
RESTPostAPIGuildChannelResult,
Routes,
} from "@spacebarchat/spacebar-api-types/v9";
import { messageFromFieldError } from "@utils";
import React from "react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import * as yup from "yup";
import { ModalProps, modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import { messageFromFieldError } from "../../utils/messageFromFieldError";
import { Input, InputErrorText } from "../AuthComponents";
import { TextDivider } from "../Divider";
import Icon, { IconType } from "../Icon";
import { Modal } from "./ModalComponents";
const CHANNEL_OPTIONS: {

View File

@ -1,18 +1,18 @@
import { ModalProps } from "@/controllers/modals/types";
import { Input, InputErrorText, InputLabel, LabelWrapper } from "@components/AuthComponents";
import Button from "@components/Button";
import { TextDivider } from "@components/Divider";
import { InputSelect, InputSelectOption } from "@components/FormComponents";
import Icon from "@components/Icon";
import IconButton from "@components/IconButton";
import { useAppStore } from "@hooks/useAppStore";
import useLogger from "@hooks/useLogger";
import { APIInvite, Routes } from "@spacebarchat/spacebar-api-types/v9";
import { messageFromFieldError } from "@utils";
import dayjs from "dayjs";
import React from "react";
import { useForm } from "react-hook-form";
import styled from "styled-components";
import { ModalProps } from "../../controllers/modals/types";
import { useAppStore } from "../../hooks/useAppStore";
import useLogger from "../../hooks/useLogger";
import { messageFromFieldError } from "../../utils/messageFromFieldError";
import { Input, InputErrorText, InputLabel, LabelWrapper } from "../AuthComponents";
import Button from "../Button";
import { TextDivider } from "../Divider";
import { InputSelect, InputSelectOption } from "../FormComponents";
import Icon from "../Icon";
import IconButton from "../IconButton";
import { InputContainer, Modal } from "./ModalComponents";
// TODO: refactor the layout of this modal when we have dms and friends, and move settings to a separate modal

View File

@ -1,14 +1,14 @@
import { modalController, ModalProps } from "@/controllers/modals";
import { Input, InputErrorText, InputLabel, InputWrapper, LabelWrapper } from "@components/AuthComponents";
import { TextDivider } from "@components/Divider";
import { useAppStore } from "@hooks/useAppStore";
import useLogger from "@hooks/useLogger";
import { APIGuild, Routes } from "@spacebarchat/spacebar-api-types/v9";
import { messageFromFieldError } from "@utils";
import React from "react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import { ModalProps, modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import useLogger from "../../hooks/useLogger";
import { messageFromFieldError } from "../../utils/messageFromFieldError";
import { Input, InputErrorText, InputLabel, InputWrapper, LabelWrapper } from "../AuthComponents";
import { TextDivider } from "../Divider";
import { InputContainer, Modal } from "./ModalComponents";
const UploadIcon = styled.div`

View File

@ -1,6 +1,6 @@
import { ModalProps, modalController } from "@/controllers/modals";
import MarkdownRenderer from "@components/markdown/MarkdownRenderer";
import styled from "styled-components";
import { ModalProps, modalController } from "../../controllers/modals";
import MarkdownRenderer from "../markdown/MarkdownRenderer";
import { Modal } from "./ModalComponents";
const PreviewContainer = styled.div`
@ -12,7 +12,7 @@ const PreviewContainer = styled.div`
padding: 5px 6px;
`;
export function DeleteMessageModal({ target, ...props }: ModalProps<"delete_message">) {
export function DeleteMessageModal({ target, ...props }: ModalProps<"delete_message">) {
return (
<Modal
{...props}

View File

@ -1,4 +1,4 @@
import { ModalProps } from "../../controllers/modals/types";
import { ModalProps } from "@/controllers/modals/types";
import { Modal } from "./ModalComponents";
export function ErrorModal({ error, ...props }: ModalProps<"error">) {

View File

@ -0,0 +1,56 @@
import { ModalProps } from "@/controllers/modals";
import REST from "@utils/REST";
import styled from "styled-components";
import { Modal, ModalHeader, ModalHeaderText, ModalSubHeaderText } from "./ModalComponents";
const ActionWrapper = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
`;
export function InviteModal({ inviteData, ...props }: ModalProps<"invite">) {
const splashUrl = REST.makeCDNUrl(`/splashes/${inviteData.guild?.id}/${inviteData.guild?.splash}.png`, {
size: 2048,
});
return (
<Modal nonDismissable padding="0" maxWidth="920">
<div
style={{
display: "flex",
flexDirection: "row",
maxHeight: "100%",
}}
>
<div
style={{
height: "100%",
}}
>
<div
style={{
overflow: "hidden scroll",
paddingRight: 24,
width: 400,
padding: 32,
}}
>
<ModalHeader>
<ModalSubHeaderText>You've been invited to join</ModalSubHeaderText>
<ModalHeaderText>{inviteData.guild?.name}</ModalHeaderText>
</ModalHeader>
</div>
</div>
<div
style={{
width: "520px",
backgroundImage: `url(${splashUrl})`,
backgroundSize: "cover",
backgroundPosition: "100%",
borderRadius: "0 5px 5px 0",
}}
></div>
</div>
</Modal>
);
}

View File

@ -0,0 +1,20 @@
import { ModalProps } from "@/controllers/modals";
import styled from "styled-components";
import { Modal, ModalHeader, ModalHeaderText, ModalSubHeaderText } from "./ModalComponents";
const ActionWrapper = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
`;
export function InviteUnauthedModal({ inviteData, ...props }: ModalProps<"invite">) {
return (
<Modal nonDismissable>
<ModalHeader>
<ModalSubHeaderText>You've been invited to join</ModalSubHeaderText>
<ModalHeaderText>{inviteData.guild?.name}</ModalHeaderText>
</ModalHeader>
</Modal>
);
}

View File

@ -1,13 +1,13 @@
import { ModalProps, modalController } from "@/controllers/modals";
import { Input, InputErrorText, InputLabel, LabelWrapper } from "@components/AuthComponents";
import { TextDivider } from "@components/Divider";
import { useAppStore } from "@hooks/useAppStore";
import useLogger from "@hooks/useLogger";
import { Routes } from "@spacebarchat/spacebar-api-types/v9";
import { messageFromFieldError } from "@utils";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import { ModalProps, modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import useLogger from "../../hooks/useLogger";
import { messageFromFieldError } from "../../utils/messageFromFieldError";
import { Input, InputErrorText, InputLabel, LabelWrapper } from "../AuthComponents";
import { TextDivider } from "../Divider";
import { Modal } from "./ModalComponents";
const InviteInputContainer = styled.div`

View File

@ -1,10 +1,10 @@
import { ModalProps, modalController } from "@/controllers/modals";
import { yupResolver } from "@hookform/resolvers/yup";
import { useAppStore } from "@hooks/useAppStore";
import { Routes } from "@spacebarchat/spacebar-api-types/v9";
import { useForm } from "react-hook-form";
import styled from "styled-components";
import * as yup from "yup";
import { ModalProps, modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import { Modal } from "./ModalComponents";
const DescriptionText = styled.p`

View File

@ -1,9 +1,9 @@
import { ModalProps, modalController } from "@/controllers/modals";
import { useAppStore } from "@hooks/useAppStore";
import useLogger from "@hooks/useLogger";
import { Routes } from "@spacebarchat/spacebar-api-types/v9";
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { ModalProps, modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import useLogger from "../../hooks/useLogger";
import { Modal } from "./ModalComponents";
export function LeaveServerModal({ target, ...props }: ModalProps<"leave_server">) {

View File

@ -1,9 +1,9 @@
import Button, { Props as ButtonProps } from "@components/Button";
import { animationFadeIn, animationFadeOut, animationZoomIn, animationZoomOut } from "@components/common/animations";
import Icon from "@components/Icon";
import React, { useCallback, useEffect, useState } from "react";
import { Portal } from "react-portal";
import styled, { css } from "styled-components";
import Button, { Props as ButtonProps } from "../Button";
import Icon from "../Icon";
import { animationFadeIn, animationFadeOut, animationZoomIn, animationZoomOut } from "../common/animations";
export type ModalAction = Omit<React.HTMLAttributes<HTMLButtonElement>, "as"> &
Omit<ButtonProps, "onClick"> & {

View File

@ -1,12 +1,11 @@
import { ModalProps, modalController } from "@/controllers/modals";
import Icon from "@components/Icon";
import Link from "@components/Link";
import { useAppStore } from "@hooks/useAppStore";
import { APP_VERSION, GIT_BRANCH, GIT_REVISION, REPO_URL, isTauri } from "@utils";
import { observer } from "mobx-react-lite";
import { useState } from "react";
import styled, { css } from "styled-components";
import { ModalProps, modalController } from "../../controllers/modals";
import { useAppStore } from "../../hooks/useAppStore";
import { isTauri } from "../../utils/Utils";
import { APP_VERSION, GIT_BRANCH, GIT_REVISION, REPO_URL } from "../../utils/revison";
import Icon from "../Icon";
import Link from "../Link";
import { Modal } from "./ModalComponents";
import AccountSettingsPage from "./SettingsPages/AccountSettingsPage";
import DeveloperSettingsPage from "./SettingsPages/DeveloperSettingsPage";

View File

@ -1,11 +1,11 @@
import Avatar from "@components/Avatar";
import Button from "@components/Button";
import SectionTitle from "@components/SectionTitle";
import { useAppStore } from "@hooks/useAppStore";
import { RESTPatchAPICurrentUserJSONBody, Routes } from "@spacebarchat/spacebar-api-types/v9";
import { observer } from "mobx-react-lite";
import { useEffect, useRef, useState } from "react";
import styled, { css } from "styled-components";
import { useAppStore } from "../../../hooks/useAppStore";
import Avatar from "../../Avatar";
import Button from "../../Button";
import SectionTitle from "../../SectionTitle";
const Content = styled.div`
display: flex;

View File

@ -1,6 +1,6 @@
import SectionTitle from "@components/SectionTitle";
import { observer } from "mobx-react-lite";
import styled from "styled-components";
import SectionTitle from "../../SectionTitle";
const Content = styled.div`
display: flex;

View File

@ -1,9 +1,9 @@
import SectionTitle from "@components/SectionTitle";
import { useAppStore } from "@hooks/useAppStore";
import { EXPERIMENT_LIST, Experiment as ExperimentType } from "@stores/ExperimentsStore";
import { observer } from "mobx-react-lite";
import { useState } from "react";
import styled from "styled-components";
import { useAppStore } from "../../../hooks/useAppStore";
import { EXPERIMENT_LIST, Experiment as ExperimentType } from "../../../stores/ExperimentsStore";
import SectionTitle from "../../SectionTitle";
const Content = styled.div`
display: flex;

View File

@ -7,6 +7,8 @@ export * from "./DeleteMessageModal";
export * from "./ErrorModal";
export * from "./ForgotPasswordModal";
export * from "./ImageViewerModal";
export * from "./InviteModal";
export * from "./InviteUnauthedModal";
export * from "./JoinServerModal";
export * from "./KickMemberModal";
export * from "./LeaveServerModal";

View File

@ -1,11 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useFloating } from "@floating-ui/react";
import { Channel, Guild, GuildMember, MessageLike, User } from "@structures";
import React from "react";
import Channel from "../stores/objects/Channel";
import Guild from "../stores/objects/Guild";
import GuildMember from "../stores/objects/GuildMember";
import { MessageLike } from "../stores/objects/Message";
import User from "../stores/objects/User";
export type ContextMenuProps =
| {

View File

@ -1,6 +1,6 @@
import { FloatingPortal } from "@floating-ui/react";
import useContextMenu, { ContextMenuComponents } from "@hooks/useContextMenu";
import React from "react";
import useContextMenu, { ContextMenuComponents } from "../hooks/useContextMenu";
import { ContextMenuContext, ContextMenuProps } from "./ContextMenuContext";
// eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@ -1,5 +1,5 @@
import useFloating from "@hooks/useFloating";
import React from "react";
import useFloating from "../hooks/useFloating";
type ContextType = ReturnType<typeof useFloating> | null;

View File

@ -1,7 +1,7 @@
import { useAppStore } from "@hooks/useAppStore";
import { rgbToHsl } from "@utils";
import { observer } from "mobx-react-lite";
import { createGlobalStyle } from "styled-components";
import { useAppStore } from "../hooks/useAppStore";
import { rgbToHsl } from "../utils/Utils";
const font: ThemeFont["font"] = {
weight: {

View File

@ -1,8 +1,8 @@
import IconButton from "@components/IconButton";
import { OfflineBanner } from "@components/banners";
import { AnimatePresence, motion } from "framer-motion";
import { action, computed, makeObservable, observable } from "mobx";
import styled from "styled-components";
import IconButton from "../../components/IconButton";
import OfflineBanner from "../../components/banners/OfflineBanner";
import { Banner } from "./types";
const Container = styled(motion.div)`

View File

@ -1,5 +1,5 @@
import { observer } from "mobx-react-lite";
import { bannerController } from ".";
import { bannerController } from "./BannerController";
export default observer(() => {
return <>{bannerController.rendered}</>;

View File

@ -1,6 +1,5 @@
// adapted from https://github.com/revoltchat/revite/blob/master/src/controllers/modals/ModalController.tsx
import { action, computed, makeObservable, observable } from "mobx";
import {
AddServerModal,
BanMemberModal,
@ -14,7 +13,8 @@ import {
KickMemberModal,
LeaveServerModal,
SettingsModal,
} from "../../components/modals";
} from "@components/modals";
import { action, computed, makeObservable, observable } from "mobx";
import { Modal } from "./types";
function randomUUID() {

View File

@ -1,3 +1,3 @@
export * from "./ModalController";
export * from "./ModalRenderer";
export { default as ModalRenderer } from "./ModalRenderer";
export * from "./types";

View File

@ -1,10 +1,13 @@
// adapted from https://github.com/revoltchat/revite/blob/master/src/controllers/modals/types.ts
import { APIAttachment, APIEmbedImage, APIEmbedThumbnail, APIEmbedVideo } from "@spacebarchat/spacebar-api-types/v9";
import Channel from "../../stores/objects/Channel";
import Guild from "../../stores/objects/Guild";
import GuildMember from "../../stores/objects/GuildMember";
import Message from "../../stores/objects/Message";
import {
APIAttachment,
APIEmbedImage,
APIEmbedThumbnail,
APIEmbedVideo,
APIInvite,
} from "@spacebarchat/spacebar-api-types/v9";
import { Channel, Guild, GuildMember, Message } from "@structures";
export type Modal = {
key?: string;
@ -55,6 +58,10 @@ export type Modal = {
guild: Guild;
category?: Channel;
}
| {
type: "invite";
inviteData: APIInvite;
}
);
export type ModalProps<T extends Modal["type"]> = Modal & { type: T } & {

View File

@ -1,4 +1,4 @@
import AppStore from "../stores/AppStore";
import AppStore from "@stores/AppStore";
export const appStore = new AppStore();

View File

@ -1,11 +1,13 @@
import {
ChannelMentionContextMenu,
GuildContextMenu,
MessageContextMenu,
UserContextMenu,
} from "@components/contextMenus";
import ChannelContextMenu from "@components/contextMenus/ChannelContextMenu";
import { ContextMenuProps } from "@contexts/ContextMenuContext";
import { autoUpdate, flip, offset, shift, useDismiss, useFloating, useInteractions, useRole } from "@floating-ui/react";
import { useMemo, useState } from "react";
import ChannelContextMenu from "../components/contextMenus/ChannelContextMenu";
import ChannelMentionContextMenu from "../components/contextMenus/ChannelMentionContextMenu";
import GuildContextMenu from "../components/contextMenus/GuildContextMenu";
import MessageContextMenu from "../components/contextMenus/MessageContextMenu";
import UserContextMenu from "../components/contextMenus/UserContextMenu";
import { ContextMenuProps } from "../contexts/ContextMenuContext";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Components = Record<string, React.FC<any>>;

Some files were not shown because too many files have changed in this diff Show More