aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-11-29 02:37:28 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-12-09 00:07:39 +0100
commitf416bfecc61075d3dff69821b2d4503d6e1c4037 (patch)
treec0e6f3dab0e79d4117ff6b7dc7ea29f07891f1b7
parentfix(frontend): content assist error recovery (diff)
downloadrefinery-f416bfecc61075d3dff69821b2d4503d6e1c4037.tar.gz
refinery-f416bfecc61075d3dff69821b2d4503d6e1c4037.tar.zst
refinery-f416bfecc61075d3dff69821b2d4503d6e1c4037.zip
fix(frontend): reduce Android rendering errors
-rw-r--r--subprojects/frontend/src/App.tsx31
-rw-r--r--subprojects/frontend/src/Refinery.tsx2
-rw-r--r--subprojects/frontend/src/TopBar.tsx1
-rw-r--r--subprojects/frontend/src/editor/EditorTheme.ts12
-rw-r--r--subprojects/frontend/src/editor/scrollbarViewPlugin.ts74
-rw-r--r--subprojects/frontend/src/theme/ThemeProvider.tsx5
6 files changed, 88 insertions, 37 deletions
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 @@
1import Box from '@mui/material/Box'; 1import Box from '@mui/material/Box';
2import CssBaseline from '@mui/material/CssBaseline'; 2import CssBaseline from '@mui/material/CssBaseline';
3import { StrictMode, Suspense, lazy } from 'react'; 3import { throttle } from 'lodash-es';
4import {
5 StrictMode,
6 Suspense,
7 lazy,
8 useState,
9 useEffect,
10 useMemo,
11} from 'react';
4 12
5import Loading from './Loading'; 13import Loading from './Loading';
6import type RootStore from './RootStore'; 14import type RootStore from './RootStore';
@@ -10,18 +18,37 @@ import ThemeProvider from './theme/ThemeProvider';
10 18
11const Refinery = lazy(() => import('./Refinery.js')); 19const Refinery = lazy(() => import('./Refinery.js'));
12 20
21function useInnerHeight(): number {
22 const [innerHeight, setInnerHeight] = useState(window.innerHeight);
23 const resizeHandler = useMemo(
24 () => throttle(() => setInnerHeight(window.innerHeight), 250),
25 [],
26 );
27 useEffect(() => {
28 window.addEventListener('resize', resizeHandler, { passive: true });
29 return () => {
30 window.removeEventListener('resize', resizeHandler);
31 resizeHandler.cancel();
32 };
33 }, [resizeHandler]);
34 return innerHeight;
35}
36
13export default function App({ 37export default function App({
14 rootStore, 38 rootStore,
15}: { 39}: {
16 rootStore: RootStore; 40 rootStore: RootStore;
17}): JSX.Element { 41}): JSX.Element {
42 // See https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
43 const innerHeight = useInnerHeight();
44
18 return ( 45 return (
19 <StrictMode> 46 <StrictMode>
20 <RootStoreProvider rootStore={rootStore}> 47 <RootStoreProvider rootStore={rootStore}>
21 <ThemeProvider> 48 <ThemeProvider>
22 <CssBaseline enableColorScheme /> 49 <CssBaseline enableColorScheme />
23 <WindowControlsOverlayColor /> 50 <WindowControlsOverlayColor />
24 <Box height="100vh" overflow="auto"> 51 <Box height={`${innerHeight}px`} overflow="hidden">
25 <Suspense fallback={<Loading />}> 52 <Suspense fallback={<Loading />}>
26 <Refinery /> 53 <Refinery />
27 </Suspense> 54 </Suspense>
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 {
11 // @ts-expect-error -- notistack has problems with `exactOptionalPropertyTypes 11 // @ts-expect-error -- notistack has problems with `exactOptionalPropertyTypes
12 <SnackbarProvider TransitionComponent={Grow}> 12 <SnackbarProvider TransitionComponent={Grow}>
13 <UpdateNotification /> 13 <UpdateNotification />
14 <Stack direction="column" height="100vh" overflow="auto"> 14 <Stack direction="column" height="100%" overflow="auto">
15 <TopBar /> 15 <TopBar />
16 <EditorPane /> 16 <EditorPane />
17 </Stack> 17 </Stack>
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 {
36 'geometrychange', 36 'geometrychange',
37 updateWindowControlsOverlayVisible, 37 updateWindowControlsOverlayVisible,
38 ); 38 );
39 updateWindowControlsOverlayVisible.cancel();
39 }; 40 };
40 } 41 }
41 // Nothing to clean up if `windowControlsOverlay` is unsupported. 42 // 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', {
52 }, 52 },
53 }; 53 };
54 54
55 const scrollerThumbOpacity = theme.palette.mode === 'dark' ? 0.16 : 0.28;
56
55 const generalStyle: CSSObject = { 57 const generalStyle: CSSObject = {
56 background: theme.palette.background.default, 58 background: theme.palette.background.default,
57 '&, .cm-editor': { 59 '&, .cm-editor': {
@@ -70,6 +72,7 @@ export default styled('div', {
70 zIndex: 300, 72 zIndex: 300,
71 width: 1, 73 width: 1,
72 marginRight: -1, 74 marginRight: -1,
75 pointerEvents: 'none',
73 }, 76 },
74 '.cm-scroller': { 77 '.cm-scroller': {
75 color: theme.palette.text.secondary, 78 color: theme.palette.text.secondary,
@@ -84,12 +87,17 @@ export default styled('div', {
84 '.cm-scroller-thumb': { 87 '.cm-scroller-thumb': {
85 position: 'absolute', 88 position: 'absolute',
86 background: theme.palette.text.secondary, 89 background: theme.palette.text.secondary,
87 opacity: theme.palette.mode === 'dark' ? 0.16 : 0.28, 90 opacity: scrollerThumbOpacity,
88 transition: theme.transitions.create('opacity', { 91 transition: theme.transitions.create('opacity', {
89 duration: theme.transitions.duration.shortest, 92 duration: theme.transitions.duration.shortest,
90 }), 93 }),
94 touchAction: 'none',
95 WebkitTapHighlightColor: 'transparent',
91 '&:hover': { 96 '&:hover': {
92 opacity: 0.75, 97 opacity: 0.75,
98 '@media (hover: none)': {
99 opacity: scrollerThumbOpacity,
100 },
93 }, 101 },
94 '&.active': { 102 '&.active': {
95 opacity: 1, 103 opacity: 1,
@@ -445,6 +453,7 @@ export default styled('div', {
445 background: theme.palette.text.primary, 453 background: theme.palette.text.primary,
446 border: 'none', 454 border: 'none',
447 cursor: 'pointer', 455 cursor: 'pointer',
456 WebkitTapHighlightColor: 'transparent',
448 [theme.breakpoints.down('sm')]: { 457 [theme.breakpoints.down('sm')]: {
449 margin: '2px 0', 458 margin: '2px 0',
450 }, 459 },
@@ -471,6 +480,7 @@ export default styled('div', {
471 background: 'transparent', 480 background: 'transparent',
472 border: 'none', 481 border: 'none',
473 cursor: 'pointer', 482 cursor: 'pointer',
483 WebkitTapHighlightColor: 'transparent',
474 // Use an inner `span` element to match the height of other text highlights. 484 // Use an inner `span` element to match the height of other text highlights.
475 span: { 485 span: {
476 color: theme.palette.text.secondary, 486 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;
22export const ANNOTATION_WIDTH = SCROLLBAR_WIDTH / 2; 22export const ANNOTATION_WIDTH = SCROLLBAR_WIDTH / 2;
23export const MIN_ANNOTATION_HEIGHT = 1; 23export const MIN_ANNOTATION_HEIGHT = 1;
24 24
25function handleDrag(
26 element: HTMLElement,
27 callback: (movementX: number, movementY: number) => void,
28) {
29 let pointerId: number | undefined;
30 element.addEventListener('pointerdown', (event) => {
31 if (pointerId === undefined) {
32 ({ pointerId } = event);
33 element.setPointerCapture(pointerId);
34 element.classList.add(THUMB_ACTIVE_CLASS);
35 } else {
36 event.preventDefault();
37 // Avoid implicit pointer capture, see https://w3c.github.io/pointerevents/#dfn-implicit-pointer-capture
38 element.releasePointerCapture(event.pointerId);
39 }
40 });
41
42 element.addEventListener('pointermove', (event) => {
43 if (event.pointerId !== pointerId) {
44 return;
45 }
46 callback(event.movementX, event.movementY);
47 event.preventDefault();
48 });
49
50 function scrollEnd(event: PointerEvent) {
51 if (event.pointerId !== pointerId) {
52 return;
53 }
54 pointerId = undefined;
55 element.classList.remove(THUMB_ACTIVE_CLASS);
56 }
57
58 element.addEventListener('pointerup', scrollEnd, { passive: true });
59 element.addEventListener('pointercancel', scrollEnd, { passive: true });
60}
61
25export default function scrollbarViewPlugin( 62export default function scrollbarViewPlugin(
26 editorStore: EditorStore, 63 editorStore: EditorStore,
27): ViewPlugin<PluginValue> { 64): ViewPlugin<PluginValue> {
@@ -63,44 +100,15 @@ export default function scrollbarViewPlugin(
63 100
64 const thumbY = ownerDocument.createElement('div'); 101 const thumbY = ownerDocument.createElement('div');
65 thumbY.className = `${THUMB_CLASS} ${THUMB_Y_CLASS}`; 102 thumbY.className = `${THUMB_CLASS} ${THUMB_Y_CLASS}`;
66 const scrollY = (event: MouseEvent) => { 103 handleDrag(thumbY, (_movementX, movementY) =>
67 scrollDOM.scrollBy({ top: event.movementY / factorY }); 104 scrollDOM.scrollBy({ top: movementY / factorY }),
68 event.preventDefault();
69 };
70 const stopScrollY = () => {
71 thumbY.classList.remove(THUMB_ACTIVE_CLASS);
72 window.removeEventListener('mousemove', scrollY);
73 window.removeEventListener('mouseup', stopScrollY);
74 };
75 thumbY.addEventListener(
76 'mousedown',
77 () => {
78 thumbY.classList.add(THUMB_ACTIVE_CLASS);
79 window.addEventListener('mousemove', scrollY);
80 window.addEventListener('mouseup', stopScrollY, { passive: true });
81 },
82 { passive: true },
83 ); 105 );
84 holder.appendChild(thumbY); 106 holder.appendChild(thumbY);
85 107
86 const thumbX = ownerDocument.createElement('div'); 108 const thumbX = ownerDocument.createElement('div');
87 thumbX.className = `${THUMB_CLASS} ${THUMB_X_CLASS}`; 109 thumbX.className = `${THUMB_CLASS} ${THUMB_X_CLASS}`;
88 const scrollX = (event: MouseEvent) => { 110 handleDrag(thumbX, (movementX) =>
89 scrollDOM.scrollBy({ left: event.movementX / factorX }); 111 scrollDOM.scrollBy({ left: movementX / factorX }),
90 };
91 const stopScrollX = () => {
92 thumbX.classList.remove(THUMB_ACTIVE_CLASS);
93 window.removeEventListener('mousemove', scrollX);
94 window.removeEventListener('mouseup', stopScrollX);
95 };
96 thumbX.addEventListener(
97 'mousedown',
98 () => {
99 thumbX.classList.add(THUMB_ACTIVE_CLASS);
100 window.addEventListener('mousemove', scrollX);
101 window.addEventListener('mouseup', stopScrollX, { passive: true });
102 },
103 { passive: true },
104 ); 112 );
105 holder.appendChild(thumbX); 113 holder.appendChild(thumbX);
106 114
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 = {
73 fontWeightMedium: 600, 73 fontWeightMedium: 600,
74 fontWeightEditorNormal: 400, 74 fontWeightEditorNormal: 400,
75 fontWeightEditorBold: 700, 75 fontWeightEditorBold: 700,
76 button: {
77 // 24px line height for 14px button text means 36px high buttons.
78 // Making sure the button has whole pixel height reduces redering errors on Android.
79 lineHeight: 1.7143,
80 },
76 editor: { 81 editor: {
77 fontFamily: 82 fontFamily:
78 '"JetBrains MonoVariable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace', 83 '"JetBrains MonoVariable", "JetBrains Mono", "Cascadia Code", "Fira Code", monospace',