diff options
author | Kristóf Marussy <kristof@marussy.com> | 2022-08-21 00:33:01 +0200 |
---|---|---|
committer | Kristóf Marussy <kristof@marussy.com> | 2022-08-21 02:01:59 +0200 |
commit | 8ebc24a5ba1c87cb5cb14fbaff3bee329e30fc15 (patch) | |
tree | 2ea323c637ef371d4bdfc9ccc69460147dd1597b | |
parent | feat(frontend): try to match OS theme (diff) | |
download | refinery-8ebc24a5ba1c87cb5cb14fbaff3bee329e30fc15.tar.gz refinery-8ebc24a5ba1c87cb5cb14fbaff3bee329e30fc15.tar.zst refinery-8ebc24a5ba1c87cb5cb14fbaff3bee329e30fc15.zip |
feat(frontend): overlay window controls
Might need manual intervention in browsers, e.g.
https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/window-controls-overlay#enable-the-window-controls-overlay-api-in-microsoft-edge
-rw-r--r-- | subprojects/frontend/package.json | 2 | ||||
-rw-r--r-- | subprojects/frontend/src/TopBar.tsx | 58 | ||||
-rw-r--r-- | subprojects/frontend/src/WindowControlsOverlayColor.tsx | 21 | ||||
-rw-r--r-- | subprojects/frontend/src/index.tsx | 2 | ||||
-rw-r--r-- | subprojects/frontend/types/windowControlsOverlay.d.ts | 32 | ||||
-rw-r--r-- | subprojects/frontend/vite.config.ts | 2 | ||||
-rw-r--r-- | yarn.lock | 25 |
7 files changed, 140 insertions, 2 deletions
diff --git a/subprojects/frontend/package.json b/subprojects/frontend/package.json index aa453cd0..448b9710 100644 --- a/subprojects/frontend/package.json +++ b/subprojects/frontend/package.json | |||
@@ -40,8 +40,10 @@ | |||
40 | "@material-icons/svg": "^1.0.32", | 40 | "@material-icons/svg": "^1.0.32", |
41 | "@mui/icons-material": "5.8.4", | 41 | "@mui/icons-material": "5.8.4", |
42 | "@mui/material": "5.10.1", | 42 | "@mui/material": "5.10.1", |
43 | "@types/lodash-es": "^4.17.6", | ||
43 | "ansi-styles": "^6.1.0", | 44 | "ansi-styles": "^6.1.0", |
44 | "escape-string-regexp": "^5.0.0", | 45 | "escape-string-regexp": "^5.0.0", |
46 | "lodash-es": "^4.17.21", | ||
45 | "loglevel": "^1.8.0", | 47 | "loglevel": "^1.8.0", |
46 | "loglevel-plugin-prefix": "^0.8.4", | 48 | "loglevel-plugin-prefix": "^0.8.4", |
47 | "mobx": "^6.6.1", | 49 | "mobx": "^6.6.1", |
diff --git a/subprojects/frontend/src/TopBar.tsx b/subprojects/frontend/src/TopBar.tsx index 5ad80d40..4424e6b3 100644 --- a/subprojects/frontend/src/TopBar.tsx +++ b/subprojects/frontend/src/TopBar.tsx | |||
@@ -1,11 +1,47 @@ | |||
1 | import AppBar from '@mui/material/AppBar'; | 1 | import AppBar from '@mui/material/AppBar'; |
2 | import Toolbar from '@mui/material/Toolbar'; | 2 | import Toolbar from '@mui/material/Toolbar'; |
3 | import Typography from '@mui/material/Typography'; | 3 | import Typography from '@mui/material/Typography'; |
4 | import React from 'react'; | 4 | import { throttle } from 'lodash-es'; |
5 | import React, { useEffect, useMemo, useState } from 'react'; | ||
5 | 6 | ||
6 | import ToggleDarkModeButton from './ToggleDarkModeButton'; | 7 | import ToggleDarkModeButton from './ToggleDarkModeButton'; |
7 | 8 | ||
9 | function useWindowControlsOverlayVisible(): boolean { | ||
10 | const [windowControlsOverlayVisible, setWindowControlsOverlayVisible] = | ||
11 | useState(false); | ||
12 | const updateWindowControlsOverlayVisible = useMemo( | ||
13 | () => | ||
14 | throttle( | ||
15 | ({ visible }: WindowControlsOverlayGeometryChangeEvent) => | ||
16 | setWindowControlsOverlayVisible(visible), | ||
17 | 250, | ||
18 | ), | ||
19 | [], | ||
20 | ); | ||
21 | useEffect(() => { | ||
22 | if ('windowControlsOverlay' in navigator) { | ||
23 | const { windowControlsOverlay } = navigator; | ||
24 | setWindowControlsOverlayVisible(windowControlsOverlay.visible); | ||
25 | windowControlsOverlay.addEventListener( | ||
26 | 'geometrychange', | ||
27 | updateWindowControlsOverlayVisible, | ||
28 | ); | ||
29 | return () => { | ||
30 | windowControlsOverlay.removeEventListener( | ||
31 | 'geometrychange', | ||
32 | updateWindowControlsOverlayVisible, | ||
33 | ); | ||
34 | }; | ||
35 | } | ||
36 | // Nothing to clean up if `windowControlsOverlay` is unsupported. | ||
37 | return () => {}; | ||
38 | }, [updateWindowControlsOverlayVisible]); | ||
39 | return windowControlsOverlayVisible; | ||
40 | } | ||
41 | |||
8 | export default function TopBar(): JSX.Element { | 42 | export default function TopBar(): JSX.Element { |
43 | const overlayVisible = useWindowControlsOverlayVisible(); | ||
44 | |||
9 | return ( | 45 | return ( |
10 | <AppBar | 46 | <AppBar |
11 | position="static" | 47 | position="static" |
@@ -14,9 +50,27 @@ export default function TopBar(): JSX.Element { | |||
14 | sx={(theme) => ({ | 50 | sx={(theme) => ({ |
15 | background: theme.palette.outer.background, | 51 | background: theme.palette.outer.background, |
16 | borderBottom: `1px solid ${theme.palette.outer.border}`, | 52 | borderBottom: `1px solid ${theme.palette.outer.border}`, |
53 | appRegion: 'drag', | ||
54 | '.MuiButtonBase-root': { | ||
55 | appRegion: 'no-drag', | ||
56 | }, | ||
17 | })} | 57 | })} |
18 | > | 58 | > |
19 | <Toolbar> | 59 | <Toolbar |
60 | sx={{ | ||
61 | ...(overlayVisible | ||
62 | ? { | ||
63 | marginLeft: 'env(titlebar-area-x, 0)', | ||
64 | marginTop: 'env(titlebar-area-y, 0)', | ||
65 | width: 'env(titlebar-area-width, 100%)', | ||
66 | minHeight: 'env(titlebar-area-height, auto)', | ||
67 | } | ||
68 | : { | ||
69 | minHeight: 'auto', | ||
70 | }), | ||
71 | py: 0.5, | ||
72 | }} | ||
73 | > | ||
20 | <Typography variant="h6" component="h1" flexGrow={1}> | 74 | <Typography variant="h6" component="h1" flexGrow={1}> |
21 | Refinery | 75 | Refinery |
22 | </Typography> | 76 | </Typography> |
diff --git a/subprojects/frontend/src/WindowControlsOverlayColor.tsx b/subprojects/frontend/src/WindowControlsOverlayColor.tsx new file mode 100644 index 00000000..14eda566 --- /dev/null +++ b/subprojects/frontend/src/WindowControlsOverlayColor.tsx | |||
@@ -0,0 +1,21 @@ | |||
1 | import { useTheme } from '@mui/material/styles'; | ||
2 | import { useEffect } from 'react'; | ||
3 | |||
4 | export default function WindowControlsOverlayColor(): null { | ||
5 | const { | ||
6 | palette: { | ||
7 | outer: { background }, | ||
8 | }, | ||
9 | } = useTheme(); | ||
10 | useEffect(() => { | ||
11 | document.head | ||
12 | .querySelectorAll('meta[name="theme-color"]') | ||
13 | .forEach((meta) => meta.remove()); | ||
14 | const meta = document.createElement('meta'); | ||
15 | meta.name = 'theme-color'; | ||
16 | meta.content = background; | ||
17 | document.head.appendChild(meta); | ||
18 | }, [background]); | ||
19 | |||
20 | return null; | ||
21 | } | ||
diff --git a/subprojects/frontend/src/index.tsx b/subprojects/frontend/src/index.tsx index a65821ef..460f6f77 100644 --- a/subprojects/frontend/src/index.tsx +++ b/subprojects/frontend/src/index.tsx | |||
@@ -6,6 +6,7 @@ import { createRoot } from 'react-dom/client'; | |||
6 | import Loading from './Loading'; | 6 | import Loading from './Loading'; |
7 | import RegisterServiceWorker from './RegisterServiceWorker'; | 7 | import RegisterServiceWorker from './RegisterServiceWorker'; |
8 | import RootStore, { RootStoreProvider } from './RootStore'; | 8 | import RootStore, { RootStoreProvider } from './RootStore'; |
9 | import WindowControlsOverlayColor from './WindowControlsOverlayColor'; | ||
9 | import ThemeProvider from './theme/ThemeProvider'; | 10 | import ThemeProvider from './theme/ThemeProvider'; |
10 | import getLogger from './utils/getLogger'; | 11 | import getLogger from './utils/getLogger'; |
11 | 12 | ||
@@ -67,6 +68,7 @@ const app = ( | |||
67 | <RootStoreProvider rootStore={rootStore}> | 68 | <RootStoreProvider rootStore={rootStore}> |
68 | <ThemeProvider> | 69 | <ThemeProvider> |
69 | <CssBaseline enableColorScheme /> | 70 | <CssBaseline enableColorScheme /> |
71 | <WindowControlsOverlayColor /> | ||
70 | <SnackbarProvider> | 72 | <SnackbarProvider> |
71 | <RegisterServiceWorker /> | 73 | <RegisterServiceWorker /> |
72 | <Suspense fallback={<Loading />}> | 74 | <Suspense fallback={<Loading />}> |
diff --git a/subprojects/frontend/types/windowControlsOverlay.d.ts b/subprojects/frontend/types/windowControlsOverlay.d.ts new file mode 100644 index 00000000..d8f3182f --- /dev/null +++ b/subprojects/frontend/types/windowControlsOverlay.d.ts | |||
@@ -0,0 +1,32 @@ | |||
1 | interface WindowControlsOverlayGeometryChangeEvent extends Event { | ||
2 | titlebarAreaRect: DOMRect; | ||
3 | |||
4 | visible: boolean; | ||
5 | } | ||
6 | |||
7 | interface WindowControlsOverlay { | ||
8 | readonly visible: boolean; | ||
9 | |||
10 | getTitlebarAreaRect(): DOMRect; | ||
11 | |||
12 | addEventListener( | ||
13 | type: 'geometrychange', | ||
14 | listener: ( | ||
15 | this: WindowControlsOverlay, | ||
16 | event: WindowControlsOverlayGeometryChangeEvent, | ||
17 | ) => unknown, | ||
18 | options?: boolean | AddEventListenerOptions, | ||
19 | ); | ||
20 | |||
21 | removeEventListener( | ||
22 | type: 'geometrychange', | ||
23 | listener: ( | ||
24 | this: WindowControlsOverlay, | ||
25 | event: WindowControlsOverlayGeometryChangeEvent, | ||
26 | ) => unknown, | ||
27 | ); | ||
28 | } | ||
29 | |||
30 | interface Navigator { | ||
31 | windowControlsOverlay?: WindowControlsOverlay; | ||
32 | } | ||
diff --git a/subprojects/frontend/vite.config.ts b/subprojects/frontend/vite.config.ts index 6ec2d513..f2e0f15c 100644 --- a/subprojects/frontend/vite.config.ts +++ b/subprojects/frontend/vite.config.ts | |||
@@ -111,6 +111,8 @@ export default defineConfig({ | |||
111 | description: | 111 | description: |
112 | 'An efficient graph sovler for generating well-formed models', | 112 | 'An efficient graph sovler for generating well-formed models', |
113 | theme_color: '#21252b', | 113 | theme_color: '#21252b', |
114 | display_override: ['window-controls-overlay'], | ||
115 | display: 'standalone', | ||
114 | background_color: '#21252b', | 116 | background_color: '#21252b', |
115 | icons: [ | 117 | icons: [ |
116 | { | 118 | { |
@@ -2010,6 +2010,7 @@ __metadata: | |||
2010 | "@mui/material": 5.10.1 | 2010 | "@mui/material": 5.10.1 |
2011 | "@types/eslint": ^8.4.6 | 2011 | "@types/eslint": ^8.4.6 |
2012 | "@types/html-minifier-terser": ^7.0.0 | 2012 | "@types/html-minifier-terser": ^7.0.0 |
2013 | "@types/lodash-es": ^4.17.6 | ||
2013 | "@types/node": ^18.7.8 | 2014 | "@types/node": ^18.7.8 |
2014 | "@types/prettier": ^2.7.0 | 2015 | "@types/prettier": ^2.7.0 |
2015 | "@types/react": ^18.0.17 | 2016 | "@types/react": ^18.0.17 |
@@ -2031,6 +2032,7 @@ __metadata: | |||
2031 | eslint-plugin-react: ^7.30.1 | 2032 | eslint-plugin-react: ^7.30.1 |
2032 | eslint-plugin-react-hooks: ^4.6.0 | 2033 | eslint-plugin-react-hooks: ^4.6.0 |
2033 | html-minifier-terser: ^7.0.0 | 2034 | html-minifier-terser: ^7.0.0 |
2035 | lodash-es: ^4.17.21 | ||
2034 | loglevel: ^1.8.0 | 2036 | loglevel: ^1.8.0 |
2035 | loglevel-plugin-prefix: ^0.8.4 | 2037 | loglevel-plugin-prefix: ^0.8.4 |
2036 | mobx: ^6.6.1 | 2038 | mobx: ^6.6.1 |
@@ -2180,6 +2182,22 @@ __metadata: | |||
2180 | languageName: node | 2182 | languageName: node |
2181 | linkType: hard | 2183 | linkType: hard |
2182 | 2184 | ||
2185 | "@types/lodash-es@npm:^4.17.6": | ||
2186 | version: 4.17.6 | ||
2187 | resolution: "@types/lodash-es@npm:4.17.6" | ||
2188 | dependencies: | ||
2189 | "@types/lodash": "*" | ||
2190 | checksum: 9bd239dd525086e278821949ce12fbdd4f100a060fed9323fc7ad5661113e1641f28a7ebab617230ed3474680d8f4de705c1928b48252bb684be6ec9eed715db | ||
2191 | languageName: node | ||
2192 | linkType: hard | ||
2193 | |||
2194 | "@types/lodash@npm:*": | ||
2195 | version: 4.14.184 | ||
2196 | resolution: "@types/lodash@npm:4.14.184" | ||
2197 | checksum: 6d9a4d67f7f9d0ec3fd21174f3dd3d00629dc1227eb469450eace53adbc1f7e2330699c28d0fe093e5f0fef0f0e763098be1f779268857213224af082b62be21 | ||
2198 | languageName: node | ||
2199 | linkType: hard | ||
2200 | |||
2183 | "@types/node@npm:*, @types/node@npm:^18.7.8": | 2201 | "@types/node@npm:*, @types/node@npm:^18.7.8": |
2184 | version: 18.7.8 | 2202 | version: 18.7.8 |
2185 | resolution: "@types/node@npm:18.7.8" | 2203 | resolution: "@types/node@npm:18.7.8" |
@@ -4849,6 +4867,13 @@ __metadata: | |||
4849 | languageName: node | 4867 | languageName: node |
4850 | linkType: hard | 4868 | linkType: hard |
4851 | 4869 | ||
4870 | "lodash-es@npm:^4.17.21": | ||
4871 | version: 4.17.21 | ||
4872 | resolution: "lodash-es@npm:4.17.21" | ||
4873 | checksum: 05cbffad6e2adbb331a4e16fbd826e7faee403a1a04873b82b42c0f22090f280839f85b95393f487c1303c8a3d2a010048bf06151a6cbe03eee4d388fb0a12d2 | ||
4874 | languageName: node | ||
4875 | linkType: hard | ||
4876 | |||
4852 | "lodash.debounce@npm:^4.0.8": | 4877 | "lodash.debounce@npm:^4.0.8": |
4853 | version: 4.0.8 | 4878 | version: 4.0.8 |
4854 | resolution: "lodash.debounce@npm:4.0.8" | 4879 | resolution: "lodash.debounce@npm:4.0.8" |