mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-22 02:12:38 +01:00
some refactoring
This commit is contained in:
parent
c480f86799
commit
7b82cc7b79
10
.gitignore
vendored
10
.gitignore
vendored
@ -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/
|
||||
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -1,2 +1,3 @@
|
||||
{
|
||||
"references.preferredLocation": "view"
|
||||
}
|
||||
|
10
package.json
10
package.json
@ -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",
|
||||
|
574
pnpm-lock.yaml
574
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
32
src/App.tsx
32
src/App.tsx
@ -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>
|
||||
|
@ -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;
|
||||
|
29
src/components/AuthenticationGuard.tsx
Normal file
29
src/components/AuthenticationGuard.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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`
|
||||
|
@ -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")};
|
||||
|
@ -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)`
|
||||
|
@ -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;
|
||||
|
@ -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";
|
||||
|
||||
|
271
src/components/InviteEmbed.tsx
Normal file
271
src/components/InviteEmbed.tsx
Normal 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;
|
@ -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;
|
||||
|
@ -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`
|
||||
|
@ -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")};
|
||||
|
@ -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>) {
|
||||
|
@ -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;
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Icon from "@components/Icon";
|
||||
import styled from "styled-components";
|
||||
import Icon from "../Icon";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
|
1
src/components/banners/index.ts
Normal file
1
src/components/banners/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as OfflineBanner } from "./OfflineBanner";
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
6
src/components/contextMenus/index.ts
Normal file
6
src/components/contextMenus/index.ts
Normal 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";
|
@ -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";
|
||||
|
||||
|
@ -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 },
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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";
|
||||
|
||||
|
6
src/components/floating/index.ts
Normal file
6
src/components/floating/index.ts
Normal 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";
|
@ -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>
|
||||
);
|
||||
};
|
@ -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
31
src/components/index.ts
Normal 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";
|
@ -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
|
||||
|
@ -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 (
|
@ -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;
|
@ -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);
|
4
src/components/markdown/components/index.ts
Normal file
4
src/components/markdown/components/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from "./Codeblock";
|
||||
export * from "./Mention";
|
||||
export * from "./Spoiler";
|
||||
export * from "./Timestamp";
|
4
src/components/markdown/index.ts
Normal file
4
src/components/markdown/index.ts
Normal 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";
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
3
src/components/media/index.ts
Normal file
3
src/components/media/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./Audio";
|
||||
export * from "./File";
|
||||
export * from "./Video";
|
@ -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";
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 }>`
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
|
@ -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 ?? "");
|
||||
|
@ -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`
|
||||
|
276
src/components/messaging/TextareaAutosize.tsx
Normal file
276
src/components/messaging/TextareaAutosize.tsx
Normal 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 };
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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`
|
||||
|
@ -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`
|
||||
|
@ -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: {
|
||||
|
@ -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
|
||||
|
@ -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`
|
||||
|
@ -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}
|
||||
|
@ -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">) {
|
||||
|
56
src/components/modals/InviteModal.tsx
Normal file
56
src/components/modals/InviteModal.tsx
Normal 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>
|
||||
);
|
||||
}
|
20
src/components/modals/InviteUnauthedModal.tsx
Normal file
20
src/components/modals/InviteUnauthedModal.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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`
|
||||
|
@ -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`
|
||||
|
@ -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">) {
|
||||
|
@ -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"> & {
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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";
|
||||
|
@ -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 =
|
||||
| {
|
||||
|
@ -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
|
||||
|
@ -1,5 +1,5 @@
|
||||
import useFloating from "@hooks/useFloating";
|
||||
import React from "react";
|
||||
import useFloating from "../hooks/useFloating";
|
||||
|
||||
type ContextType = ReturnType<typeof useFloating> | null;
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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)`
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { bannerController } from ".";
|
||||
import { bannerController } from "./BannerController";
|
||||
|
||||
export default observer(() => {
|
||||
return <>{bannerController.rendered}</>;
|
||||
|
@ -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() {
|
||||
|
@ -1,3 +1,3 @@
|
||||
export * from "./ModalController";
|
||||
export * from "./ModalRenderer";
|
||||
export { default as ModalRenderer } from "./ModalRenderer";
|
||||
export * from "./types";
|
||||
|
@ -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 } & {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import AppStore from "../stores/AppStore";
|
||||
import AppStore from "@stores/AppStore";
|
||||
|
||||
export const appStore = new AppStore();
|
||||
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user