Browse Source

feat: complete browser console for code running

master
yangpei 4 years ago
parent
commit
9cf87e9fcf
  1. 11
      packages/core/src/components/codeRun/index.module.less
  2. 20
      packages/core/src/components/codeRun/index.tsx
  3. 67
      packages/core/src/components/console/index.tsx
  4. 59
      packages/core/src/components/console/log/index.tsx
  5. 114
      packages/core/src/components/console/logView/dataItem.tsx
  6. 64
      packages/core/src/components/console/logView/dataList.tsx
  7. 48
      packages/core/src/components/console/logView/index.module.less
  8. 61
      packages/core/src/components/console/logView/index.tsx
  9. 28
      packages/core/src/components/console/logView/simple.tsx
  10. 35
      packages/core/src/components/console/store/index.ts
  11. 2
      packages/core/src/mods/settingBar/components/code/index.tsx
  12. 2
      packages/core/src/mods/settingBar/mods/testCase/index.tsx

11
packages/core/src/components/codeRun/index.module.less

@ -39,10 +39,13 @@
height:50%; height:50%;
flex:1; flex:1;
border:1px solid #eee; border:1px solid #eee;
text-align: center;
color:#bfbfbf; .placeholder{
font-size:24px; text-align: center;
line-height: 300px; color:#bfbfbf;
font-size:18px;
line-height: 300px;
}
} }
} }
} }

20
packages/core/src/components/codeRun/index.tsx

