Add open route / invite route.

Modularise server header.
This commit is contained in:
Paul 2021-06-21 21:35:21 +01:00
parent 602cca1047
commit c597fc81f8
6 changed files with 279 additions and 23 deletions

View file

@ -0,0 +1,39 @@
import Header from "../ui/Header";
import styled from "styled-components";
import { Link } from "react-router-dom";
import IconButton from "../ui/IconButton";
import { Settings } from "@styled-icons/feather";
import { Server } from "revolt.js/dist/api/objects";
import { ServerPermission } from "revolt.js/dist/api/permissions";
import { HookContext, useServerPermission } from "../../context/revoltjs/hooks";
interface Props {
server: Server,
ctx: HookContext
}
const ServerName = styled.div`
flex-grow: 1;
`;
export default function ServerHeader({ server, ctx }: Props) {
const permissions = useServerPermission(server._id, ctx);
const bannerURL = ctx.client.servers.getBannerURL(server._id, { width: 480 }, true);
return (
<Header placement="secondary"
background={typeof bannerURL !== 'undefined'}
style={{ background: bannerURL ? `linear-gradient(to bottom, transparent 50%, #000e), url('${bannerURL}')` : undefined }}>
<ServerName>
{ server.name }
</ServerName>
{ (permissions & ServerPermission.ManageServer) > 0 && <div className="actions">
<Link to={`/server/${server._id}/settings`}>
<IconButton>
<Settings size={24} />
</IconButton>
</Link>
</div> }
</Header>
)
}

View file

