chore: merge branch 'ui/glass-header'

This commit is contained in:
Paul Makles
2022-01-02 23:48:19 +00:00
45 changed files with 863 additions and 396 deletions

View File

@@ -4,84 +4,135 @@ import { observer } from "mobx-react-lite";
import { Link } from "react-router-dom";
import { ServerPermission } from "revolt.js/dist/api/permissions";
import { Server } from "revolt.js/dist/maps/Servers";
import styled from "styled-components";
import styled, { css } from "styled-components";
import { Text } from "preact-i18n";
import Header from "../ui/Header";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
import IconButton from "../ui/IconButton";
import Tooltip from "./Tooltip";
interface Props {
server: Server;
background?: boolean;
}
const ServerName = styled.div`
flex-grow: 1;
const ServerBanner = styled.div<Omit<Props, "server">>`
flex-shrink: 0;
display: flex;
flex-direction: column;
justify-content: end;
background-size: cover;
background-repeat: norepeat;
background-position: center center;
${(props) =>
props.background
? css`
height: 120px;
.container {
background: linear-gradient(
0deg,
var(--secondary-background),
transparent
);
}
`
: css`
background-color: var(--secondary-header);
`}
.container {
height: var(--header-height);
display: flex;
align-items: center;
padding: 0 14px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
gap: 8px;
.title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-grow: 1;
}
}
`;
export default observer(({ server }: Props) => {
const bannerURL = server.generateBannerURL({ width: 480 });
return (
<Header
borders
placement="secondary"
<ServerBanner
background={typeof bannerURL !== "undefined"}
style={{
background: bannerURL ? `url('${bannerURL}')` : undefined,
backgroundImage: bannerURL ? `url('${bannerURL}')` : undefined,
}}>
{server.flags && server.flags & 1 ? (
<Tooltip
content={<Text id="app.special.server-badges.official" />}
placement={"bottom-start"}>
<svg width="20" height="20">
<image
xlinkHref="/assets/badges/verified.svg"
height="20"
width="20"
/>
<image
xlinkHref="/assets/badges/revolt_r.svg"
height="15"
width="15"
x="2"
y="3"
style={
"justify-content: center; align-items: center; filter: brightness(0);"
}
/>
</svg>
</Tooltip>
) : undefined}
{server.flags && server.flags & 2 ? (
<Tooltip
content={<Text id="app.special.server-badges.verified" />}
placement={"bottom-start"}>
<svg width="20" height="20">
<image
xlinkHref="/assets/badges/verified.svg"
height="20"
width="20"
/>
<foreignObject x="2" y="2" width="15" height="15">
<Check size={15} color="black" strokeWidth={8} />
</foreignObject>
</svg>
</Tooltip>
) : undefined}
<ServerName>{server.name}</ServerName>
{(server.permission & ServerPermission.ManageServer) > 0 && (
<div className="actions">
<div className="container">
{server.flags && server.flags & 1 ? (
<Tooltip
content={
<Text id="app.special.server-badges.official" />
}
placement={"bottom-start"}>
<svg width="20" height="20">
<image
xlinkHref="/assets/badges/verified.svg"
height="20"
width="20"
/>
<image
xlinkHref="/assets/badges/revolt_r.svg"
height="15"
width="15"
x="2"
y="3"
style={
"justify-content: center; align-items: center; filter: brightness(0);"
}
/>
</svg>
</Tooltip>
) : undefined}
{server.flags && server.flags & 2 ? (
<Tooltip
content={
<Text id="app.special.server-badges.verified" />
}
placement={"bottom-start"}>
<svg width="20" height="20">
<image
xlinkHref="/assets/badges/verified.svg"
height="20"
width="20"
/>
<foreignObject x="2" y="2" width="15" height="15">
<Check
size={15}
color="black"
strokeWidth={8}
/>
</foreignObject>
</svg>
</Tooltip>
) : undefined}
<div className="title">{server.name}</div>
{(server.permission & ServerPermission.ManageServer) > 0 && (
<Link to={`/server/${server._id}/settings`}>
<IconButton>
<Cog size={24} />
<Cog size={20} />
</IconButton>
</Link>
</div>
)}
</Header>
)}
</div>
</ServerBanner>
);
});

View File

@@ -80,7 +80,7 @@ const Blocked = styled.div`
color: var(--tertiary-foreground);
.text {
padding: 14px 14px 14px 0;
padding: 14px;
}
svg {
@@ -89,13 +89,17 @@ const Blocked = styled.div`
`;
const Action = styled.div`
display: flex;
place-items: center;
> div {
height: 48px;
width: 48px;
padding: 12px;
width: 34px;
display: flex;
align-items: center;
justify-content: end;
/*padding: 14px 0 14px 14px;*/
}
.mobile {
justify-content: start;
}
${() =>

View File

@@ -13,19 +13,25 @@ export const Bar = styled.div<{ position: "top" | "bottom"; accent?: boolean }>`
z-index: 10;
position: relative;
${(props) =>
props.position === "top" &&
css`
top: 0;
`}
${(props) =>
props.position === "bottom" &&
css`
top: 65px;
${() =>
isTouchscreenDevice &&
css`
top: -90px;
`}
`}
> div {
${(props) =>
props.position === "bottom" &&
css`
top: -26px;
${() =>
isTouchscreenDevice &&
css`
top: -32px;
`}
`}
height: 28px;
width: 100%;
position: absolute;
@@ -52,6 +58,7 @@ export const Bar = styled.div<{ position: "top" | "bottom"; accent?: boolean }>`
${(props) =>
props.position === "top"
? css`
top: 48px;
border-radius: 0 0 var(--border-radius)
var(--border-radius);
`
@@ -60,6 +67,12 @@ export const Bar = styled.div<{ position: "top" | "bottom"; accent?: boolean }>`
0;
`}
${() =>
isTouchscreenDevice &&
css`
top: 56px;
`}
> div {
display: flex;
align-items: center;

View File

@@ -49,8 +49,12 @@ export default observer(
}
}}>
<div>
New messages since{" "}
{dayjs(decodeTime(last_id)).fromNow()}
<Text
id="app.main.channel.misc.new_messages"
fields={{
time_ago: dayjs(decodeTime(last_id)).fromNow(),
}}
/>
</div>
<div>
<Text id="app.main.channel.misc.jump_beginning" />

View File

@@ -25,7 +25,11 @@ const Base = styled.div`
flex-direction: row;
width: calc(100% - var(--scrollbar-thickness));
color: var(--secondary-foreground);
background: var(--secondary-background);
background-color: rgba(
var(--secondary-background-rgb),
max(var(--min-opacity), 0.75)
);
backdrop-filter: blur(10px);
}
.avatars {

View File

@@ -1,3 +1,4 @@
import { Group } from "@styled-icons/boxicons-solid";
import { autorun } from "mobx";
import { observer } from "mobx-react-lite";
import { useHistory } from "react-router-dom";
@@ -47,7 +48,7 @@ const EmbedInviteBase = styled.div`
const EmbedInviteDetails = styled.div`
flex-grow: 1;
padding-left: 12px;
padding-inline-start: 12px;
${() =>
isTouchscreenDevice &&
css`
@@ -63,7 +64,14 @@ const EmbedInviteName = styled.div`
`;
const EmbedInviteMemberCount = styled.div`
display: flex;
align-items: center;
gap: 2px;
font-size: 0.8em;
> svg {
color: var(--secondary-foreground);
}
`;
type Props = {
@@ -119,6 +127,7 @@ export function EmbedInvite({ code }: Props) {
<EmbedInviteDetails>
<EmbedInviteName>{invite.server_name}</EmbedInviteName>
<EmbedInviteMemberCount>
<Group size={12} />
{invite.member_count.toLocaleString()}{" "}
{invite.member_count === 1 ? "member" : "members"}
</EmbedInviteMemberCount>

View File

@@ -17,10 +17,10 @@ const Base = styled.div`
`;
const Navbar = styled.div`
z-index: 100;
max-width: 500px;
margin: 0 auto;
z-index: 500;
display: flex;
margin: 0 auto;
max-width: 500px;
height: var(--bottom-navigation-height);
`;
@@ -71,7 +71,12 @@ export default observer(() => {
}
}
history.push(layout.getLastHomePath());
const path = layout.getLastHomePath();
if (path === "/friends") {
history.push("/");
} else {
history.push(path);
}
}}>
<Message size={24} />
</IconButton>

View File

@@ -8,6 +8,13 @@ export default styled.div`
user-select: none;
flex-direction: row;
align-items: stretch;
/*background: var(--background);*/
background-color: rgba(
var(--background-rgb),
max(var(--min-opacity), 0.75)
);
backdrop-filter: blur(20px);
`;
export const GenericSidebarBase = styled.div<{
@@ -21,10 +28,15 @@ export const GenericSidebarBase = styled.div<{
/*border-end-start-radius: 8px;*/
background: var(--secondary-background);
> :nth-child(1) {
border-end-start-radius: 8px;
/*> :nth-child(1) {
//border-end-start-radius: 8px;
}
> :nth-child(2) {
margin-top: 48px;
background: red;
}*/
${(props) =>
props.mobilePadding &&
isTouchscreenDevice &&

View File

@@ -1,4 +1,5 @@
import { X, Crown } from "@styled-icons/boxicons-regular";
import { X } from "@styled-icons/boxicons-regular";
import { Crown } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite";
import { Presence } from "revolt-api/types/Users";
import { Channel } from "revolt.js/dist/maps/Channels";

View File

@@ -7,6 +7,7 @@ import {
import { observer } from "mobx-react-lite";
import { Link, useLocation, useParams } from "react-router-dom";
import { RelationshipStatus } from "revolt-api/types/Users";
import styled, { css } from "styled-components";
import { Text } from "preact-i18n";
import { useContext, useEffect } from "preact/hooks";
@@ -27,6 +28,21 @@ import { GenericSidebarBase, GenericSidebarList } from "../SidebarBase";
import ButtonItem, { ChannelButton } from "../items/ButtonItem";
import ConnectionStatus from "../items/ConnectionStatus";
const Navbar = styled.div`
display: flex;
align-items: center;
padding: 0 14px;
font-weight: 600;
flex-shrink: 0;
height: 48px;
${() =>
isTouchscreenDevice &&
css`
height: 56px;
`}
`;
export default observer(() => {
const { pathname } = useLocation();
const client = useContext(AppContext);
@@ -55,6 +71,9 @@ export default observer(() => {
return (
<GenericSidebarBase mobilePadding>
<Navbar>
<Text id="app.home.directs" />
</Navbar>
<ConnectionStatus />
<GenericSidebarList>
<ConditionalLink active={pathname === "/"} to="/">

View File

@@ -95,6 +95,7 @@ const ServerList = styled.div`
overflow-y: scroll;
padding-bottom: 20px;
flex-direction: column;
margin-top: -2px;
scrollbar-width: none;
@@ -168,6 +169,7 @@ const ServerCircle = styled.div`
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
.circle {
display: flex;
@@ -384,7 +386,7 @@ export default observer(() => {
</div>
</Tooltip>
</ServerCircle>
<ServerCircle>
{/*<ServerCircle>
<Tooltip
content={
<div
@@ -394,14 +396,13 @@ export default observer(() => {
gap: "4px",
}}>
<div>Discover Public Servers</div>
<LinkExternal size={12} />
</div>
}
placement="right">
<div className="circle">
<IconButton>
<a
href="https://revolt.social"
href="#"
target="_blank"
rel="noreferrer">
<Compass size={32} />
@@ -409,7 +410,7 @@ export default observer(() => {
</IconButton>
</div>
</Tooltip>
</ServerCircle>
</ServerCircle>*/}
</ServerList>
<PaintCounter small />
{!isTouchscreenDevice && (

View File

@@ -1,5 +1,6 @@
import { observer } from "mobx-react-lite";
import { Redirect, useParams } from "react-router";
import { Server } from "revolt.js/dist/maps/Servers";
import styled, { css } from "styled-components";
import { attachContextMenu } from "preact-context-menu";
@@ -48,6 +49,10 @@ const ServerList = styled.div`
}
`;
interface Props {
server: Server;
}
export default observer(() => {
const client = useClient();
const state = useApplicationState();

View File

@@ -6,9 +6,12 @@ import { Presence } from "revolt-api/types/Users";
import { Channel } from "revolt.js/dist/maps/Channels";
import { Server } from "revolt.js/dist/maps/Servers";
import { User } from "revolt.js/dist/maps/Users";
import styled, { css } from "styled-components";
import { useContext, useEffect, useMemo } from "preact/hooks";
import { isTouchscreenDevice } from "../../../lib/isTouchscreenDevice";
import {
ClientStatus,
StatusContext,
@@ -18,6 +21,15 @@ import {
import { GenericSidebarBase } from "../SidebarBase";
import MemberList, { MemberListGroup } from "./MemberList";
export const Container = styled.div`
padding-top: 48px;
${isTouchscreenDevice &&
css`
padding-top: 0;
`}
`;
export default function MemberSidebar() {
const channel = useClient().channels.get(
useParams<{ channel: string }>().channel,
@@ -157,6 +169,10 @@ export const GroupMemberSidebar = observer(
return (
<GenericSidebarBase>
<Container>
{/*{isTouchscreenDevice && <div>Group settings go here</div>}*/}
</Container>
<MemberList entries={entries} context={channel} />
</GenericSidebarBase>
);
@@ -180,6 +196,9 @@ export const ServerMemberSidebar = observer(
return (
<GenericSidebarBase>
<Container>
{/*{isTouchscreenDevice && <div>Server settings go here</div>}*/}
</Container>
<MemberList entries={entries} context={channel} />
</GenericSidebarBase>
);

View File

@@ -218,28 +218,32 @@ export const DisplaySeasonalShim = observer(() => {
const settings = useApplicationState().settings;
return (
<>
<h3>
<Text id="app.settings.pages.appearance.theme_options.title" />
</h3>
{/* TOFIX: WIP feature - follows system theme */}
{/*<Checkbox
checked={settings.get("appearance:seasonal") ?? true}
onChange={(v) => settings.set("appearance:seasonal", v)}
description={
<Text id="app.settings.pages.appearance.theme_options.follow_desc" />
}>
<Text id="app.settings.pages.appearance.theme_options.follow" />
</Checkbox>*/}
<Checkbox
checked={settings.get("appearance:seasonal") ?? true}
onChange={(v) => settings.set("appearance:seasonal", v)}
description={
<Text id="app.settings.pages.appearance.theme_options.seasonal_desc" />
}>
<Text id="app.settings.pages.appearance.theme_options.seasonal" />
</Checkbox>
</>
<Checkbox
checked={settings.get("appearance:seasonal") ?? true}
onChange={(v) => settings.set("appearance:seasonal", v)}
description={
<Text id="app.settings.pages.appearance.theme_options.seasonal_desc" />
}>
<Text id="app.settings.pages.appearance.theme_options.seasonal" />
</Checkbox>
);
});
/**
* Component providing a way to toggle transparency effects.
*/
export const DisplayTransparencyShim = observer(() => {
const settings = useApplicationState().settings;
return (
<Checkbox
checked={settings.get("appearance:transparency") ?? true}
onChange={(v) => settings.set("appearance:transparency", v)}
description={
<Text id="app.settings.pages.appearance.theme_options.transparency_desc" />
}>
<Text id="app.settings.pages.appearance.theme_options.transparency" />
</Checkbox>
);
});

View File

@@ -19,4 +19,8 @@ export default styled.select`
&:focus {
box-shadow: 0 0 0 1.5pt var(--accent);
}
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
`;

View File

@@ -1,13 +1,20 @@
import styled, { css } from "styled-components";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
export default styled.details<{ sticky?: boolean; large?: boolean }>`
summary {
${(props) =>
props.sticky &&
css`
top: -1px;
top: 48px;
z-index: 10;
position: sticky;
${() =>
isTouchscreenDevice &&
css`
top: 56px;
`}
`}
${(props) =>

View File

@@ -4,6 +4,7 @@ import {
Menu,
} from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import { useLocation } from "react-router-dom";
import styled, { css } from "styled-components";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
@@ -14,14 +15,16 @@ import { SIDEBAR_CHANNELS } from "../../mobx/stores/Layout";
import { Children } from "../../types/Preact";
interface Props {
borders?: boolean;
topBorder?: boolean;
bottomBorder?: boolean;
background?: boolean;
transparent?: boolean;
placement: "primary" | "secondary";
}
const Header = styled.div<Props>`
gap: 10px;
height: 48px;
flex: 0 auto;
display: flex;
flex-shrink: 0;
@@ -29,15 +32,11 @@ const Header = styled.div<Props>`
font-weight: 600;
user-select: none;
align-items: center;
height: var(--header-height);
background-size: cover !important;
background-position: center !important;
background-color: var(--primary-header);
/*> div {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}*/
svg {
flex-shrink: 0;
@@ -48,11 +47,21 @@ const Header = styled.div<Props>`
color: var(--secondary-foreground);
}
${() =>
isTouchscreenDevice &&
css`
height: 56px;
`}
${(props) =>
props.transparent
? css`
background-color: rgba(
var(--primary-header-rgb),
max(var(--min-opacity), 0.75)
);
backdrop-filter: blur(10px);
z-index: 20;
position: absolute;
width: 100%;
`
: css`
background-color: var(--primary-header);
`}
${(props) =>
props.background &&
@@ -71,10 +80,16 @@ const Header = styled.div<Props>`
`}
${(props) =>
props.borders &&
props.topBorder &&
css`
border-start-start-radius: 8px;
`}
${(props) =>
props.bottomBorder &&
css`
border-end-start-radius: 8px;
`}
`;
export default Header;
@@ -98,19 +113,24 @@ const IconContainer = styled.div`
`}
`;
interface PageHeaderProps {
type PageHeaderProps = Omit<Props, "placement" | "borders"> & {
noBurger?: boolean;
children: Children;
icon: Children;
}
};
export const PageHeader = observer(
({ children, icon, noBurger }: PageHeaderProps) => {
({ children, icon, noBurger, ...props }: PageHeaderProps) => {
const layout = useApplicationState().layout;
const visible = layout.getSectionState(SIDEBAR_CHANNELS, true);
const { pathname } = useLocation();
return (
<Header placement="primary" borders={!visible}>
<Header
{...props}
placement="primary"
topBorder={!visible}
bottomBorder={!pathname.includes("/server")}>
{!noBurger && <HamburgerAction />}
<IconContainer
onClick={() =>
@@ -135,7 +155,7 @@ export function HamburgerAction() {
function openSidebar() {
document
.querySelector("#app > div > div")
.querySelector("#app > div > div > div")
?.scrollTo({ behavior: "smooth", left: 0 });
}

View File

@@ -1,9 +1,9 @@
import styled from "styled-components";
export default styled.div`
height: 0px;
height: 0;
opacity: 0.6;
flex-shrink: 0;
margin: 8px 10px;
margin: 8px 15px;
border-top: 1px solid var(--tertiary-foreground);
`;