diff options
author | 2022-08-21 00:33:01 +0200 | |
---|---|---|
committer | 2022-08-21 02:01:59 +0200 | |
commit | 8ebc24a5ba1c87cb5cb14fbaff3bee329e30fc15 (patch) | |
tree | 2ea323c637ef371d4bdfc9ccc69460147dd1597b /subprojects | |
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
Diffstat (limited to 'subprojects')
-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 |
6 files changed, 115 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 | { |