Minor log style tweak

This commit is contained in:
Haishan 2021-05-31 21:50:46 +08:00
parent d4015f6423
commit fcab7cad4f
10 changed files with 101 additions and 68 deletions

View file

@ -1,14 +1,9 @@
import { LogsAPIConfig } from 'src/types';
import { pad0 } from 'src/misc/utils';
import { Log, LogsAPIConfig } from 'src/types';
import { buildLogsWebSocketURL, getURLAndInit } from '../misc/request-helper';
type LogEntry = {
time?: string;
id?: string;
even?: boolean;
// and some other props
};
type AppendLogFn = (x: LogEntry) => void;
type AppendLogFn = (x: Log) => void;
const endpoint = '/logs';
const textDecoder = new TextDecoder('utf-8');
@ -22,7 +17,7 @@ let fetched = false;
let decoded = '';
function appendData(s: string, callback: AppendLogFn) {
let o: LogEntry;
let o: Partial<Log>;
try {
o = JSON.parse(s);
} catch (err) {
@ -31,7 +26,7 @@ function appendData(s: string, callback: AppendLogFn) {
}
const now = new Date();
const time = now.toLocaleString('zh-Hans');
const time = formatDate(now);
// mutate input param in place intentionally
o.time = time;
o.id = +now - 0 + getRandomStr();
@ -39,6 +34,17 @@ function appendData(s: string, callback: AppendLogFn) {
callback(o);
}
function formatDate(d: Date) {
// 19-03-09 12:45
const YY = d.getFullYear() % 100;
const MM = pad0(d.getMonth() + 1, 2);
const dd = pad0(d.getDate(), 2);
const HH = pad0(d.getHours(), 2);
const mm = pad0(d.getMinutes(), 2);
const ss = pad0(d.getSeconds(), 2);
return `${YY}-${MM}-${dd} ${HH}:${mm}:${ss}`;
}
function pump(reader: ReadableStreamDefaultReader, appendLog: AppendLogFn) {
return reader.read().then(({ done, value }) => {
const str = textDecoder.decode(value, { stream: !done });

View file

@ -3,7 +3,6 @@
align-items: center;
flex-wrap: wrap;
font-size: 0.9em;
padding: 10px;
}
.logType {
@ -11,8 +10,7 @@
flex-shrink: 0;
text-align: center;
width: 66px;
background: green;
border-radius: 5px;
border-radius: 100px;
padding: 3px 5px;
margin: 0 8px;
}

View file

@ -1,18 +1,23 @@
import cx from 'clsx';
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { areEqual, FixedSizeList as List } from 'react-window';
import {
areEqual,
FixedSizeList as List,
ListChildComponentProps,
} from 'react-window';
import { fetchLogs } from 'src/api/logs';
import ContentHeader from 'src/components/ContentHeader';
import LogSearch from 'src/components/LogSearch';
import { connect } from 'src/components/StateProvider';
import SvgYacd from 'src/components/SvgYacd';
import useRemainingViewPortHeight from 'src/hooks/useRemainingViewPortHeight';
import { getClashAPIConfig } 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 { fetchLogs } from '../api/logs';
import useRemainingViewPortHeight from '../hooks/useRemainingViewPortHeight';
import { getClashAPIConfig } from '../store/app';
import { getLogLevel } from '../store/configs';
import { appendLog, getLogsForDisplay } from '../store/logs';
import ContentHeader from './ContentHeader';
import s0 from './Logs.module.css';
import LogSearch from './LogSearch';
import { connect } from './StateProvider';
import SvgYacd from './SvgYacd';
import s from './Logs.module.css';
const { useCallback, memo, useEffect } = React;
@ -20,48 +25,44 @@ const paddingBottom = 30;
const colors = {
debug: 'none',
// debug: '#8a8a8a',
info: '#454545',
// info: '#147d14',
info: 'var(--bg-log-info-tag)',
warning: '#b99105',
error: '#c11c1c',
};
type LogLineProps = {
time?: string;
even?: boolean;
payload?: string;
type?: string;
};
type LogLineProps = Partial<Log>;
function LogLine({ time, even, payload, type }: LogLineProps) {
const className = cx({ even }, s0.log);
const className = cx({ even }, s.log);
return (
<div className={className}>
<div className={s0.logMeta}>
<div className={s0.logTime}>{time}</div>
<div className={s0.logType} style={{ backgroundColor: colors[type] }}>
<div className={s.logMeta}>
<div className={s.logTime}>{time}</div>
<div className={s.logType} style={{ backgroundColor: colors[type] }}>
{type}
</div>
<div className={s0.logText}>{payload}</div>
<div className={s.logText}>{payload}</div>
</div>
</div>
);
}
function itemKey(index, data) {
function itemKey(index: number, data: LogLineProps[]) {
const item = data[index];
return item.id;
}
// @ts-expect-error ts-migrate(2339) FIXME: Property 'index' does not exist on type '{ childre... Remove this comment to see the full error message
const Row = memo(({ index, style, data }) => {
const r = data[index];
return (
<div style={style}>
<LogLine {...r} />
</div>
);
}, areEqual);
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(
@ -80,23 +81,20 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
<div>
<ContentHeader title={t('Logs')} />
<LogSearch />
{/* @ts-expect-error ts-migrate(2322) FIXME: Type 'number | MutableRefObject<any>' is not assig... Remove this comment to see the full error message */}
<div ref={refLogsContainer} style={{ paddingBottom }}>
{logs.length === 0 ? (
<div
className={s0.logPlaceholder}
// @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
className={s.logPlaceholder}
style={{ height: containerHeight - paddingBottom }}
>
<div className={s0.logPlaceholderIcon}>
<div className={s.logPlaceholderIcon}>
<SvgYacd width={200} height={200} />
</div>
<div>{t('no_logs')}</div>
</div>
) : (
<div className={s0.logsWrapper}>
<div className={s.logsWrapper}>
<List
// @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}
width="100%"
itemCount={logs.length}
@ -113,7 +111,7 @@ function Logs({ dispatch, logLevel, apiConfig, logs }) {
);
}
const mapState = (s) => ({
const mapState = (s: State) => ({
logs: getLogsForDisplay(s),
logLevel: getLogLevel(s),
apiConfig: getClashAPIConfig(s),

View file

@ -124,6 +124,7 @@ body.dark {
--color-btn-fg: #bebebe;
--color-bg-proxy: #303030;
--color-row-odd: #282828;
--bg-log-info-tag: #454545;
--bg-modal: #1f1f20;
--bg-near-transparent: rgba(255, 255, 255, 0.1);
--bg-tooltip: #111;
@ -151,6 +152,7 @@ body.light {
--color-btn-fg: #101010;
--color-bg-proxy: #fafafa;
--color-row-odd: #f5f5f5;
--bg-log-info-tag: #888;
--bg-modal: #fbfbfb;
--bg-near-transparent: rgba(0, 0, 0, 0.1);
--bg-tooltip: #f0f0f0;

View file

@ -1,5 +1,6 @@
.ToggleSwitch {
user-select: none;
border-radius: 4px;
border: 1px solid #525252;
color: var(--color-text);
background: var(--color-toggle-bg);

View file

@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
const { useState, useRef, useCallback, useLayoutEffect } = React;
@ -9,8 +9,10 @@ const { useState, useRef, useCallback, useLayoutEffect } = React;
* to the bottom of the view port
*
*/
export default function useRemainingViewPortHeight() {
const ref = useRef(null);
export default function useRemainingViewPortHeight<
ElementType extends HTMLDivElement
>(): [React.MutableRefObject<ElementType>, number] {
const ref = useRef<ElementType>(null);
const [containerHeight, setContainerHeight] = useState(200);
const updateContainerHeight = useCallback(() => {
const { top } = ref.current.getBoundingClientRect();

View file

@ -12,7 +12,7 @@ function genCommonHeaders({ secret }: { secret?: string }) {
return h;
}
function buildWebSocketURLBase(baseURL: string, params: URLSearchParams, endpoint: string) {
let qs = '?' + params.toString()
const qs = '?' + params.toString()
const url = new URL(baseURL);
url.protocol === 'https:' ? (url.protocol = 'wss:') : (url.protocol = 'ws:');
return `${trimTrailingSlash(url.href)}${endpoint}${qs}`;

View file

@ -31,3 +31,11 @@ export function debounce<T extends any[]>(
export function trimTrailingSlash(s: string) {
return s.replace(/\/$/, '');
}
export function pad0(number: number | string, len: number): string {
let output = String(number);
while (output.length < len) {
output = '0' + output;
}
return output;
}

View file

@ -1,10 +1,12 @@
import { createSelector } from 'reselect';
import { DispatchFn, GetStateFn, Log, State } from './types';
const LogSize = 300;
const getLogs = (s) => s.logs.logs;
const getTail = (s) => s.logs.tail;
export const getSearchText = (s) => s.logs.searchText;
const getLogs = (s: State) => s.logs.logs;
const getTail = (s: State) => s.logs.tail;
export const getSearchText = (s: State) => s.logs.searchText;
export const getLogsForDisplay = createSelector(
getLogs,
getTail,
@ -25,16 +27,16 @@ export const getLogsForDisplay = createSelector(
}
);
export function updateSearchText(text) {
return (dispatch) => {
export function updateSearchText(text: string) {
return (dispatch: DispatchFn) => {
dispatch('logsUpdateSearchText', (s) => {
s.logs.searchText = text.toLowerCase();
});
};
}
export function appendLog(log) {
return (dispatch, getState) => {
export function appendLog(log: Log) {
return (dispatch: DispatchFn, getState: GetStateFn) => {
const s = getState();
const logs = getLogs(s);
const tailCurr = getTail(s);
@ -42,9 +44,7 @@ export function appendLog(log) {
// mutate intentionally for performance
logs[tail] = log;
dispatch('logsAppendLog', (s) => {
s.logs.tail = tail;
});
dispatch('logsAppendLog', (s: State) => (s.logs.tail = tail));
};
}

View file

@ -65,6 +65,22 @@ export type StateProxies = {
switchProxyCtx?: SwitchProxyCtx;
};
///// store.logs
export type Log = {
time: string;
even: boolean;
payload: string;
type: string;
id: string;
};
export type StateLogs = {
searchText: string;
logs: Log[];
tail: number;
};
///// store.configs
export type StateConfigs = {
@ -78,7 +94,9 @@ export type State = {
app: StateApp;
configs: StateConfigs;
proxies: StateProxies;
logs: StateLogs;
};
export type GetStateFn = () => State;
export interface DispatchFn {
(msg: string, change: (s: State) => void): void;