@ -14,6 +14,7 @@ import { connectState } from "../../../redux/connector";
import PaintCounter from "../../../lib/PaintCounter"; import PaintCounter from "../../../lib/PaintCounter";
import styled from "styled-components"; import styled from "styled-components";
import { attachContextMenu } from 'preact-context-menu'; import { attachContextMenu } from 'preact-context-menu';
import ServerHeader from "../../common/ServerHeader";
interface Props { interface Props {
unreads: Unreads; unreads: Unreads;
@ -45,7 +46,6 @@ function ServerSidebar(props: Props & WithDispatcher) {
const server = useServer(server_id, ctx); const server = useServer(server_id, ctx);
if (!server) return <Redirect to="/" />; if (!server) return <Redirect to="/" />;
const permissions = useServerPermission(server._id, ctx);
const channels = (useChannels(server.channels, ctx) const channels = (useChannels(server.channels, ctx)
.filter(entry => typeof entry !== 'undefined') as Readonly<Channels.TextChannel>[]) .filter(entry => typeof entry !== 'undefined') as Readonly<Channels.TextChannel>[])
.map(x => mapChannelWithUnread(x, props.unreads)); .map(x => mapChannelWithUnread(x, props.unreads));
@ -55,16 +55,7 @@ function ServerSidebar(props: Props & WithDispatcher) {
return ( return (
<ServerBase> <ServerBase>
<Header placement="secondary" background style={{ background: `url('${ctx.client.servers.getBannerURL(server._id, { width: 480 }, true)}')` }}> <ServerHeader server={server} ctx={ctx} />
<div>
{ server.name }
</div>
{ (permissions & ServerPermission.ManageServer) > 0 && <div className="actions">
{/*<IconButton to={`/server/${server._id}/settings`}>*/}
<Settings size={24} />
{/*</IconButton>*/}
</div> }
</Header>
<ConnectionStatus /> <ConnectionStatus />
<ServerList onContextMenu={attachContextMenu('Menu', { server_list: server._id })}> <ServerList onContextMenu={attachContextMenu('Menu', { server_list: server._id })}>
{channels.map(entry => { {channels.map(entry => {

72
src/pages/Open.tsx Normal file
View file

@ -0,0 +1,72 @@
import { Text } from "preact-i18n";
import Header from "../components/ui/Header";
import { useContext, useEffect } from "preact/hooks";
import { useHistory, useParams } from "react-router-dom";
import { useIntermediate } from "../context/intermediate/Intermediate";
import { useChannels, useForceUpdate, useUser } from "../context/revoltjs/hooks";
import { AppContext, ClientStatus, StatusContext } from "../context/revoltjs/RevoltClient";
export default function Open() {
const history = useHistory();
const client = useContext(AppContext);
const status = useContext(StatusContext);
const { id } = useParams<{ id: string }>();
const { openScreen } = useIntermediate();
if (status !== ClientStatus.ONLINE) {
return (
<Header placement="primary">
<Text id="general.loading" />
</Header>
);
}
const ctx = useForceUpdate();
const channels = useChannels(undefined, ctx);
const user = useUser(id, ctx);
useEffect(() => {
if (id === "saved") {
for (const channel of channels) {
if (channel?.channel_type === "SavedMessages") {
history.push(`/channel/${channel._id}`);
return;
}
}
client.users
.openDM(client.user?._id as string)
.then(channel => history.push(`/channel/${channel?._id}`))
.catch(error => openScreen({ id: "error", error }));
return;
}
if (user) {
const channel: string | undefined = channels.find(
channel =>
channel?.channel_type === "DirectMessage" &&
channel.recipients.includes(id)
)?._id;
if (channel) {
history.push(`/channel/${channel}`);
} else {
client.users
.openDM(id)
.then(channel => history.push(`/channel/${channel?._id}`))
.catch(error => openScreen({ id: "error", error }));
}
return;
}
history.push("/");
}, []);
return (
<Header placement="primary">
<Text id="general.loading" />
</Header>
);
}

View file

@ -13,7 +13,9 @@ import LeftSidebar from "../components/navigation/LeftSidebar";
import RightSidebar from "../components/navigation/RightSidebar"; import RightSidebar from "../components/navigation/RightSidebar";
import BottomNavigation from "../components/navigation/BottomNavigation"; import BottomNavigation from "../components/navigation/BottomNavigation";
import Open from "./Open";
import Home from './home/Home'; import Home from './home/Home';
import Invite from "./invite/Invite";
import Friends from "./friends/Friends"; import Friends from "./friends/Friends";
import Channel from "./channels/Channel"; import Channel from "./channels/Channel";
import Settings from './settings/Settings'; import Settings from './settings/Settings';
@ -64,6 +66,8 @@ export default function App() {
<Route path="/dev" component={Developer} /> <Route path="/dev" component={Developer} />
<Route path="/friends" component={Friends} /> <Route path="/friends" component={Friends} />
<Route path="/open/:id" component={Open} />
<Route path="/invite/:code" component={Invite} />
<Route path="/" component={Home} /> <Route path="/" component={Home} />
</Switch> </Switch>
</Routes> </Routes>
@ -75,15 +79,3 @@ export default function App() {
</OverlappingPanels> </OverlappingPanels>
); );
}; };
/**
*
*
<Route path="/open/:id">
<Open />
</Route>
{/*<Route path="/invite/:code">
<OpenInvite />
</Route>
*/

View file

@ -0,0 +1,75 @@
.invite {
height: 100%;
display: flex;
color: white;
user-select: none;
align-items: center;
flex-direction: column;
background-size: cover;
justify-content: center;
background-position: center;
* {
overflow: visible;
}
.icon {
width: 64px;
z-index: 100;
text-align: left;
position: relative;
> * {
top: -32px;
position: absolute;
}
}
.leave {
top: 8px;
left: 8px;
cursor: pointer;
position: fixed;
}
.details {
text-align: center;
border-radius: 3px;
align-self: center;
padding: 32px 16px 16px 16px;
background: rgba(0, 0, 0, 0.6);
h1 {
margin: 0;
font-weight: 500;
}
h2 {
margin: 4px;
opacity: 0.7;
font-size: 0.8em;
font-weight: 400;
}
h3 {
gap: 8px;
display: flex;
font-size: 1em;
font-weight: 400;
flex-direction: row;
justify-content: center;
}
button {
margin: auto;
display: block;
background: rgba(0, 0, 0, 0.8);
}
}
}
.preloader {
height: 100%;
display: grid;
place-items: center;
}

View file

@ -0,0 +1,87 @@
import styles from './Invite.module.scss';
import Button from '../../components/ui/Button';
import { ArrowLeft } from "@styled-icons/feather";
import Overline from '../../components/ui/Overline';
import { Invites } from "revolt.js/dist/api/objects";
import Preloader from '../../components/ui/Preloader';
import { takeError } from "../../context/revoltjs/util";
import { useHistory, useParams } from "react-router-dom";
import ServerIcon from '../../components/common/ServerIcon';
import UserIcon from '../../components/common/user/UserIcon';
import { useContext, useEffect, useState } from "preact/hooks";
import RequiresOnline from '../../context/revoltjs/RequiresOnline';
import { AppContext, ClientStatus, StatusContext } from "../../context/revoltjs/RevoltClient";
export default function Invite() {
const history = useHistory();
const client = useContext(AppContext);
const status = useContext(StatusContext);
const { code } = useParams<{ code: string }>();
const [ processing, setProcessing ] = useState(false);
const [ error, setError ] = useState<string | undefined>(undefined);
const [ invite, setInvite ] = useState<Invites.RetrievedInvite | undefined>(undefined);
useEffect(() => {
if (typeof invite === 'undefined' && (status === ClientStatus.ONLINE || status === ClientStatus.READY)) {
client.fetchInvite(code)
.then(data => setInvite(data))
.catch(err => setError(takeError(err)))
}
}, [ status ]);
if (typeof invite === 'undefined') {
return (
<div className={styles.preloader}>
<RequiresOnline>
{ error ? <Overline type="error" error={error} />
: <Preloader /> }
</RequiresOnline>
</div>
)
}
return (
<div className={styles.invite} style={{ backgroundImage: invite.server_banner ? `url('${client.generateFileURL(invite.server_banner)}')` : undefined }}>
<div className={styles.leave}>
<ArrowLeft size={32} onClick={() => history.push('/')} />
</div>
{ !processing &&
<div className={styles.icon}>
<ServerIcon attachment={invite.server_icon} server_name={invite.server_name} size={64} />
</div> }
<div className={styles.details}>
{ processing ? <Preloader /> :
<>
<h1>{ invite.server_name }</h1>
<h2>#{invite.channel_name}</h2>
<h3>Invited by <UserIcon size={24} attachment={invite.user_avatar} /> { invite.user_name }</h3>
<Overline type="error" error={error} />
<Button contrast
onClick={
async () => {
if (status === ClientStatus.READY) {
return history.push('/');
}
try {
setProcessing(true);
let result = await client.joinInvite(code);
if (result.type === 'Server') {
history.push(`/server/${result.server._id}/channel/${result.channel._id}`);
}
} catch (err) {
setError(takeError(err));
setProcessing(false);
}
}
}
>{ status === ClientStatus.READY ? 'Login to REVOLT' : 'Accept Invite' }</Button>
</>
}
</div>
</div>
);
}