Hide document UI while typing

This commit is contained in:
Tom Moor
2023-09-04 22:10:27 -04:00
parent 2358c3d13d
commit 5f788012db
7 changed files with 49 additions and 12 deletions

View File

@@ -1,5 +1,6 @@
import * as React from "react"; import * as React from "react";
import { Editor } from "~/editor"; import { Editor } from "~/editor";
import useIdle from "~/hooks/useIdle";
export type DocumentContextValue = { export type DocumentContextValue = {
/** The current editor instance for this document. */ /** The current editor instance for this document. */
@@ -16,4 +17,21 @@ const DocumentContext = React.createContext<DocumentContextValue>({
export const useDocumentContext = () => React.useContext(DocumentContext); export const useDocumentContext = () => React.useContext(DocumentContext);
const activityEvents = [
"click",
"mousemove",
"DOMMouseScroll",
"mousewheel",
"mousedown",
"touchstart",
"touchmove",
"focus",
];
export const useEditingFocus = () => {
const { editor } = useDocumentContext();
const isIdle = useIdle(3000, activityEvents);
return isIdle && !!editor?.view.hasFocus();
};
export default DocumentContext; export default DocumentContext;

View File

@@ -21,9 +21,10 @@ type Props = {
title: React.ReactNode; title: React.ReactNode;
actions?: React.ReactNode; actions?: React.ReactNode;
hasSidebar?: boolean; hasSidebar?: boolean;
className?: string;
}; };
function Header({ left, title, actions, hasSidebar }: Props) { function Header({ left, title, actions, hasSidebar, className }: Props) {
const { ui } = useStores(); const { ui } = useStores();
const isMobile = useMobile(); const isMobile = useMobile();
const hasMobileSidebar = hasSidebar && isMobile; const hasMobileSidebar = hasSidebar && isMobile;
@@ -54,6 +55,7 @@ function Header({ left, title, actions, hasSidebar }: Props) {
<Wrapper <Wrapper
align="center" align="center"
shrink={false} shrink={false}
className={className}
$passThrough={passThrough} $passThrough={passThrough}
$insetTitleAdjust={ui.sidebarIsClosed && Desktop.hasInsetTitlebar()} $insetTitleAdjust={ui.sidebarIsClosed && Desktop.hasInsetTitlebar()}
> >

View File

@@ -17,10 +17,14 @@ const activityEvents = [
/** /**
* Hook to detect user idle state. * Hook to detect user idle state.
* *
* @param {number} timeToIdle * @param timeToIdle The time in ms until idle
* @param events The events to listen to
* @returns boolean if the user is idle * @returns boolean if the user is idle
*/ */
export default function useIdle(timeToIdle: number = 3 * Minute) { export default function useIdle(
timeToIdle: number = 3 * Minute,
events = activityEvents
) {
const [isIdle, setIsIdle] = React.useState(false); const [isIdle, setIsIdle] = React.useState(false);
const timeout = React.useRef<ReturnType<typeof setTimeout>>(); const timeout = React.useRef<ReturnType<typeof setTimeout>>();
@@ -40,15 +44,15 @@ export default function useIdle(timeToIdle: number = 3 * Minute) {
onActivity(); onActivity();
}, 1000); }, 1000);
activityEvents.forEach((eventName) => events.forEach((eventName) =>
window.addEventListener(eventName, handleUserActivityEvent) window.addEventListener(eventName, handleUserActivityEvent)
); );
return () => { return () => {
activityEvents.forEach((eventName) => events.forEach((eventName) =>
window.removeEventListener(eventName, handleUserActivityEvent) window.removeEventListener(eventName, handleUserActivityEvent)
); );
}; };
}, [onActivity]); }, [events, onActivity]);
return isIdle; return isIdle;
} }

View File

