From 043bcb3ae407c9ac7f9061191ee9ce430c0cf546 Mon Sep 17 00:00:00 2001 From: imspace Date: Thu, 9 Mar 2023 05:36:14 +0800 Subject: [PATCH] feat: add log display --- gui/src/components/Checkbox.tsx | 5 ++- gui/src/components/RunPanel.tsx | 46 +++++++++++++++++------ gui/src/main.css | 3 +- gui/src/main.tsx | 11 ++++-- gui/src/pages/Home.tsx | 11 +++--- gui/src/services/config.ts | 2 +- gui/src/services/{s3si.ts => s3si.tsx} | 52 +++++++++++++++++++++++++- 7 files changed, 104 insertions(+), 26 deletions(-) rename gui/src/services/{s3si.ts => s3si.tsx} (58%) diff --git a/gui/src/components/Checkbox.tsx b/gui/src/components/Checkbox.tsx index ee99e70..adcfc42 100644 --- a/gui/src/components/Checkbox.tsx +++ b/gui/src/components/Checkbox.tsx @@ -1,16 +1,17 @@ import React from 'react' type CheckboxProps = { + disabled?: boolean children?: React.ReactNode value?: boolean onChange?: (value: boolean) => void } -export const Checkbox: React.FC = ({ value, onChange, children }) => { +export const Checkbox: React.FC = ({ disabled, value, onChange, children }) => { return
} diff --git a/gui/src/components/RunPanel.tsx b/gui/src/components/RunPanel.tsx index ac991b8..1fa1fc1 100644 --- a/gui/src/components/RunPanel.tsx +++ b/gui/src/components/RunPanel.tsx @@ -1,9 +1,9 @@ import classNames from 'classnames'; import { usePromise } from 'hooks/usePromise'; -import React, { useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next'; import { canExport, getProfile, setProfile } from 'services/config'; -import { run } from 'services/s3si'; +import { run, useLog } from 'services/s3si'; import { Checkbox } from './Checkbox'; import { Loading } from './Loading'; @@ -39,16 +39,40 @@ export const RunPanel: React.FC = () => { setLoading(false); } } + const disabled = !canExport(result); return <> - {t('导出对战数据')} - {t('导出打工数据')} - +
+ {t('导出对战数据')} + {t('导出打工数据')} + +
} + +export type LogPanelProps = { + className?: string +} + +export const LogPanel: React.FC = ({ className }) => { + const { renderedLogs } = useLog(); + const div = useRef(null); + const { t } = useTranslation(); + + useEffect(() => { + if (div.current) { + div.current.scrollTop = div.current.scrollHeight; + } + }, [renderedLogs]) + + return
+ {renderedLogs.length === 0 &&
{t('欢迎! 请点击"导出"按钮开始使用.')}
} + {renderedLogs.map((line, i) =>
{line}
)} +
+} diff --git a/gui/src/main.css b/gui/src/main.css index 50fd673..cd96134 100644 --- a/gui/src/main.css +++ b/gui/src/main.css @@ -26,6 +26,7 @@ body { #root { height: 100vh; + overflow: hidden } @media (prefers-color-scheme: dark) { @@ -42,5 +43,5 @@ body { } .full-card { - @apply card m-2 h-full; + @apply card p-2 h-full; } diff --git a/gui/src/main.tsx b/gui/src/main.tsx index c67c01e..6fb17db 100644 --- a/gui/src/main.tsx +++ b/gui/src/main.tsx @@ -1,12 +1,15 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; +import { LogProvider } from "services/s3si"; import App from "./App"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - - - + + + + + + ); diff --git a/gui/src/pages/Home.tsx b/gui/src/pages/Home.tsx index f27dc69..911306d 100644 --- a/gui/src/pages/Home.tsx +++ b/gui/src/pages/Home.tsx @@ -1,6 +1,6 @@ import { ErrorContent } from 'components/ErrorContent'; import { Loading } from 'components/Loading'; -import { RunPanel } from 'components/RunPanel'; +import { LogPanel, RunPanel } from 'components/RunPanel'; import { STAT_INK } from 'constant'; import { usePromise } from 'hooks/usePromise'; import React from 'react' @@ -28,14 +28,15 @@ export const Home: React.FC = () => { } - return <> -
-

{t('欢迎!')}

+ return
+
+ {t('配置')} {t('前往 stat.ink')}
- + +
} diff --git a/gui/src/services/config.ts b/gui/src/services/config.ts index 3d8ed76..0022ab9 100644 --- a/gui/src/services/config.ts +++ b/gui/src/services/config.ts @@ -60,5 +60,5 @@ export async function setProfile(index: number, profile: Profile) { } export function canExport(profile: Profile): boolean { - return !!profile.state.loginState?.sessionToken + return !!(profile.state.loginState?.sessionToken && profile.state.statInkApiKey) } diff --git a/gui/src/services/s3si.ts b/gui/src/services/s3si.tsx similarity index 58% rename from gui/src/services/s3si.ts rename to gui/src/services/s3si.tsx index 6520740..7ba9dc8 100644 --- a/gui/src/services/s3si.ts +++ b/gui/src/services/s3si.tsx @@ -1,11 +1,12 @@ import { invoke } from "@tauri-apps/api"; import { JSONRPCClient, S3SIService, StdioTransport } from "jsonrpc"; -import { ExportOpts, State } from "jsonrpc/types"; -import { useCallback } from "react"; +import { ExportOpts, Log, State } from "jsonrpc/types"; +import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; const client = new JSONRPCClient({ transport: new StdioTransport() }).getProxy(); +const LOG_SUB = new Set<(logs: Log[]) => void>(); async function getLogs() { while (true) { @@ -31,10 +32,57 @@ async function getLogs() { break; } } + for (const cb of LOG_SUB) { + cb(r.result); + } } } getLogs() +const LOG_CONTEXT = createContext<{ + logs: Log[], + renderedLogs: React.ReactNode[] +}>({ + logs: [], + renderedLogs: [], +}); + +export const useLog = () => { + return useContext(LOG_CONTEXT); +} + +function renderLevel(log: Log) { + return `[${log.level.toUpperCase()}]`.padEnd(7) +} + +function renderLog(log: Log) { + return `${renderLevel(log)} ${log.msg.map(String).join(' ')}` +} + +export const LogProvider: React.FC<{ limit?: number, children?: React.ReactNode }> = ({ children, limit = 10 }) => { + const [logs, setLogs] = useState([]); + + useEffect(() => { + const cb = (logs: Log[]) => { + setLogs(old => [...old, ...logs].slice(-limit)); + } + LOG_SUB.add(cb); + return () => { + LOG_SUB.delete(cb); + } + }, [limit]) + + + const renderedLogs = useMemo(() => logs.map(renderLog), [logs]) + + return + {children} + +} + export const useLogin = () => { const login = useCallback(async () => { const result = await client.loginSteps();