@ -3,12 +3,10 @@ import JsonView from 'react-json-view';
import { Tabs, Form, Input, Button, Space } from 'antd'; import { Tabs, Form, Input, Button, Space } from 'antd';
const { TabPane } = Tabs; const { TabPane } = Tabs;
import CodeEditor from '../codeEditor'; import CodeEditor from '../codeEditor';
import Console from '../console';
import { inputJson, outputJson } from './json' import { inputJson, outputJson } from './json'
import styles from './index.module.less'; import styles from './index.module.less';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons'; import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
interface IProps {
onChange: (value: object) => void
}
interface MyFormItemProps { interface MyFormItemProps {
type: string type: string
} }
@ -51,8 +49,11 @@ const MyFormItem: React.FC<MyFormItemProps> = (props) => {
) )
} }
interface VisualInputProps {
onChange: (value: object) => void
}
const VisualInput: React.FC<IProps> = (props) => { const VisualInput: React.FC<VisualInputProps> = (props) => {
const [form] = Form.useForm() const [form] = Form.useForm()
const PAYLOAD = 'payload', PIPE = 'pipe', CONTEXT = 'context', CONFIG = 'config' const PAYLOAD = 'payload', PIPE = 'pipe', CONTEXT = 'context', CONFIG = 'config'
const onChange = () => { const onChange = () => {
@ -70,8 +71,12 @@ const VisualInput: React.FC<IProps> = (props) => {
</div> </div>
) )
} }
interface CodeRunProps {
onChange: (value: object) => void,
hasConsole: boolean
}
const CodeRun: React.FC<IProps> = (props) => { const CodeRun: React.FC<CodeRunProps> = (props) => {
const onFormChange = (value: any) => { const onFormChange = (value: any) => {
props.onChange(value) props.onChange(value)
} }
@ -120,7 +125,10 @@ const CodeRun: React.FC<IProps> = (props) => {
/> />
</div> </div>
<div className={styles.rightBottom}> <div className={styles.rightBottom}>
console控制台 {props.hasConsole ?
<Console /> :
<p className={styles.placeholder}>console内容哦</p>
}
</div> </div>
</div> </div>
</div> </div>

67
packages/core/src/components/console/index.tsx

@ -0,0 +1,67 @@
import React, { useEffect } from 'react'
import Log from './log'
import logStore, { ConsoleType } from './store'
const MyConsole: React.FC = () => {
const console: any = {}
useEffect(() => {
executeConsole()
}, [])
const processLog = (item: ConsoleType) => {
const { logType } = item
let { infos = [] } = item
if (!infos.length) return
infos = [].slice.call(infos || [])
logStore.addLog({ logType, infos })
printOriginLog(item)
}
const clearLog = () => {
logStore.clearLog()
}
const printOriginLog = (item: ConsoleType) => {
const { logType, infos } = item
if (typeof console[logType] === 'function') {
console[logType].apply(window.console, infos)
}
}
const executeConsole = () => {
if (!window.console) {
return
}
// 模拟浏览器中的console方法
const methodList = ['log', 'info', 'warn', 'debug', 'error']
methodList.map(method => {
// @ts-ignore
console[method] = window.console[method]
})
methodList.map(method => {
// @ts-ignore
window.console[method] = (...args: any[]) => {
processLog({
logType: method,
infos: args
})
}
})
window.console.clear = (...args: any[]) => {
clearLog()
console.clear.apply(window.console, args)
}
}
return (
<div style={{ height: '100%' }}>
<Log logList={logStore.computeLogList} />
</div>
)
}
export default MyConsole

59
packages/core/src/components/console/log/index.tsx

@ -0,0 +1,59 @@
import React from 'react'
import DataView from '../logView'
import { ConsoleType } from '../store'
interface Props {
logList: ConsoleType[]
}
const Log: React.FC<Props> = (props) => {
const renderLogList = (logList: ConsoleType[]) => {
return logList.map((log: ConsoleType, index: number) => {
const dataView = log.infos.map((info: any, i: number) => {
return typeof info !== 'object' || info === null ?
<div key={i}>{`${info}`}</div> :
<DataView data={info} key={i} />
})
let ele = null
// 根据不同console类型展示不同的样式
switch (log.logType) {
case 'warn':
ele = <div
key={index}
style={{
background: '#fffacd',
color: 'orange',
border: '1px solid #ffb930'
}}
>
{dataView}
</div>
break
case 'log':
ele = <div key={index}>
{dataView}
</div>
break
case 'error':
ele = <div
key={index}
style={{
background: '#ffe4e1',
border: '1px solid #f4a0ab'
}}
>
<div style={{ color: '#dc143c' }}>{dataView}</div>
</div>
break
}
return ele
})
}
return (
<div style={{ height: '100%', overflow: 'auto' }}>{renderLogList(props.logList)}</div>
)
}
export default Log

114
packages/core/src/components/console/logView/dataItem.tsx

@ -0,0 +1,114 @@
import React, { useState, useEffect } from 'react'
// import DataList from './dataList'
import Simple from './simple'
import styles from './index.module.less'
const previewComplex = (data: any) => {
if (Array.isArray(data)) {
return <span>Array[{data.length}]</span>
}
switch (typeof data) {
case 'function':
return <span>{data.name || 'fn'}()</span>
case 'object':
case 'undefined':
case null:
return '{…}'
}
return null
}
interface Props {
startOpen?: boolean
noSort?: boolean
name: string
value: any
}
interface State {
open: boolean
loading?: boolean
}
const DataItem: React.FC<Props> = (props) => {
const [state, setState] = useState<State>({ open: !!props.startOpen, loading: false })
let opener = null
let children = null
let { name } = props
let preview
useEffect(() => {
if (state.open && props.value) {
setState({ loading: true, open: true })
}
}, [])
useEffect(() => {
const data = props.value
const otype = typeof data
let complex = true
if (
otype === 'number' ||
otype === 'string' ||
data == null ||
otype === 'boolean'
) {
preview = <Simple data={data} />
complex = false
} else {
preview = previewComplex(data)
}
const open = state.open
if (complex && Object.keys(props.value).length) {
opener = (
<div onClick={toggleOpen()} className={styles.opener}>
{open ?
<span className={styles.openerOpen} /> :
<span className={styles.openerClose} />
}
</div>
)
}
// if (complex && open) {
// children = <DataList data={data} />
// }
if (name.length > 50) {
name = name.slice(0, 50) + '…'
}
}, [props])
const toggleOpen = () => {
return () => {
const data = props.value
const isArray = Array.isArray(data)
if (state.loading) return
if (!isArray && !Object.keys(props.value).length) return
if (isArray && data.length === 0) return
setState({
open: !state.open
})
}
}
return (
<li className={styles.codeWrap}>
<div className={styles.codeBox}>
{opener}
<div onClick={toggleOpen()} className={styles.codeKey}>
{name ? `${name}:` : ''}
</div>
<div className={styles.codeVal}>{preview}</div>
</div>
{children}
</li>
)
}
export default DataItem

64
packages/core/src/components/console/logView/dataList.tsx

@ -0,0 +1,64 @@
import React, { useState, useEffect } from 'react'
import DataItem from './dataItem'
import styles from './index.module.less'
interface DataListProps {
data: any
startOpen?: boolean
noSort?: boolean
}
const alphanumericSort = (a: string, b: string): number => {
if ('' + +a === a) {
if ('' + +b !== b) {
return -1
}
return +a < +b ? -1 : 1
}
return a < b ? -1 : 1
}
const DataList: React.FC<DataListProps> = (props) => {
const [element, setElement] = useState<any[]>([])
const renderItem = (name: string, key: string) => {
const data = props.data
return (
<DataItem
key={key}
name={name}
startOpen={props.startOpen}
value={data[name]}
/>
)
}
useEffect(() => {
const { data } = props
if (!data) return
const isArray = Array.isArray(data)
const ele: any[] = []
if (isArray) {
data.forEach((item: any, i: number) => {
ele.push(renderItem(String(i), String(i)))
})
} else {
const names = Object.keys(data)
if (!props.noSort) {
names.sort(alphanumericSort)
}
names.forEach((name, i) => {
ele.push(renderItem(name, name))
})
}
setElement(ele)
})
return (
!element.length ?
<div>{Array.isArray(props.data) ? 'Empty array' : 'Empty object'}</div> :
<ul className={styles.codeContainer}>{element}</ul>
)
}
export default DataList

48
packages/core/src/components/console/logView/index.module.less

@ -0,0 +1,48 @@
.codeContainer {
padding: 10px;
font-size: 12px;
list-style: none;
margin: 0;
padding: 0;
}
.codeBox {
display: flex;
position: relative;
}
.codeVal {
display: flex;
white-space: pre;
word-break: break-word;
margin-left: 10px;
flex: 1;
color: #183691;
}
.codeKey {
margin-left: 5px;
color: #905;
}
.opener {
cursor: pointer;
margin-left: -10px;
position: absolute;
}
.openerOpen {
border-color: #bbb transparent transparent transparent;
border-style: solid;
border-width: 6px 4px 0 4px;
display: inline-block;
vertical-align: middle;
}
.openerClose {
border-color: transparent transparent transparent #bbb;
border-style: solid;
border-width: 4px 0 4px 6px;
display: inline-block;
vertical-align: middle;
}

61
packages/core/src/components/console/logView/index.tsx

@ -0,0 +1,61 @@
import React, { useState, useEffect } from 'react'
import DataList from './dataList'
import styles from './index.module.less'
interface DataViewProps {
data: any
startOpen?: boolean
}
interface State {
open: boolean
}
const DataView: React.FC<DataViewProps> = (props) => {
const [state, setState] = useState<State>({ open: !!props.startOpen })
let children = null
let preview
const toggleOpen = () => {
return () => {
setState({
open: !state.open
})
}
}
useEffect(() => {
const { data } = props
const { open } = state
if (!data) {
return
}
const isArray = Array.isArray(data)
children = open ? <DataList data={data} /> : null
if (isArray) {
preview = '[...]'
} else {
preview = '{...}'
}
}, [props])
return (
<ul className={styles.codeContainer}>
<li className={styles.codeWrap}>
<div className={styles.codeBox}>
<div onClick={toggleOpen()} className={styles.opener}>
{state.open ? (
<span className={styles.openerOpen} />
) : (
<span className={styles.openerClose} />
)}
</div>
<div className={styles.codeVal}>{preview}</div>
</div>
{children}
</li>
</ul>
)
}
export default DataView

28
packages/core/src/components/console/logView/simple.tsx

@ -0,0 +1,28 @@
import React, { useState, useEffect } from 'react'
interface Props {
data: any
}
const Simple: React.FC<Props> = (props) => {
const [data, setData] = useState(props.data)
const valueToText = (value: any) => {
if (value === undefined) {
return 'undefined'
} else if (typeof value === 'number') {
return value.toString()
}
return JSON.stringify(value)
}
useEffect(() => {
if (typeof data === 'string' && data.length > 200) {
setData(data.slice(0, 200) + '…')
}
}, [props])
return <div style={{ color: '#333' }}>{valueToText(data)}</div>
}
export default Simple

35
packages/core/src/components/console/store/index.ts

@ -0,0 +1,35 @@
export interface ConsoleType {
logType: string
infos: any[]
}
export class ConsoleStore {
logList: ConsoleType[] = []
logType: string = 'All'
addLog(log: ConsoleType) {
this.logList.push(log)
}
clearLog() {
this.logList = []
}
get computeLogList() {
let ret: ConsoleType[] = []
if (this.logType === 'All') {
ret = this.logList
} else {
ret = this.logList.filter((item) => {
return item.logType === this.logType.toLowerCase()
})
}
return ret
}
changeLogType(type: string) {
this.logType = type
}
}
export default new ConsoleStore()

2
packages/core/src/mods/settingBar/components/code/index.tsx

@ -158,7 +158,7 @@ const EditModal: React.FC<IEditorModalProps> = (props) => {
onOk={onRunOk} onOk={onRunOk}
onCancel={onRunCancel} onCancel={onRunCancel}
> >
<CodeRun onChange={onChange} /> <CodeRun onChange={onChange} hasConsole={false} />
</Modal> </Modal>
</Modal> </Modal>
); );

2
packages/core/src/mods/settingBar/mods/testCase/index.tsx

@ -62,7 +62,7 @@ const EditModal: React.FC<IEditModalProps> = (props): JSX.Element => {
onOk={onOk} onOk={onOk}
onCancel={onCancel} onCancel={onCancel}
> >
<CodeRun onChange={onChange} /> <CodeRun onChange={onChange} hasConsole={false} />
</Modal> </Modal>
); );
}; };

Loading…
Cancel
Save