@@ -20,6 +20,7 @@ import Badge from "~/components/Badge";
import Button from "~/components/Button"; import Button from "~/components/Button";
import Collaborators from "~/components/Collaborators"; import Collaborators from "~/components/Collaborators";
import DocumentBreadcrumb from "~/components/DocumentBreadcrumb"; import DocumentBreadcrumb from "~/components/DocumentBreadcrumb";
import { useEditingFocus } from "~/components/DocumentContext";
import Header from "~/components/Header"; import Header from "~/components/Header";
import EmojiIcon from "~/components/Icons/EmojiIcon"; import EmojiIcon from "~/components/Icons/EmojiIcon";
import Star from "~/components/Star"; import Star from "~/components/Star";
@@ -88,6 +89,7 @@ function DocumentHeader({
const { team, user } = auth; const { team, user } = auth;
const isMobile = useMobile(); const isMobile = useMobile();
const isRevision = !!revision; const isRevision = !!revision;
const isEditingFocus = useEditingFocus();
// We cache this value for as long as the component is mounted so that if you // We cache this value for as long as the component is mounted so that if you
// apply a template there is still the option to replace it until the user // apply a template there is still the option to replace it until the user
@@ -168,7 +170,8 @@ function DocumentHeader({
if (shareId) { if (shareId) {
return ( return (
<Header <StyledHeader
$hidden={isEditingFocus}
title={document.title} title={document.title}
hasSidebar={!!sharedTree} hasSidebar={!!sharedTree}
left={ left={
@@ -196,7 +199,8 @@ function DocumentHeader({
return ( return (
<> <>
<Header <StyledHeader
$hidden={isEditingFocus}
hasSidebar hasSidebar
left={ left={
isMobile ? ( isMobile ? (
@@ -362,6 +366,11 @@ function DocumentHeader({
); );
} }
const StyledHeader = styled(Header)<{ $hidden: boolean }>`
transition: opacity 500ms ease-in-out;
${(props) => props.$hidden && "opacity: 0;"}
`;
const ArchivedBadge = styled(Badge)` const ArchivedBadge = styled(Badge)`
position: absolute; position: absolute;
`; `;

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import styled from "styled-components"; import styled from "styled-components";
import breakpoint from "styled-components-breakpoint"; import breakpoint from "styled-components-breakpoint";
import KeyboardShortcuts from "~/scenes/KeyboardShortcuts"; import KeyboardShortcuts from "~/scenes/KeyboardShortcuts";
import { useEditingFocus } from "~/components/DocumentContext";
import NudeButton from "~/components/NudeButton"; import NudeButton from "~/components/NudeButton";
import Tooltip from "~/components/Tooltip"; import Tooltip from "~/components/Tooltip";
import useStores from "~/hooks/useStores"; import useStores from "~/hooks/useStores";
@@ -12,6 +13,7 @@ import useStores from "~/hooks/useStores";
function KeyboardShortcutsButton() { function KeyboardShortcutsButton() {
const { t } = useTranslation(); const { t } = useTranslation();
const { dialogs } = useStores(); const { dialogs } = useStores();
const isEditingFocus = useEditingFocus();
const handleOpenKeyboardShortcuts = () => { const handleOpenKeyboardShortcuts = () => {
dialogs.openGuide({ dialogs.openGuide({
@@ -22,18 +24,20 @@ function KeyboardShortcutsButton() {
return ( return (
<Tooltip tooltip={t("Keyboard shortcuts")} shortcut="?" delay={500}> <Tooltip tooltip={t("Keyboard shortcuts")} shortcut="?" delay={500}>
<Button onClick={handleOpenKeyboardShortcuts}> <Button onClick={handleOpenKeyboardShortcuts} $hidden={isEditingFocus}>
<KeyboardIcon /> <KeyboardIcon />
</Button> </Button>
</Tooltip> </Tooltip>
); );
} }
const Button = styled(NudeButton)` const Button = styled(NudeButton)<{ $hidden: boolean }>`
display: none; display: none;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
margin: 24px; margin: 24px;
transition: opacity 500ms ease-in-out;
${(props) => props.$hidden && "opacity: 0;"}
${breakpoint("tablet")` ${breakpoint("tablet")`
display: block; display: block;

View File

@@ -122,7 +122,7 @@ function Preferences() {
name={UserPreference.SeamlessEdit} name={UserPreference.SeamlessEdit}
label={t("Separate editing")} label={t("Separate editing")}
description={t( description={t(
`When enabled documents have a separate editing mode, when disabled documents are always editable when you have permission.` `When enabled, documents have a separate editing mode. When disabled, documents are always editable when you have permission.`
)} )}
> >
<Switch <Switch

View File

@@ -834,7 +834,7 @@
"Show a hand cursor when hovering over interactive elements.": "Show a hand cursor when hovering over interactive elements.", "Show a hand cursor when hovering over interactive elements.": "Show a hand cursor when hovering over interactive elements.",
"Show line numbers": "Show line numbers", "Show line numbers": "Show line numbers",
"Show line numbers on code blocks in documents.": "Show line numbers on code blocks in documents.", "Show line numbers on code blocks in documents.": "Show line numbers on code blocks in documents.",
"When enabled documents have a separate editing mode, when disabled documents are always editable when you have permission.": "When enabled documents have a separate editing mode, when disabled documents are always editable when you have permission.", "When enabled, documents have a separate editing mode. When disabled, documents are always editable when you have permission.": "When enabled, documents have a separate editing mode. When disabled, documents are always editable when you have permission.",
"Remember previous location": "Remember previous location", "Remember previous location": "Remember previous location",
"Automatically return to the document you were last viewing when the app is re-opened.": "Automatically return to the document you were last viewing when the app is re-opened.", "Automatically return to the document you were last viewing when the app is re-opened.": "Automatically return to the document you were last viewing when the app is re-opened.",
"You may delete your account at any time, note that this is unrecoverable": "You may delete your account at any time, note that this is unrecoverable", "You may delete your account at any time, note that this is unrecoverable": "You may delete your account at any time, note that this is unrecoverable",