Desktop support (#4484)

* Remove home link on desktop app

* Spellcheck, installation toasts, background styling, …

* Add email,slack, auth support

* More desktop style tweaks

* Move redirect to client

* cleanup

* Record desktop usage

* docs

* fix: Selection state in search input when double clicking header
This commit is contained in:
Tom Moor
2022-11-27 15:07:48 -08:00
committed by GitHub
parent ea9680c3d7
commit cc333637dd
38 changed files with 492 additions and 83 deletions

View File

@@ -6,6 +6,7 @@ import styled from "styled-components";
import ActionButton, {
Props as ActionButtonProps,
} from "~/components/ActionButton";
import { undraggableOnDesktop } from "~/styles";
type RealProps = {
$fullwidth?: boolean;
@@ -33,6 +34,7 @@ const RealButton = styled(ActionButton)<RealProps>`
cursor: var(--pointer);
user-select: none;
appearance: none !important;
${undraggableOnDesktop()}
${(props) =>
!props.$borderOnHover &&

View File

@@ -0,0 +1,44 @@
import * as React from "react";
import { useHistory } from "react-router-dom";
import { useDesktopTitlebar } from "~/hooks/useDesktopTitlebar";
import useToasts from "~/hooks/useToasts";
import Desktop from "~/utils/Desktop";
export default function DesktopEventHandler() {
useDesktopTitlebar();
const history = useHistory();
const { showToast } = useToasts();
React.useEffect(() => {
Desktop.bridge?.redirect((path: string, replace = false) => {
if (replace) {
history.replace(path);
} else {
history.push(path);
}
});
Desktop.bridge?.updateDownloaded(() => {
showToast("An update is ready to install.", {
type: "info",
timeout: Infinity,
action: {
text: "Install now",
onClick: () => {
Desktop.bridge?.restartAndInstall();
},
},
});
});
Desktop.bridge?.focus(() => {
window.document.body.classList.remove("backgrounded");
});
Desktop.bridge?.blur(() => {
window.document.body.classList.add("backgrounded");
});
}, [history, showToast]);
return null;
}

View File

@@ -12,6 +12,8 @@ import Flex from "~/components/Flex";
import useEventListener from "~/hooks/useEventListener";
import useMobile from "~/hooks/useMobile";
import useStores from "~/hooks/useStores";
import { draggableOnDesktop, fadeOnDesktopBackgrounded } from "~/styles";
import Desktop from "~/utils/Desktop";
import { supportsPassiveListener } from "~/utils/browser";
type Props = {
@@ -26,6 +28,7 @@ function Header({ left, title, actions, hasSidebar }: Props) {
const isMobile = useMobile();
const hasMobileSidebar = hasSidebar && isMobile;
const sidebarCollapsed = ui.isEditing || ui.sidebarCollapsed;
const passThrough = !actions && !left && !title;
@@ -50,7 +53,12 @@ function Header({ left, title, actions, hasSidebar }: Props) {
}, []);
return (
<Wrapper align="center" shrink={false} $passThrough={passThrough}>
<Wrapper
align="center"
shrink={false}
$passThrough={passThrough}
$insetTitleAdjust={sidebarCollapsed && Desktop.hasInsetTitlebar()}
>
{left || hasMobileSidebar ? (
<Breadcrumbs>
{hasMobileSidebar && (
@@ -98,7 +106,12 @@ const Actions = styled(Flex)`
`};
`;
const Wrapper = styled(Flex)<{ $passThrough?: boolean }>`
type WrapperProps = {
$passThrough?: boolean;
$insetTitleAdjust?: boolean;
};
const Wrapper = styled(Flex)<WrapperProps>`
top: 0;
z-index: ${depths.header};
position: sticky;
@@ -120,6 +133,8 @@ const Wrapper = styled(Flex)<{ $passThrough?: boolean }>`
transform: translate3d(0, 0, 0);
min-height: 64px;
justify-content: flex-start;
${draggableOnDesktop()}
${fadeOnDesktopBackgrounded()}
@supports (backdrop-filter: blur(20px)) {
backdrop-filter: blur(20px);
@@ -133,7 +148,8 @@ const Wrapper = styled(Flex)<{ $passThrough?: boolean }>`
${breakpoint("tablet")`
padding: 16px;
justify-content: center;
`};
${(props: WrapperProps) => props.$insetTitleAdjust && `padding-left: 64px;`}
`};
`;
const Title = styled("div")`

View File

@@ -5,6 +5,7 @@ import { VisuallyHidden } from "reakit/VisuallyHidden";
import styled from "styled-components";
import breakpoint from "styled-components-breakpoint";
import Flex from "~/components/Flex";
import { undraggableOnDesktop } from "~/styles";
const RealTextarea = styled.textarea<{ hasIcon?: boolean }>`
border: 0;
@@ -32,6 +33,7 @@ const RealInput = styled.input<{ hasIcon?: boolean }>`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
${undraggableOnDesktop()}
&:disabled,
&::placeholder {
@@ -98,6 +100,9 @@ export const Outline = styled(Flex)<{
align-items: center;
overflow: hidden;
background: ${(props) => props.theme.background};
/* Prevents an issue where input placeholder appears in a selected style when double clicking title bar */
user-select: none;
`;
export const LabelText = styled.div`

View File

@@ -15,6 +15,7 @@ import useMobile from "~/hooks/useMobile";
import usePrevious from "~/hooks/usePrevious";
import useUnmount from "~/hooks/useUnmount";
import { fadeAndScaleIn } from "~/styles/animations";
import Desktop from "~/utils/Desktop";
let openModals = 0;
type Props = {
@@ -222,7 +223,7 @@ const Back = styled(NudeButton)`
position: absolute;
display: none;
align-items: center;
top: 2rem;
top: ${Desktop.hasInsetTitlebar() ? "3rem" : "2rem"};
left: 2rem;
opacity: 0.75;
color: ${(props) => props.theme.text};

View File

@@ -14,6 +14,7 @@ import useCurrentUser from "~/hooks/useCurrentUser";
import usePolicy from "~/hooks/usePolicy";
import useStores from "~/hooks/useStores";
import OrganizationMenu from "~/menus/OrganizationMenu";
import Desktop from "~/utils/Desktop";
import {
homePath,
draftsPath,
@@ -63,7 +64,16 @@ function AppSidebar() {
<HeaderButton
{...props}
title={team.name}
image={<TeamLogo model={team} size={32} alt={t("Logo")} />}
image={
<TeamLogo
model={team}
size={Desktop.hasInsetTitlebar() ? 24 : 32}
alt={t("Logo")}
/>
}
style={
Desktop.hasInsetTitlebar() ? { paddingLeft: 8 } : undefined
}
showDisclosure
/>
)}

View File

@@ -8,6 +8,7 @@ import styled from "styled-components";
import Flex from "~/components/Flex";
import Scrollable from "~/components/Scrollable";
import useAuthorizedSettingsConfig from "~/hooks/useAuthorizedSettingsConfig";
import Desktop from "~/utils/Desktop";
import isCloudHosted from "~/utils/isCloudHosted";
import Sidebar from "./Sidebar";
import Header from "./components/Header";
@@ -32,7 +33,7 @@ function SettingsSidebar() {
title={t("Return to App")}
image={<StyledBackIcon color="currentColor" />}
onClick={returnToApp}
minHeight={48}
minHeight={Desktop.hasInsetTitlebar() ? undefined : 48}
/>
<Flex auto column>

View File

@@ -11,7 +11,9 @@ import useMenuContext from "~/hooks/useMenuContext";
import usePrevious from "~/hooks/usePrevious";
import useStores from "~/hooks/useStores";
import AccountMenu from "~/menus/AccountMenu";
import { draggableOnDesktop, fadeOnDesktopBackgrounded } from "~/styles";
import { fadeIn } from "~/styles/animations";
import Desktop from "~/utils/Desktop";
import Avatar from "../Avatar";
import HeaderButton, { HeaderButtonProps } from "./components/HeaderButton";
import ResizeBorder from "./components/ResizeBorder";
@@ -251,6 +253,9 @@ const Container = styled(Flex)<ContainerProps>`
z-index: ${depths.sidebar};
max-width: 70%;
min-width: 280px;
padding-top: ${Desktop.hasInsetTitlebar() ? 24 : 0}px;
${draggableOnDesktop()}
${fadeOnDesktopBackgrounded()}
${Positioner} {
display: none;
@@ -265,7 +270,9 @@ const Container = styled(Flex)<ContainerProps>`
margin: 0;
min-width: 0;
transform: translateX(${(props: ContainerProps) =>
props.$collapsed ? "calc(-100% + 16px)" : 0});
props.$collapsed
? `calc(-100% + ${Desktop.hasInsetTitlebar() ? 8 : 16}px)`
: 0});
&:hover,
&:focus-within {

View File

@@ -3,7 +3,7 @@ import * as React from "react";
import styled from "styled-components";
import Flex from "~/components/Flex";
export type HeaderButtonProps = {
export type HeaderButtonProps = React.ComponentProps<typeof Wrapper> & {
title: React.ReactNode;
image: React.ReactNode;
minHeight?: number;

View File

@@ -1,4 +1,5 @@
import styled from "styled-components";
import { undraggableOnDesktop } from "~/styles";
const ResizeBorder = styled.div<{ dir?: "left" | "right" }>`
position: absolute;
@@ -8,6 +9,7 @@ const ResizeBorder = styled.div<{ dir?: "left" | "right" }>`
left: ${(props) => (props.dir === "right" ? "-1px" : "auto")};
width: 2px;
cursor: col-resize;
${undraggableOnDesktop()}
&:hover {
transition-delay: 500ms;
@@ -22,6 +24,7 @@ const ResizeBorder = styled.div<{ dir?: "left" | "right" }>`
bottom: 0;
right: -4px;
width: 10px;
${undraggableOnDesktop()}
}
`;

View File

@@ -4,6 +4,7 @@ import styled, { useTheme, css } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import EventBoundary from "~/components/EventBoundary";
import NudeButton from "~/components/NudeButton";
import { undraggableOnDesktop } from "~/styles";
import { NavigationNode } from "~/types";
import Disclosure from "./Disclosure";
import NavLink, { Props as NavLinkProps } from "./NavLink";
@@ -181,6 +182,7 @@ const Link = styled(NavLink)<{
font-size: 16px;
cursor: var(--pointer);
overflow: hidden;
${undraggableOnDesktop()}
${(props) =>
props.$disabled &&

View File

@@ -2,6 +2,7 @@ import * as React from "react";
import styled from "styled-components";
import { LabelText } from "~/components/Input";
import Text from "~/components/Text";
import { undraggableOnDesktop } from "~/styles";
type Props = React.HTMLAttributes<HTMLInputElement> & {
width?: number;
@@ -62,6 +63,7 @@ function Switch({
const Wrapper = styled.div`
padding-bottom: 8px;
${undraggableOnDesktop()}
`;
const InlineLabelText = styled(LabelText)`

View File

@@ -69,14 +69,14 @@ function Toast({ closeAfterMs = 3000, onRequestClose, toast }: Props) {
const Action = styled.span`
display: inline-block;
padding: 10px 12px;
height: 100%;
text-transform: uppercase;
font-size: 12px;
padding: 4px 8px;
color: ${(props) => props.theme.toastText};
background: ${(props) => darken(0.05, props.theme.toastBackground)};
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-radius: 4px;
margin-left: 8px;
margin-right: -4px;
font-weight: 500;
user-select: none;
&:hover {
background: ${(props) => darken(0.1, props.theme.toastBackground)};