From f416bfecc61075d3dff69821b2d4503d6e1c4037 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Tue, 29 Nov 2022 02:37:28 +0100 Subject: fix(frontend): reduce Android rendering errors --- subprojects/frontend/src/App.tsx | 31 ++++++++- subprojects/frontend/src/Refinery.tsx | 2 +- subprojects/frontend/src/TopBar.tsx | 1 + subprojects/frontend/src/editor/EditorTheme.ts | 12 +++- .../frontend/src/editor/scrollbarViewPlugin.ts | 74 ++++++++++++---------- subprojects/frontend/src/theme/ThemeProvider.tsx | 5 ++ 6 files changed, 88 insertions(+), 37 deletions(-) (limited to 'subprojects') diff --git a/subprojects/frontend/src/App.tsx b/subprojects/frontend/src/App.tsx index b162e551..cd394345 100644 --- a/subprojects/frontend/src/App.tsx +++ b/subprojects/frontend/src/App.tsx @@ -1,6 +1,14 @@ import Box from '@mui/material/Box'; import CssBaseline from '@mui/material/CssBaseline'; -import { StrictMode, Suspense, lazy } from 'react'; +import { throttle } from 'lodash-es'; +import { + StrictMode, + Suspense, + lazy, + useState, + useEffect, + useMemo, +} from 'react'; import Loading from './Loading'; import type RootStore from './RootStore'; @@ -10,18 +18,37 @@ import ThemeProvider from './theme/ThemeProvider'; const Refinery = lazy(() => import('./Refinery.js')); +function useInnerHeight(): number { + const [innerHeight, setInnerHeight] = useState(window.innerHeight); + const resizeHandler = useMemo( + () => throttle(() => setInnerHeight(window.innerHeight), 250), + [], + ); + useEffect(() => { + window.addEventListener('resize', resizeHandler, { passive: true }); + return () => { + window.removeEventListener('resize', resizeHandler); + resizeHandler.cancel(); + }; + }, [resizeHandler]); + return innerHeight; +} + export default function App({ rootStore, }: { rootStore: RootStore; }): JSX.Element { + // See https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ + const innerHeight = useInnerHeight(); + return ( - + }> diff --git a/subprojects/frontend/src/Refinery.tsx b/subprojects/frontend/src/Refinery.tsx index d6bd6159..93a82ee1 100644 --- a/subprojects/frontend/src/Refinery.tsx +++ b/subprojects/frontend/src/Refinery.tsx @@ -11,7 +11,7 @@ export default function Refinery(): JSX.Element { // @ts-expect-error -- notistack has problems with `exactOptionalPropertyTypes - + diff --git a/subprojects/frontend/src/TopBar.tsx b/subprojects/frontend/src/TopBar.tsx index c943f7c4..0f757986 100644 --- a/subprojects/frontend/src/TopBar.tsx +++ b/subprojects/frontend/src/TopBar.tsx @@ -36,6 +36,7 @@ function useWindowControlsOverlayVisible(): boolean { 'geometrychange', updateWindowControlsOverlayVisible, ); + updateWindowControlsOverlayVisible.cancel(); }; } // Nothing to clean up if `windowControlsOverlay` is unsupported. diff --git a/subprojects/frontend/src/editor/EditorTheme.ts b/subprojects/frontend/src/editor/EditorTheme.ts index c3cbffc8..89bc8932 100644 --- a/subprojects/frontend/src/editor/EditorTheme.ts +++ b/subprojects/frontend/src/editor/EditorTheme.ts @@ -52,6 +52,8 @@ export default styled('div', { }, }; + const scrollerThumbOpacity = theme.palette.mode === 'dark' ? 0.16 : 0.28; + const generalStyle: CSSObject = { background: theme.palette.background.default, '&, .cm-editor': { @@ -70,6 +72,7 @@ export default styled('div', { zIndex: 300, width: 1, marginRight: -1, + pointerEvents: 'none', }, '.cm-scroller': { color: theme.palette.text.secondary, @@ -84,12 +87,17 @@ export default styled('div', { '.cm-scroller-thumb': { position: 'absolute', background: theme.palette.text.secondary, - opacity: theme.palette.mode === 'dark' ? 0.16 : 0.28, + opacity: scrollerThumbOpacity, transition: theme.transitions.create('opacity', { duration: theme.transitions.duration.shortest, }), + touchAction: 'none', + WebkitTapHighlightColor: 'transparent', '&:hover': { opacity: 0.75, + '@media (hover: none)': { + opacity: scrollerThumbOpacity, + }, }, '&.active': { opacity: 1, @@ -445,6 +453,7 @@ export default styled('div', { background: theme.palette.text.primary, border: 'none', cursor: 'pointer', + WebkitTapHighlightColor: 'transparent', [theme.breakpoints.down('sm')]: { margin: '2px 0', }, @@ -471,6 +480,7 @@ export default styled('div', { background: 'transparent', border: 'none', cursor: 'pointer', + WebkitTapHighlightColor: 'transparent', // Use an inner `span` element to match the height of other text highlights. span: { color: theme.palette.text.secondary, diff --git a/subprojects/frontend/src/editor/scrollbarViewPlugin.ts b/subprojects/frontend/src/editor/scrollbarViewPlugin.ts index 9ee70441..c1eb2bbd 100644 --- a/subprojects/frontend/src/editor/scrollbarViewPlugin.ts +++ b/subprojects/frontend/src/editor/scrollbarViewPlugin.ts @@ -22,6 +22,43 @@ export const SCROLLBAR_WIDTH = 12; export const ANNOTATION_WIDTH = SCROLLBAR_WIDTH / 2; export const MIN_ANNOTATION_HEIGHT = 1; +function handleDrag( + element: HTMLElement, + callback: (movementX: number, movementY: number) => void, +) { + let pointerId: number | undefined; + element.addEventListener('pointerdown', (event) => { + if (pointerId === undefined) { + ({ pointerId } = event); + element.setPointerCapture(pointerId); + element.classList.add(THUMB_ACTIVE_CLASS); + } else { + event.preventDefault(); + // Avoid implicit pointer capture, see https://w3c.github.io/pointerevents/#dfn-implicit-pointer-capture + element.releasePointerCapture(event.pointerId); + } + }); + + element.addEventListener('pointermove', (event) => { + if (event.pointerId !== pointerId) { + return; + } + callback(event.movementX, event.movementY); + event.preventDefault(); + }); + + function scrollEnd(event: PointerEvent) { + if (event.pointerId !== pointerId) { + return; + } + pointerId = undefined; + element.classList.remove(THUMB_ACTIVE_CLASS); + } + + element.addEventListener('pointerup', scrollEnd, { passive: true }); + element.addEventListener('pointercancel', scrollEnd, { passive: true }); +} + export default function scrollbarViewPlugin( editorStore: EditorStore, ): ViewPlugin { @@ -63,44 +100,15 @@ export default function scrollbarViewPlugin( const thumbY = ownerDocument.createElement('div'); thumbY.className = `${THUMB_CLASS} ${THUMB_Y_CLASS}`; - const scrollY = (event: MouseEvent) => { - scrollDOM.scrollBy({ top: event.movementY / factorY }); - event.preventDefault(); - }; - const stopScrollY = () => { - thumbY.classList.remove(THUMB_ACTIVE_CLASS); - window.removeEventListener('mousemove', scrollY); - window.removeEventListener('mouseup', stopScrollY); - }; - thumbY.addEventListener( - 'mousedown', - () => { - thumbY.classList.add(THUMB_ACTIVE_CLASS); - window.addEventListener('mousemove', scrollY); - window.addEventListener('mouseup', stopScrollY, { passive: true }); - }, - { passive: true }, + handleDrag(thumbY, (_movementX, movementY) => + scrollDOM.scrollBy({ top: movementY / factorY }), ); holder.appendChild(thumbY); const thumbX = ownerDocument.createElement('div'); thumbX.className = `${THUMB_CLASS} ${THUMB_X_CLASS}`; - const scrollX = (event: MouseEvent) => { - scrollDOM.scrollBy({ left: event.movementX / factorX }); - }; - const stopScrollX = () => { - thumbX.classList.remove(THUMB_ACTIVE_CLASS); - window.removeEventListener('mousemove', scrollX); - window.removeEventListener('mouseup', stopScrollX); - }; - thumbX.addEventListener( - 'mousedown', - () => { - thumbX.classList.add(THUMB_ACTIVE_CLASS); - window.addEventListener('mousemove', scrollX); - window.addEventListener('mouseup', stopScrollX, { passive: true }); - }, - { passive: true }, + handleDrag(thumbX, (movementX) => + scrollDOM.scrollBy({ left: movementX / factorX }), ); holder.appendChild(thumbX); diff --git a/subprojects/frontend/src/theme/ThemeProvider.tsx b/subprojects/frontend/src/theme/ThemeProvider.tsx index 9cf870d5..85844d3c 100644 --- a/subprojects/frontend/src/theme/ThemeProvider.tsx +++ b/subprojects/frontend/src/theme/ThemeProvider.tsx @@ -73,6 +73,11 @@ const typography: TypographyVariantsOptions = { fontWeightMedium: 600, fontWeightEditorNormal: 400, fontWeightEditorBold: 700, + button: { + // 24px line height for 14px button text means 36px high buttons. + // Making sure the button has whole pixel height reduces redering errors on Android. + lineHeight: 1.7143, + }, editor: { fontFamily: '"JetBrains MonoVariable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace', -- cgit v1.2.3-54-g00ecf