From b688461137e428601baef8f5566cefad307feafe Mon Sep 17 00:00:00 2001 From: whyour Date: Sat, 26 Feb 2022 14:14:10 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=94=99=E8=AF=AF=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- src/components/index.less | 78 ++++++++++++++++++++++++++++++++++++ src/components/terminal.tsx | 80 +++++++++++++++++++++++++++++++++++++ src/pages/error/index.tsx | 12 ++++-- yarn.lock | 23 ++++++++--- 5 files changed, 185 insertions(+), 10 deletions(-) create mode 100644 src/components/index.less create mode 100644 src/components/terminal.tsx diff --git a/package.json b/package.json index c4ca9a33..bccd7231 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "@umijs/plugin-antd": "^0.11.0", "@umijs/plugin-esbuild": "^1.4.1", "@umijs/test": "^3.3.9", + "ansi-to-react": "^6.1.6", "antd": "^4.17.0-alpha.6", "codemirror": "^5.62.2", "compression-webpack-plugin": "6.1.1", @@ -100,7 +101,6 @@ "react-dnd-html5-backend": "^14.0.0", "react-dom": "17.x", "react-split-pane": "^0.1.92", - "react-terminal-ui": "^0.1.14", "sockjs-client": "^1.5.2", "ts-node": "^9.0.0", "typescript": "^4.1.2", diff --git a/src/components/index.less b/src/components/index.less new file mode 100644 index 00000000..755f4704 --- /dev/null +++ b/src/components/index.less @@ -0,0 +1,78 @@ +.react-terminal-wrapper { + width: 100%; + background: #252a33; + color: #eee; + font-size: 18px; + font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, + monospace; + border-radius: 4px; + padding: 75px 45px 35px; + position: relative; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.react-terminal { + height: 600px; + overflow: auto; + display: flex; + flex-direction: column; +} + +.react-terminal-wrapper.react-terminal-light { + background: #ddd; + color: #1a1e24; +} + +.react-terminal-wrapper:before { + content: ''; + position: absolute; + top: 15px; + left: 15px; + display: inline-block; + width: 15px; + height: 15px; + border-radius: 50%; + background: #d9515d; + -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; + box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; +} + +.react-terminal-wrapper:after { + content: attr(data-terminal-name); + position: absolute; + color: #a2a2a2; + top: 5px; + left: 0; + width: 100%; + text-align: center; +} + +.react-terminal-wrapper.react-terminal-light:after { + color: #d76d77; +} + +.react-terminal-line { + display: block; + line-height: 1.5; +} + +.react-terminal-line:before { + content: ''; + display: inline-block; + vertical-align: middle; + color: #a2a2a2; +} + +.react-terminal-light .react-terminal-line:before { + color: #d76d77; +} + +.react-terminal-input:before { + margin-right: 0.75em; + content: '$'; +} + +.react-terminal-input[data-terminal-prompt]:before { + content: attr(data-terminal-prompt); +} diff --git a/src/components/terminal.tsx b/src/components/terminal.tsx new file mode 100644 index 00000000..0492595b --- /dev/null +++ b/src/components/terminal.tsx @@ -0,0 +1,80 @@ +import React, { useEffect, useRef } from 'react'; +import './index.less'; + +export enum LineType { + Input, + Output, +} + +export enum ColorMode { + Light, + Dark, +} + +export interface Props { + name?: string; + prompt?: string; + colorMode?: ColorMode; + lineData: Array<{ type: LineType; value: string | React.ReactNode }>; + startingInputValue?: string; +} + +const Terminal = ({ + name, + prompt, + colorMode, + lineData, + startingInputValue = '', +}: Props) => { + const lastLineRef = useRef(null); + + // An effect that handles scrolling into view the last line of terminal input or output + const performScrolldown = useRef(false); + useEffect(() => { + if (performScrolldown.current) { + // skip scrolldown when the component first loads + setTimeout( + () => + lastLineRef?.current?.scrollIntoView({ + behavior: 'smooth', + block: 'nearest', + }), + 500, + ); + } + performScrolldown.current = true; + }, [lineData.length]); + + const renderedLineData = lineData.map((ld, i) => { + const classes = ['react-terminal-line']; + if (ld.type === LineType.Input) { + classes.push('react-terminal-input'); + } + // `lastLineRef` is used to ensure the terminal scrolls into view to the last line; make sure to add the ref to the last + if (lineData.length === i + 1) { + return ( + + {ld.value} + + ); + } else { + return ( + + {ld.value} + + ); + } + }); + + const classes = ['react-terminal-wrapper']; + if (colorMode === ColorMode.Light) { + classes.push('react-terminal-light'); + } + return ( +
+
{renderedLineData}
+
+ ); +}; + +export default Terminal; diff --git a/src/pages/error/index.tsx b/src/pages/error/index.tsx index 59b905e0..04643e5f 100644 --- a/src/pages/error/index.tsx +++ b/src/pages/error/index.tsx @@ -1,12 +1,13 @@ import React, { useState, useEffect } from 'react'; import config from '@/utils/config'; import { request } from '@/utils/http'; -import Terminal, { ColorMode, LineType } from 'react-terminal-ui'; +import Terminal, { ColorMode, LineType } from '../../components/terminal'; import { PageLoading } from '@ant-design/pro-layout'; import { history } from 'umi'; +import Ansi from 'ansi-to-react'; import './index.less'; -const Error = ({ user }: any) => { +const Error = ({ user, theme }: any) => { const [loading, setLoading] = useState(false); const [data, setData] = useState('暂无日志'); @@ -37,10 +38,13 @@ const Error = ({ user }: any) => { ) : ( {data}, + }, ]} /> )} diff --git a/yarn.lock b/yarn.lock index 0833ef45..9e66a2df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1997,6 +1997,11 @@ ajv@^6.1.0, ajv@^6.12.3, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +anser@^1.4.1: + version "1.4.10" + resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.10.tgz#befa3eddf282684bd03b63dcda3927aef8c2e35b" + integrity sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww== + ansi-align@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz" @@ -2055,6 +2060,14 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-to-react@^6.1.6: + version "6.1.6" + resolved "https://registry.yarnpkg.com/ansi-to-react/-/ansi-to-react-6.1.6.tgz#d6fe15ecd4351df626a08121b1646adfe6c02ccb" + integrity sha512-+HWn72GKydtupxX9TORBedqOMsJRiKTqaLUKW8txSBZw9iBpzPKLI8KOu4WzwD4R7hSv1zEspobY6LwlWvwZ6Q== + dependencies: + anser "^1.4.1" + escape-carriage "^1.3.0" + antd-mobile@^2.3.1: version "2.3.4" resolved "https://registry.npmjs.org/antd-mobile/-/antd-mobile-2.3.4.tgz" @@ -4090,6 +4103,11 @@ escalade@^3.1.1: resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escape-carriage@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/escape-carriage/-/escape-carriage-1.3.0.tgz#71006b2d4da8cb6828686addafcb094239c742f3" + integrity sha512-ATWi5MD8QlAGQOeMgI8zTp671BG8aKvAC0M7yenlxU4CRLGO/sKthxVUyjiOFKjHdIo+6dZZUNFgHFeVEaKfGQ== + escape-goat@^2.0.0: version "2.1.1" resolved "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz" @@ -9207,11 +9225,6 @@ react-style-proptype@^3.2.2: dependencies: prop-types "^15.5.4" -react-terminal-ui@^0.1.14: - version "0.1.14" - resolved "https://registry.yarnpkg.com/react-terminal-ui/-/react-terminal-ui-0.1.14.tgz#c024853da862d83f1a5720f92ffb7e91c508a4cf" - integrity sha512-VTk4/kjSV1ElyiBP2mWlXwU+UscIb7edolYlZcL+MqZBrx5hbI/O6zaG+7nCMih5Nk4fjbKEysxCcOvf8k5J8A== - react-tween-state@^0.1.5: version "0.1.5" resolved "https://registry.npmjs.org/react-tween-state/-/react-tween-state-0.1.5.tgz"