diff options
| author | Haishan <[email protected]> | 2021-06-13 15:20:12 +0800 |
|---|---|---|
| committer | Haishan <[email protected]> | 2021-06-13 18:26:30 +0800 |
| commit | c78dbcf8f89072dc9c2fa8ba81e2cf5a80218cd7 (patch) | |
| tree | 1801b2954623c3b6710ba769836e5c25a68cf65a /src | |
| parent | aad1d2681e4415add1ab440159bf1253b5b34d8e (diff) | |
Support switch theme on backend config page
Diffstat (limited to 'src')
| -rw-r--r-- | src/components/APIConfig.module.scss | 7 | ||||
| -rw-r--r-- | src/components/APIConfig.tsx | 10 | ||||
| -rw-r--r-- | src/components/APIDiscovery.module.scss | 9 | ||||
| -rw-r--r-- | src/components/APIDiscovery.tsx | 19 | ||||
| -rw-r--r-- | src/components/BackendList.module.scss | 3 | ||||
| -rw-r--r-- | src/components/Field.module.scss | 3 | ||||
| -rw-r--r-- | src/components/Field.tsx | 10 | ||||
| -rw-r--r-- | src/components/Modal.module.scss | 4 | ||||
| -rw-r--r-- | src/components/Modal.tsx | 6 | ||||
| -rw-r--r-- | src/components/Root.scss | 20 | ||||
| -rw-r--r-- | src/components/SideBar.module.scss | 7 | ||||
| -rw-r--r-- | src/components/SideBar.tsx | 88 | ||||
| -rw-r--r-- | src/components/SvgYacd.tsx | 17 | ||||
| -rw-r--r-- | src/components/shared/ThemeSwitcher.module.css | 28 | ||||
| -rw-r--r-- | src/components/shared/ThemeSwitcher.tsx | 97 | ||||
| -rw-r--r-- | src/store/app.ts | 11 | ||||
| -rw-r--r-- | src/store/modals.ts | 10 | ||||
| -rw-r--r-- | src/store/types.ts | 7 |
18 files changed, 216 insertions, 140 deletions
diff --git a/src/components/APIConfig.module.scss b/src/components/APIConfig.module.scss index afc2d53..6581d46 100644 --- a/src/components/APIConfig.module.scss +++ b/src/components/APIConfig.module.scss @@ -10,11 +10,12 @@ align-items: center; .icon { - color: #2d2d30; - opacity: 0.4; + --stroke: #f3f3f3; + color: #20497e; + opacity: 0.7; transition: opacity 400ms; &:hover { - opacity: 0.7; + opacity: 1; } } } diff --git a/src/components/APIConfig.tsx b/src/components/APIConfig.tsx index 9b2e7e9..9bf255f 100644 --- a/src/components/APIConfig.tsx +++ b/src/components/APIConfig.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { fetchConfigs } from 'src/api/configs'; import { BackendList } from 'src/components/BackendList'; +import { addClashAPIConfig, getClashAPIConfig } from 'src/store/app'; +import { State } from 'src/store/types'; import { ClashAPIConfig } from 'src/types'; -import { addClashAPIConfig, getClashAPIConfig } from '../store/app'; import s0 from './APIConfig.module.scss'; import Button from './Button'; import Field from './Field'; @@ -13,7 +14,7 @@ import SvgYacd from './SvgYacd'; const { useState, useRef, useCallback } = React; const Ok = 0; -const mapState = (s) => ({ +const mapState = (s: State) => ({ apiConfig: getClashAPIConfig(s), }); @@ -73,23 +74,22 @@ function APIConfig({ dispatch }) { <div className={s0.root} ref={contentEl} onKeyDown={handleContentOnKeyDown}> <div className={s0.header}> <div className={s0.icon}> - <SvgYacd width={160} height={160} /> + <SvgYacd width={160} height={160} stroke="var(--stroke)" /> </div> </div> <div className={s0.body}> <div className={s0.hostnamePort}> <Field id="baseURL" - // @ts-expect-error ts-migrate(2322) FIXME: Type '{ id: string; name: string; label: string; t... Remove this comment to see the full error message name="baseURL" label="API Base URL" type="text" + placeholder="http://127.0.0.1:9090" value={baseURL} onChange={handleInputOnChange} /> <Field id="secret" - // @ts-expect-error ts-migrate(2322) FIXME: Type '{ id: string; name: string; label: string; v... Remove this comment to see the full error message name="secret" label="Secret(optional)" value={secret} diff --git a/src/components/APIDiscovery.module.scss b/src/components/APIDiscovery.module.scss index 6c1295a..e2161f8 100644 --- a/src/components/APIDiscovery.module.scss +++ b/src/components/APIDiscovery.module.scss @@ -22,5 +22,12 @@ } .overlay.overlay { - background: #222; + background-color: var(--color-background); +} + +.fixed { + position: fixed; + padding: 16px; + bottom: 0; + right: 0; } diff --git a/src/components/APIDiscovery.tsx b/src/components/APIDiscovery.tsx index 82ffbbf..f34c886 100644 --- a/src/components/APIDiscovery.tsx +++ b/src/components/APIDiscovery.tsx @@ -1,9 +1,11 @@ -import React from 'react'; +import * as React from 'react'; +import { ThemeSwitcher } from 'src/components/shared/ThemeSwitcher'; +import { DOES_NOT_SUPPORT_FETCH, errors } from 'src/misc/errors'; +import { getClashAPIConfig } from 'src/store/app'; +import { fetchConfigs } from 'src/store/configs'; +import { closeModal } from 'src/store/modals'; +import { State } from 'src/store/types'; -import { DOES_NOT_SUPPORT_FETCH, errors } from '../misc/errors'; -import { getClashAPIConfig } from '../store/app'; -import { fetchConfigs } from '../store/configs'; -import { closeModal } from '../store/modals'; import APIConfig from './APIConfig'; import s0 from './APIDiscovery.module.scss'; import Modal from './Modal'; @@ -32,7 +34,6 @@ function APIDiscovery({ dispatch, apiConfig, modals }) { isOpen={modals.apiConfig} className={s0.content} overlayClassName={s0.overlay} - // @ts-expect-error ts-migrate(2322) FIXME: Type '{ children: Element; isOpen: any; className:... Remove this comment to see the full error message shouldCloseOnOverlayClick={false} shouldCloseOnEsc={false} onRequestClose={closeApiConfigModal} @@ -40,11 +41,15 @@ function APIDiscovery({ dispatch, apiConfig, modals }) { <div className={s0.container}> <APIConfig /> </div> + + <div className={s0.fixed}> + <ThemeSwitcher /> + </div> </Modal> ); } -const mapState = (s) => ({ +const mapState = (s: State) => ({ modals: s.modals, apiConfig: getClashAPIConfig(s), }); diff --git a/src/components/BackendList.module.scss b/src/components/BackendList.module.scss index 1de1972..6872d3a 100644 --- a/src/components/BackendList.module.scss +++ b/src/components/BackendList.module.scss @@ -19,6 +19,7 @@ grid-template-rows: 30px; grid-template-areas: 'close url .'; column-gap: 10px; + border: 1px solid var(--bg-near-transparent); } .li:hover { @@ -29,6 +30,7 @@ opacity: 0; grid-area: close; place-self: center; + cursor: pointer; } .li:hover .close, @@ -83,6 +85,7 @@ } .btn:hover:enabled { background-color: var(--color-focus-blue); + color: white; } .btn:active:enabled { transform: scale(0.97); diff --git a/src/components/Field.module.scss b/src/components/Field.module.scss index 9a5f1e4..72a5149 100644 --- a/src/components/Field.module.scss +++ b/src/components/Field.module.scss @@ -9,13 +9,12 @@ border-radius: 0; border-bottom: 1px solid var(--color-input-border); box-sizing: border-box; - color: #c1c1c1; + color: inherit; display: inline-block; font-size: inherit; height: 40px; outline: none; padding: 0 4px; - transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1); width: 100%; &:focus { border-color: var(--color-focus-blue); diff --git a/src/components/Field.tsx b/src/components/Field.tsx index 4134d3e..a0d43cf 100644 --- a/src/components/Field.tsx +++ b/src/components/Field.tsx @@ -1,27 +1,25 @@ -import cx from 'clsx'; -import React from 'react'; +import * as React from 'react'; import s from './Field.module.scss'; const { useCallback } = React; type Props = { + name: string; value?: string | number; type?: 'text' | 'number'; onChange?: (...args: any[]) => any; id?: string; label?: string; + placeholder?: string; }; export default function Field({ id, label, value, onChange, ...props }: Props) { const valueOnChange = useCallback((e) => onChange(e), [onChange]); - const labelClassName = cx({ - [s.floatAbove]: typeof value === 'string' && value !== '', - }); return ( <div className={s.root}> <input id={id} value={value} onChange={valueOnChange} {...props} /> - <label htmlFor={id} className={labelClassName}> + <label htmlFor={id} className={s.floatAbove}> {label} </label> </div> diff --git a/src/components/Modal.module.scss b/src/components/Modal.module.scss index 6192a1f..8f9807c 100644 --- a/src/components/Modal.module.scss +++ b/src/components/Modal.module.scss @@ -11,11 +11,11 @@ .content { outline: none; position: relative; - color: #ddd; + color: var(--color-text); + background: #444; top: 50%; left: 50%; transform: translate(-50%, -50%); - background: #444; padding: 20px; border-radius: 10px; } diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx index fda3263..e91523c 100644 --- a/src/components/Modal.tsx +++ b/src/components/Modal.tsx @@ -1,10 +1,10 @@ import cx from 'clsx'; -import React from 'react'; -import Modal from 'react-modal'; +import * as React from 'react'; +import Modal, { Props as ReactModalProps } from 'react-modal'; import s0 from './Modal.module.scss'; -type Props = { +type Props = ReactModalProps & { isOpen: boolean; onRequestClose: (...args: any[]) => any; children: React.ReactNode; diff --git a/src/components/Root.scss b/src/components/Root.scss index 83d4171..55198ab 100644 --- a/src/components/Root.scss +++ b/src/components/Root.scss @@ -68,12 +68,12 @@ body { -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-text-size-adjust: 100%; -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; margin: 0; padding: 0; } -body, -body.dark { +@mixin dark { --color-background: #202020; --color-background2: rgba(32, 32, 32, 0.3); --color-bg-card: #2d2d2d; @@ -100,8 +100,7 @@ body.dark { --select-border-color: #040404; --select-bg-hover: url(data:image/svg+xml,%0A%20%20%20%20%3Csvg%20width%3D%228%22%20height%3D%2224%22%20viewBox%3D%220%200%208%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%207L7%2011H1L4%207Z%22%20fill%3D%22%23ffffff%22%20%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%2017L1%2013L7%2013L4%2017Z%22%20fill%3D%22%23ffffff%22%20%2F%3E%0A%20%20%20%20%3C%2Fsvg%3E%0A%20%20); } - -body.light { +@mixin light { --color-background: #eee; --color-background2: rgba(240, 240, 240, 0.3); --color-bg-card: #fafafa; @@ -129,6 +128,19 @@ body.light { --select-bg-hover: url(data:image/svg+xml,%0A%20%20%20%20%3Csvg%20width%3D%228%22%20height%3D%2224%22%20viewBox%3D%220%200%208%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%207L7%2011H1L4%207Z%22%20fill%3D%22%23222222%22%20%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%2017L1%2013L7%2013L4%2017Z%22%20fill%3D%22%23222222%22%20%2F%3E%0A%20%20%20%20%3C%2Fsvg%3E%0A%20%20); } +// we don't have a "system" or "auto" mode now +// it's just not make sense to have these yet +// @media (prefers-color-scheme: dark) {} +// @media (prefers-color-scheme: light) {} + +:root[data-theme='dark'] { + @include dark; +} + +:root[data-theme='light'] { + @include light; +} + .flexCenter { display: flex; align-items: center; diff --git a/src/components/SideBar.module.scss b/src/components/SideBar.module.scss index 744d29d..4a06377 100644 --- a/src/components/SideBar.module.scss +++ b/src/components/SideBar.module.scss @@ -103,10 +103,3 @@ .iconWrapper:focus { border-color: var(--color-focus-blue); } - -.themeSwitchContainer { - appearance: none; - user-select: none; - background: none; - cursor: pointer; -} diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index 8a6429a..dbe7f0d 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -12,13 +12,9 @@ import { FcSettings, } from 'react-icons/fc'; import { Link, useLocation } from 'react-router-dom'; +import { ThemeSwitcher } from 'src/components/shared/ThemeSwitcher'; -import { framerMotionResouce } from '../misc/motion'; -import { getTheme, switchTheme } from '../store/app'; import s from './SideBar.module.scss'; -import { connect } from './StateProvider'; - -const { useCallback } = React; const icons = { activity: FcAreaChart, @@ -85,12 +81,9 @@ const pages = [ }, ]; -function SideBar({ dispatch, theme }) { +export default function SideBar() { const { t } = useTranslation(); const location = useLocation(); - const switchThemeHooked = useCallback(() => { - dispatch(switchTheme()); - }, [dispatch]); return ( <div className={s.root}> <div className={s.logoPlaceholder} /> @@ -106,19 +99,7 @@ function SideBar({ dispatch, theme }) { ))} </div> <div className={s.footer}> - <Tooltip - label={t('theme')} - aria-label={ - 'switch to ' + (theme === 'light' ? 'dark' : 'light') + ' theme' - } - > - <button - className={cx(s.iconWrapper, s.themeSwitchContainer)} - onClick={switchThemeHooked} - > - {theme === 'light' ? <MoonA /> : <Sun />} - </button> - </Tooltip> + <ThemeSwitcher /> <Tooltip label={t('about')}> <Link to="/about" className={s.iconWrapper}> <Info size={20} /> @@ -128,66 +109,3 @@ function SideBar({ dispatch, theme }) { </div> ); } - -function MoonA() { - const module = framerMotionResouce.read(); - const motion = module.motion; - return ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="20" - height="20" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - > - <motion.path - d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" - initial={{ rotate: -30 }} - animate={{ rotate: 0 }} - transition={{ duration: 0.7 }} - /> - </svg> - ); -} - -function Sun() { - const module = framerMotionResouce.read(); - const motion = module.motion; - - return ( - <svg - xmlns="http://www.w3.org/2000/svg" - width="20" - height="20" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - strokeWidth="2" - strokeLinecap="round" - strokeLinejoin="round" - > - <circle cx="12" cy="12" r="5"></circle> - <motion.g - initial={{ scale: 0.8 }} - animate={{ scale: 1 }} - transition={{ duration: 0.7 }} - > - <line x1="12" y1="1" x2="12" y2="3"></line> - <line x1="12" y1="21" x2="12" y2="23"></line> - <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> - <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> - <line x1="1" y1="12" x2="3" y2="12"></line> - <line x1="21" y1="12" x2="23" y2="12"></line> - <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> - <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> - </motion.g> - </svg> - ); -} - -const mapState = (s) => ({ theme: getTheme(s) }); -export default connect(mapState)(SideBar); diff --git a/src/components/SvgYacd.tsx b/src/components/SvgYacd.tsx index 90ad7e2..63c0bd5 100644 --- a/src/components/SvgYacd.tsx +++ b/src/components/SvgYacd.tsx @@ -1,5 +1,5 @@ import cx from 'clsx'; -import React from 'react'; +import * as React from 'react'; import s from './SvgYacd.module.scss'; @@ -9,6 +9,9 @@ type Props = { animate?: boolean; c0?: string; c1?: string; + stroke?: string; + eye?: string; + mouth?: string; }; function SvgYacd({ @@ -16,7 +19,9 @@ function SvgYacd({ height = 320, animate = false, c0 = 'currentColor', - c1 = '#eee', + stroke = '#eee', + eye = '#eee', + mouth = '#eee', }: Props) { const faceClasName = cx({ [s.path]: animate }); return ( @@ -30,16 +35,16 @@ function SvgYacd({ {/* face */} <path d="M71.689 53.055c9.23-1.487 25.684 27.263 41.411 56.663 18.572-8.017 71.708-7.717 93.775 0 4.714-15.612 31.96-57.405 41.626-56.663 3.992.088 13.07 31.705 23.309 94.96 2.743 16.949 7.537 47.492 14.38 91.63-42.339 17.834-84.37 26.751-126.095 26.751-41.724 0-83.756-8.917-126.095-26.751C52.973 116.244 65.536 54.047 71.689 53.055z" - stroke={c1} + stroke={stroke} strokeWidth="4" strokeLinecap="round" fill={c0} className={faceClasName} /> - <circle fill={c1} cx="216.5" cy="181.5" r="14.5" /> - <circle fill={c1} cx="104.5" cy="181.5" r="14.5" /> + <circle fill={eye} cx="216.5" cy="181.5" r="14.5" /> + <circle fill={eye} cx="104.5" cy="181.5" r="14.5" /> {/* mouth */} - <g stroke={c1} strokeLinecap="round" strokeWidth="4"> + <g stroke={mouth} strokeLinecap="round" strokeWidth="4"> <path d="M175.568 218.694c-2.494 1.582-5.534 2.207-8.563 1.508-3.029-.7-5.487-2.594-7.035-5.11M143.981 218.694c2.494 1.582 5.534 2.207 8.563 1.508 3.03-.7 5.488-2.594 7.036-5.11" /> </g> </g> diff --git a/src/components/shared/ThemeSwitcher.module.css b/src/components/shared/ThemeSwitcher.module.css new file mode 100644 index 0000000..919c86c --- /dev/null +++ b/src/components/shared/ThemeSwitcher.module.css @@ -0,0 +1,28 @@ +.iconWrapper { + --sz: 40px; + + width: var(--sz); + height: var(--sz); + display: flex; + justify-content: center; + align-items: center; + + outline: none; + padding: 5px; + color: var(--color-text); + border-radius: 100%; + border: 1px solid transparent; +} +.iconWrapper:hover { + opacity: 0.6; +} +.iconWrapper:focus { + border-color: var(--color-focus-blue); +} + +.themeSwitchContainer { + appearance: none; + user-select: none; + background: none; + cursor: pointer; +} diff --git a/src/components/shared/ThemeSwitcher.tsx b/src/components/shared/ThemeSwitcher.tsx new file mode 100644 index 0000000..fba5b0b --- /dev/null +++ b/src/components/shared/ThemeSwitcher.tsx @@ -0,0 +1,97 @@ +import Tooltip from '@reach/tooltip'; +import cx from 'clsx'; +import * as React from 'react'; +import { useTranslation } from 'react-i18next'; +import { connect } from 'src/components/StateProvider'; +import { framerMotionResouce } from 'src/misc/motion'; +import { getTheme, switchTheme } from 'src/store/app'; +import { State } from 'src/store/types'; + +import s from './ThemeSwitcher.module.css'; + +export function ThemeSwitcherImpl({ theme, dispatch }) { + const { t } = useTranslation(); + + const switchThemeHooked = React.useCallback(() => { + dispatch(switchTheme()); + }, [dispatch]); + + return ( + <Tooltip + label={t('theme')} + aria-label={ + 'switch to ' + (theme === 'light' ? 'dark' : 'light') + ' theme' + } + > + <button + className={cx(s.iconWrapper, s.themeSwitchContainer)} + onClick={switchThemeHooked} + > + {theme === 'light' ? <MoonA /> : <Sun />} + </button> + </Tooltip> + ); +} + +function MoonA() { + const module = framerMotionResouce.read(); + const motion = module.motion; + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + > + <motion.path + d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" + initial={{ rotate: -30 }} + animate={{ rotate: 0 }} + transition={{ duration: 0.7 }} + /> + </svg> + ); +} + +function Sun() { + const module = framerMotionResouce.read(); + const motion = module.motion; + + return ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="20" + height="20" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + > + <circle cx="12" cy="12" r="5"></circle> + <motion.g + initial={{ scale: 0.8 }} + animate={{ scale: 1 }} + transition={{ duration: 0.7 }} + > + <line x1="12" y1="1" x2="12" y2="3"></line> + <line x1="12" y1="21" x2="12" y2="23"></line> + <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> + <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> + <line x1="1" y1="12" x2="3" y2="12"></line> + <line x1="21" y1="12" x2="23" y2="12"></line> + <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> + <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> + </motion.g> + </svg> + ); +} + +const mapState = (s: State) => ({ theme: getTheme(s) }); +export const ThemeSwitcher = connect(mapState)(ThemeSwitcherImpl); diff --git a/src/store/app.ts b/src/store/app.ts index ccdae1a..c6a455e 100644 --- a/src/store/app.ts +++ b/src/store/app.ts @@ -95,14 +95,15 @@ export function updateClashAPIConfig({ baseURL, secret }) { }; } -const bodyElement = document.body; +const rootEl = document.querySelector('html'); +const themeColorMeta = document.querySelector('meta[name="theme-color"]'); function setTheme(theme = 'dark') { if (theme === 'dark') { - bodyElement.classList.remove('light'); - bodyElement.classList.add('dark'); + rootEl.setAttribute('data-theme', 'dark'); + themeColorMeta.setAttribute('content', '#202020'); } else { - bodyElement.classList.remove('dark'); - bodyElement.classList.add('light'); + rootEl.setAttribute('data-theme', 'light'); + themeColorMeta.setAttribute('content', '#eeeeee'); } } diff --git a/src/store/modals.ts b/src/store/modals.ts index 3b8b488..0b27ce9 100644 --- a/src/store/modals.ts +++ b/src/store/modals.ts @@ -1,13 +1,15 @@ -export function openModal(modalName) { - return (dispatch) => { +import { DispatchFn } from './types'; + +export function openModal(modalName: string) { + return (dispatch: DispatchFn) => { dispatch(`openModal:${modalName}`, (s) => { s.modals[modalName] = true; }); }; } -export function closeModal(modalName) { - return (dispatch) => { +export function closeModal(modalName: string) { + return (dispatch: DispatchFn) => { dispatch(`closeModal:${modalName}`, (s) => { s.modals[modalName] = false; }); diff --git a/src/store/types.ts b/src/store/types.ts index c358ae3..7e6a39d 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -88,6 +88,12 @@ export type StateConfigs = { haveFetchedConfig: boolean; }; +///// store.modals + +export type StateModals = { + apiConfig: boolean; +}; + ////// export type State = { @@ -95,6 +101,7 @@ export type State = { configs: StateConfigs; proxies: StateProxies; logs: StateLogs; + modals: StateModals; }; export type GetStateFn = () => State; |
