summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
authoryaling888 <[email protected]>2022-05-06 03:55:39 +0800
committeryaling888 <[email protected]>2022-05-06 03:55:39 +0800
commitdafd4486f17fcd72ac86578854886a807b0c4748 (patch)
treed15d3345690f84000afda53c5c2f2ebe84cfdb8b /src/components
parent96c16b0ae5562cbe16b311da0ed9f839da172c4e (diff)
parent4dea888769ef153806bc5275616fd3c9d3e0a32b (diff)
Merge 'tracking' into master
Diffstat (limited to 'src/components')
-rw-r--r--src/components/Connections.tsx39
-rw-r--r--src/components/Logs.module.scss9
-rw-r--r--src/components/Logs.tsx63
-rw-r--r--src/components/Root.scss26
-rw-r--r--src/components/SideBar.module.scss2
-rw-r--r--src/components/shared/Basic.module.scss46
-rw-r--r--src/components/shared/ThemeSwitcher.module.css28
-rw-r--r--src/components/shared/ThemeSwitcher.module.scss58
-rw-r--r--src/components/shared/ThemeSwitcher.tsx84
-rw-r--r--src/components/shared/rtf.css4
10 files changed, 205 insertions, 154 deletions
diff --git a/src/components/Connections.tsx b/src/components/Connections.tsx
index ff38004..f25e962 100644
--- a/src/components/Connections.tsx
+++ b/src/components/Connections.tsx
@@ -99,7 +99,7 @@ function formatConnectionDataItem(
download,
start: now - new Date(start).valueOf(),
chains: chains.reverse().join(' / '),
- rule: rule === 'GeoSite' || rule === 'GeoIP' || rule === 'RuleSet' ? `${rule} (${rulePayload})` : rule,
+ rule: (rulePayload == null || rulePayload === '') ? rule : (`${rule} (${rulePayload})`),
...metadata,
host: `${host2}:${destinationPort}`,
type: `${type}(${network})`,
@@ -134,10 +134,7 @@ function Conn({ apiConfig }) {
const filteredClosedConns = filterConns(closedConns, filterKeyword);
const [isCloseAllModalOpen, setIsCloseAllModalOpen] = useState(false);
const openCloseAllModal = useCallback(() => setIsCloseAllModalOpen(true), []);
- const closeCloseAllModal = useCallback(
- () => setIsCloseAllModalOpen(false),
- []
- );
+ const closeCloseAllModal = useCallback(() => setIsCloseAllModalOpen(false), []);
const [isRefreshPaused, setIsRefreshPaused] = useState(false);
const toggleIsRefreshPaused = useCallback(() => {
setIsRefreshPaused((x) => !x);
@@ -165,11 +162,7 @@ function Conn({ apiConfig }) {
});
// if previous connections and current connections are both empty
// arrays, we wont update state to avaoid rerender
- if (
- x &&
- (x.length !== 0 || prevConnsRef.current.length !== 0) &&
- !isRefreshPaused
- ) {
+ if (x && (x.length !== 0 || prevConnsRef.current.length !== 0) && !isRefreshPaused) {
prevConnsRef.current = x;
setConns(x);
} else {
@@ -222,14 +215,9 @@ function Conn({ apiConfig }) {
/>
</div>
</div>
- <div
- // @ts-expect-error ts-migrate(2322) FIXME: Type 'number | MutableRefObject<any>' is not assig... Remove this comment to see the full error message
- ref={refContainer}
- style={{ padding: 30, paddingBottom, paddingTop: 0 }}
- >
+ <div ref={refContainer} style={{ padding: 30, paddingBottom, paddingTop: 0 }}>
<div
style={{
- // @ts-expect-error ts-migrate(2362) FIXME: The left-hand side of an arithmetic operation must... Remove this comment to see the full error message
height: containerHeight - paddingBottom,
overflow: 'auto',
}}
@@ -237,24 +225,13 @@ function Conn({ apiConfig }) {
<TabPanel>
<>{renderTableOrPlaceholder(filteredConns)}</>
<Fab
- icon={
- isRefreshPaused ? <Play size={16} /> : <Pause size={16} />
- }
- mainButtonStyles={
- isRefreshPaused
- ? {
- background: '#e74c3c',
- }
- : {}
- }
+ icon={isRefreshPaused ? <Play size={16} /> : <Pause size={16} />}
+ mainButtonStyles={isRefreshPaused ? { background: '#e74c3c' } : {}}
style={fabPosition}
- text={isRefreshPaused ? 'Resume Refresh' : 'Pause Refresh'}
+ text={isRefreshPaused ? t('Resume Refresh') : t('Pause Refresh')}
onClick={toggleIsRefreshPaused}
>
- <Action
- text="Close All Connections"
- onClick={openCloseAllModal}
- >
+ <Action text="Close All Connections" onClick={openCloseAllModal}>
<IconClose size={10} />
</Action>
</Fab>
diff --git a/src/components/Logs.module.scss b/src/components/Logs.module.scss
index 508e9c6..16ecb7f 100644
--- a/src/components/Logs.module.scss
+++ b/src/components/Logs.module.scss
@@ -41,19 +41,16 @@
color: var(--color-text);
:global {
- li {
+ .log {
+ padding: 10px 40px;
background: var(--color-background);
}
- li.even {
+ .log.even {
background: var(--color-background);
}
}
}
-.log {
- padding: 10px 40px;
-}
-
/*******************/
.logPlaceholder {
diff --git a/src/components/Logs.tsx b/src/components/Logs.tsx
index 019edd5..d9c53d7 100644
--- a/src/components/Logs.tsx
+++ b/src/components/Logs.tsx
@@ -1,23 +1,21 @@
import cx from 'clsx';
import * as React from 'react';
+import { Pause, Play } from 'react-feather';
import { useTranslation } from 'react-i18next';
-import {
- areEqual,
- FixedSizeList as List,
- ListChildComponentProps,
-} from 'react-window';
-import { fetchLogs } from 'src/api/logs';
+import { areEqual, FixedSizeList as List, ListChildComponentProps } from 'react-window';
+import { fetchLogs, reconnect as reconnectLogs,stop as stopLogs } from 'src/api/logs';
import ContentHeader from 'src/components/ContentHeader';
import LogSearch from 'src/components/LogSearch';
-import { connect } from 'src/components/StateProvider';
+import { connect, useStoreActions } from 'src/components/StateProvider';
import SvgYacd from 'src/components/SvgYacd';
import useRemainingViewPortHeight from 'src/hooks/useRemainingViewPortHeight';
-import { getClashAPIConfig } from 'src/store/app';
+import { getClashAPIConfig, getLogStreamingPaused } from 'src/store/app';
import { getLogLevel } from 'src/store/configs';
import { appendLog, getLogsForDisplay } from 'src/store/logs';
import { Log, State } from 'src/store/types';
import s from './Logs.module.scss';
+import { Fab, position as fabPosition } from './shared/Fab';
const { useCallback, memo, useEffect } = React;
@@ -32,7 +30,7 @@ const colors = {
type LogLineProps = Partial<Log>;
function LogLine({ time, even, payload, type }: LogLineProps) {
- const className = cx({ even }, s.log);
+ const className = cx({ even }, 'log');
return (
<div className={className}>
<div className={s.logMeta}>
@@ -51,23 +49,24 @@ function itemKey(index: number, data: LogLineProps[]) {
return item.id;
}
-const Row = memo(
- ({ index, style, data }: ListChildComponentProps<LogLineProps>) => {
- const r = data[index];
- return (
- <div style={style}>
- <LogLine {...r} />
- </div>
- );
- },
- areEqual
-);
-
-function Logs({ dispatch, logLevel, apiConfig, logs }) {
- const appendLogInternal = useCallback(
- (log) => dispatch(appendLog(log)),
- [dispatch]
+const Row = memo(({ index, style, data }: ListChildComponentProps<LogLineProps>) => {
+ const r = data[index];
+ return (
+ <div style={style}>
+ <LogLine {...r} />
+ </div>
);
+}, areEqual);
+
+function Logs({ dispatch, logLevel, apiConfig, logs, logStreamingPaused }) {
+ const actions = useStoreActions();
+ const toggleIsRefreshPaused = useCallback(() => {
+ logStreamingPaused ? reconnectLogs({ ...apiConfig, logLevel }) : stopLogs();
+ // being lazy here
+ // ideally we should check the result of previous operation before updating this
+ actions.app.updateAppConfig('logStreamingPaused', !logStreamingPaused);
+ }, [apiConfig, logLevel, logStreamingPaused, actions.app]);
+ const appendLogInternal = useCallback((log) => dispatch(appendLog(log)), [dispatch]);
useEffect(() => {
fetchLogs({ ...apiConfig, logLevel }, appendLogInternal);
}, [apiConfig, logLevel, appendLogInternal]);
@@ -80,10 +79,7 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
<LogSearch />
<div ref={refLogsContainer} style={{ paddingBottom }}>
{logs.length === 0 ? (
- <div
- className={s.logPlaceholder}
- style={{ height: containerHeight - paddingBottom }}
- >
+ <div className={s.logPlaceholder} style={{ height: containerHeight - paddingBottom }}>
<div className={s.logPlaceholderIcon}>
<SvgYacd width={200} height={200} />
</div>
@@ -101,6 +97,14 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
>
{Row}
</List>
+
+ <Fab
+ icon={logStreamingPaused ? <Play size={16} /> : <Pause size={16} />}
+ mainButtonStyles={logStreamingPaused ? { background: '#e74c3c' } : {}}
+ style={fabPosition}
+ text={logStreamingPaused ? t('Resume Refresh') : t('Pause Refresh')}
+ onClick={toggleIsRefreshPaused}
+ ></Fab>
</div>
)}
</div>
@@ -112,6 +116,7 @@ const mapState = (s: State) => ({
logs: getLogsForDisplay(s),
logLevel: getLogLevel(s),
apiConfig: getClashAPIConfig(s),
+ logStreamingPaused: getLogStreamingPaused(s),
});
export default connect(mapState)(Logs);
diff --git a/src/components/Root.scss b/src/components/Root.scss
index 55198ab..4ae7d5f 100644
--- a/src/components/Root.scss
+++ b/src/components/Root.scss
@@ -53,18 +53,14 @@
:root {
--font-mono: 'Roboto Mono', Menlo, monospace;
- --font-normal: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica,
- Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol,
- 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
+ // prettier-ignore
+ --font-normal: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
--color-focus-blue: #1a73e8;
--btn-bg: #387cec;
}
body {
- font-family: 'Open Sans', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
- Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji,
- Segoe UI Symbol, 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial,
- sans-serif;
+ font-family: var(--font-normal);
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-text-size-adjust: 100%;
-webkit-font-smoothing: antialiased;
@@ -128,17 +124,25 @@ body {
--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='auto'] {
+ @media (prefers-color-scheme: dark) {
+ @include dark;
+ color-scheme: dark;
+ }
+ @media (prefers-color-scheme: light) {
+ @include light;
+ color-scheme: light;
+ }
+}
:root[data-theme='dark'] {
@include dark;
+ color-scheme: dark;
}
:root[data-theme='light'] {
@include light;
+ color-scheme: light;
}
.flexCenter {
diff --git a/src/components/SideBar.module.scss b/src/components/SideBar.module.scss
index 4a06377..4bd2e60 100644
--- a/src/components/SideBar.module.scss
+++ b/src/components/SideBar.module.scss
@@ -15,7 +15,7 @@
@media (max-width: 768px) {
display: flex;
justify-content: space-between;
- overflow: scroll;
+ overflow: auto;
}
}
diff --git a/src/components/shared/Basic.module.scss b/src/components/shared/Basic.module.scss
index 8e5a113..b8e0068 100644
--- a/src/components/shared/Basic.module.scss
+++ b/src/components/shared/Basic.module.scss
@@ -36,32 +36,30 @@ h2.sectionNameType {
*
*/
-:global {
- body.light {
- /*
+:root[data-theme='light'] {
+ /*
* --loading-dot-{dot-index}-{dot-keyframe-phase}
*/
- --loading-dot-1-1: rgba(0, 0, 0, 0.1);
- --loading-dot-1-2: rgba(0, 0, 0, 0.5);
- --loading-dot-1-3: rgba(0, 0, 0, 0.3);
- --loading-dot-2-1: rgba(0, 0, 0, 0.3);
- --loading-dot-2-2: rgba(0, 0, 0, 0.1);
- --loading-dot-2-3: rgba(0, 0, 0, 0.5);
- --loading-dot-3-1: rgba(0, 0, 0, 0.5);
- --loading-dot-3-2: rgba(0, 0, 0, 0.3);
- --loading-dot-3-3: rgba(0, 0, 0, 0.1);
- }
- body.dark {
- --loading-dot-1-1: rgba(255, 255, 255, 0.5);
- --loading-dot-1-2: rgba(255, 255, 255, 0.1);
- --loading-dot-1-3: rgba(255, 255, 255, 0.3);
- --loading-dot-2-1: rgba(255, 255, 255, 0.3);
- --loading-dot-2-2: rgba(255, 255, 255, 0.5);
- --loading-dot-2-3: rgba(255, 255, 255, 0.1);
- --loading-dot-3-1: rgba(255, 255, 255, 0.1);
- --loading-dot-3-2: rgba(255, 255, 255, 0.3);
- --loading-dot-3-3: rgba(255, 255, 255, 0.5);
- }
+ --loading-dot-1-1: rgba(0, 0, 0, 0.1);
+ --loading-dot-1-2: rgba(0, 0, 0, 0.5);
+ --loading-dot-1-3: rgba(0, 0, 0, 0.3);
+ --loading-dot-2-1: rgba(0, 0, 0, 0.3);
+ --loading-dot-2-2: rgba(0, 0, 0, 0.1);
+ --loading-dot-2-3: rgba(0, 0, 0, 0.5);
+ --loading-dot-3-1: rgba(0, 0, 0, 0.5);
+ --loading-dot-3-2: rgba(0, 0, 0, 0.3);
+ --loading-dot-3-3: rgba(0, 0, 0, 0.1);
+}
+:root[data-theme='dark'] {
+ --loading-dot-1-1: rgba(255, 255, 255, 0.5);
+ --loading-dot-1-2: rgba(255, 255, 255, 0.1);
+ --loading-dot-1-3: rgba(255, 255, 255, 0.3);
+ --loading-dot-2-1: rgba(255, 255, 255, 0.3);
+ --loading-dot-2-2: rgba(255, 255, 255, 0.5);
+ --loading-dot-2-3: rgba(255, 255, 255, 0.1);
+ --loading-dot-3-1: rgba(255, 255, 255, 0.1);
+ --loading-dot-3-2: rgba(255, 255, 255, 0.3);
+ --loading-dot-3-3: rgba(255, 255, 255, 0.5);
}
.loadingDot,
diff --git a/src/components/shared/ThemeSwitcher.module.css b/src/components/shared/ThemeSwitcher.module.css
deleted file mode 100644
index 919c86c..0000000
--- a/src/components/shared/ThemeSwitcher.module.css
+++ /dev/null
@@ -1,28 +0,0 @@
-.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.module.scss b/src/components/shared/ThemeSwitcher.module.scss
new file mode 100644
index 0000000..c5de126
--- /dev/null
+++ b/src/components/shared/ThemeSwitcher.module.scss
@@ -0,0 +1,58 @@
+.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 {
+ --sz: 40px;
+
+ position: relative;
+ display: flex;
+ align-items: center;
+ height: var(--sz);
+ select {
+ cursor: pointer;
+ padding-left: var(--sz);
+ width: var(--sz);
+ height: var(--sz);
+ appearance: none;
+ outline: none;
+ border-radius: 100%;
+ border: 1px solid transparent;
+ background: var(--color-bg-sidebar);
+ &:focus {
+ border-color: var(--color-focus-blue);
+ }
+ option {
+ // this has effect in Firefox
+ // Chrome and Safari use the native menu
+ background: var(--color-bg-sidebar);
+ }
+ }
+ .iconWrapper {
+ pointer-events: none;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
+}
diff --git a/src/components/shared/ThemeSwitcher.tsx b/src/components/shared/ThemeSwitcher.tsx
index fba5b0b..45b60bc 100644
--- a/src/components/shared/ThemeSwitcher.tsx
+++ b/src/components/shared/ThemeSwitcher.tsx
@@ -1,5 +1,4 @@
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';
@@ -7,28 +6,40 @@ 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';
+import s from './ThemeSwitcher.module.scss';
export function ThemeSwitcherImpl({ theme, dispatch }) {
const { t } = useTranslation();
- const switchThemeHooked = React.useCallback(() => {
- dispatch(switchTheme());
- }, [dispatch]);
+ const themeIcon = React.useMemo(() => {
+ switch (theme) {
+ case 'dark':
+ return <MoonA />;
+ case 'auto':
+ return <Auto />;
+ case 'light':
+ return <Sun />;
+ default:
+ console.assert(false, 'Unknown theme');
+ return <MoonA />;
+ }
+ }, [theme]);
+
+ const onChange = React.useCallback(
+ (e: React.ChangeEvent<HTMLSelectElement>) => dispatch(switchTheme(e.target.value)),
+ [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 label={t('switch_theme')} aria-label={'switch theme'}>
+ <div className={s.themeSwitchContainer}>
+ <span className={s.iconWrapper}>{themeIcon}</span>
+ <select onChange={onChange}>
+ <option value="auto">Auto</option>
+ <option value="dark">Dark</option>
+ <option value="light">Light</option>
+ </select>
+ </div>
</Tooltip>
);
}
@@ -75,11 +86,7 @@ function Sun() {
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="5"></circle>
- <motion.g
- initial={{ scale: 0.8 }}
- animate={{ scale: 1 }}
- transition={{ duration: 0.7 }}
- >
+ <motion.g initial={{ scale: 0.7 }} animate={{ scale: 1 }} transition={{ duration: 0.5 }}>
<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>
@@ -93,5 +100,38 @@ function Sun() {
);
}
+function Auto() {
+ 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="11" />
+ <clipPath id="cut-off-bottom">
+ <motion.rect
+ x="12"
+ y="0"
+ width="12"
+ height="24"
+ initial={{ rotate: -30 }}
+ animate={{ rotate: 0 }}
+ transition={{ duration: 0.7 }}
+ />
+ </clipPath>
+ <circle cx="12" cy="12" r="6" clipPath="url(#cut-off-bottom)" fill="currentColor" />
+ </svg>
+ );
+}
+
const mapState = (s: State) => ({ theme: getTheme(s) });
export const ThemeSwitcher = connect(mapState)(ThemeSwitcherImpl);
diff --git a/src/components/shared/rtf.css b/src/components/shared/rtf.css
index da439ee..574aad1 100644
--- a/src/components/shared/rtf.css
+++ b/src/components/shared/rtf.css
@@ -12,8 +12,8 @@
list-style: none;
}
.rtf.open .rtf--mb {
- box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2),
- 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
+ box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14),
+ 0px 3px 14px 2px rgba(0, 0, 0, 0.12);
}
.rtf.open .rtf--mb > ul {