重构接口列表,新建接口按钮放到最上面,列表透出地址信息,清爽+1

解决组件性能问题,切换接口不再闪动

编辑接口时可以同时编辑字段和接口本身的信息,而不是分散在两个地方

接口详情中的大量信息悬停后会出现「点击复制」的按钮,方便在代码里使用

超长 value 添加滚动显示,不要占据太大的页面空间

为根节点添加加号,尊重用户使用惯性

在接口不 match 的情况下提醒用户是不是用错了请求方式

……其他视觉细节
test
bigfengyu 6 years ago
parent f9d5525374
commit 1da7d36190

@ -26,10 +26,12 @@
"@material-ui/icons": "^4.2.1",
"@material-ui/pickers": "^3.1.2",
"@material-ui/styles": "^4.2.0",
"@types/json5": "^0.0.30",
"animate.css": "3.7.2",
"bootstrap": "^4.3.1",
"chart.js": "^2.8.0",
"classnames": "^2.2.6",
"clipboard-copy": "^3.1.0",
"codemirror": "5.48.0",
"connected-react-router": "^6.5.0",
"debounce-promise": "^3.1.2",
@ -38,6 +40,7 @@
"graceful": "1.0.2",
"history": "^4.9.0",
"jquery": "^3.4.1",
"json5": "^2.1.0",
"koa": "2.7.0",
"koa-router": "7.4.0",
"koa-session": "5.12.2",
@ -47,10 +50,12 @@
"moment": "2.24.0",
"node-fetch": "2.6.0",
"normalizr": "^3.4.0",
"notistack": "^0.8.9",
"nprogress": "0.2.0",
"parsleyjs": "^2.9.1",
"popper.js": "^1.15.0",
"prop-types": "15.7.2",
"rc-tooltip": "^3.7.3",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-icons": "3.7.0",
@ -87,6 +92,7 @@
"@types/mockjs": "^1.0.2",
"@types/node": "^12.6.2",
"@types/nprogress": "^0.2.0",
"@types/rc-tooltip": "^3.7.1",
"@types/react": "^16.8.23",
"@types/react-dom": "^16.8.4",
"@types/react-modal": "^3.8.2",

@ -98,14 +98,17 @@ export const unlockInterfaceFailed = (message: any) => ({
message,
})
export const sortInterfaceList = (ids: any, onResolved: any) => ({
export const sortInterfaceList = (ids: any, moduleId: number, onResolved: any) => ({
type: 'INTERFACE_LIST_SORT',
ids,
moduleId,
onResolved,
})
export const sortInterfaceListSucceeded = (count: any) => ({
export const sortInterfaceListSucceeded = (count: any, ids: any, moduleId: number) => ({
type: 'INTERFACE_LIST_SORT_SUCCEEDED',
count,
ids,
moduleId,
})
export const sortInterfaceListFailed = (message: any) => ({
type: 'INTERFACE_LIST_SORT_FAILED',

@ -46,9 +46,10 @@ export const sortModuleList = (ids: any, onResolved: any) => ({
ids,
onResolved,
})
export const sortModuleListSucceeded = (count: any) => ({
export const sortModuleListSucceeded = (count: any, ids: any) => ({
type: 'MODULE_LIST_SORT_SUCCEEDED',
count,
ids,
})
export const sortModuleListFailed = (message: any) => ({
type: 'MODULE_LIST_SORT_FAILED',

@ -18,6 +18,8 @@ export const fetchRepository = ({ id, repository }: any) => ({ type: 'REPOSITORY
export const fetchRepositorySucceeded = (repository: any) => ({ type: 'REPOSITORY_FETCH_SUCCEEDED', repository })
export const fetchRepositoryFailed = (message: any) => ({ type: 'REPOSITORY_FETCH_FAILED', message })
export const repositoryLocationChange = ({ id, repository }: any) => ({ type: 'REPOSITORY_LOCATION_CHANGE', id, repository })
export const clearRepository = () => ({ type: 'REPOSITORY_CLEAR' })
export const fetchRepositoryCount = () => ({ type: 'REPOSITORY_COUNT_FETCH' })

@ -150,3 +150,6 @@ table.table
-ms-flex: 1 1 auto;
flex: 1 1 auto;
padding: 1.25rem;
button:focus
outline: none;

@ -2,12 +2,10 @@
// GitHub
// TODO 2.x codesandbox.io
// @import '../../node_modules/bootstrap/dist/css/bootstrap.css';
@import "./fonts.sass"
@import "./variables.sass"
@import "./components.sass"
@import "./shortcuts.sass"
// @import "../../node_modules/animate.css/animate.css"
html
font-size: 62.5%; // 10 ÷ 16 × 100% = 62.5%

@ -15,6 +15,10 @@ const MuiTheme = createMuiTheme({
},
},
},
typography: {
htmlFontSize: 10,
fontSize: 12,
},
...theme,
})

@ -9,6 +9,7 @@ import { fetchRepository } from '../../actions/repository'
import { RootState } from 'actions/types'
import { lockInterface, unlockInterface } from 'actions/interface'
import { updateProperties } from 'actions/property'
import { updateInterface } from 'actions/interface'
export const RequestPropertyList = (props: any) => {
return <PropertyList scope="request" title="请求参数" label="请求" {...props} />
@ -24,6 +25,7 @@ type InterfaceEditorProps = {
repository: any
lockInterface: typeof lockInterface
unlockInterface: typeof unlockInterface
updateInterface: typeof updateInterface
updateProperties: typeof updateProperties
}
@ -43,7 +45,7 @@ class InterfaceEditor extends Component<
static childContextTypes = {
handleLockInterface: PropTypes.func.isRequired,
handleUnlockInterface: PropTypes.func.isRequired,
handleSaveInterface: PropTypes.func.isRequired,
handleSaveInterfaceAndProperties: PropTypes.func.isRequired,
handleMoveInterface: PropTypes.func.isRequired,
handleAddMemoryProperty: PropTypes.func.isRequired,
handleAddMemoryProperties: PropTypes.func.isRequired,
@ -93,8 +95,8 @@ class InterfaceEditor extends Component<
// shouldComponentUpdate (nextProps, nextState) {}
render() {
const { auth, repository, mod, itf } = this.props
const { editable } = this.state
const { auth, repository, mod } = this.props
const { editable, itf } = this.state
const { id, locker } = this.state.itf
if (!id) { return null }
return (
@ -108,7 +110,7 @@ class InterfaceEditor extends Component<
moveInterface={this.handleMoveInterface}
handleLockInterface={this.handleLockInterface}
handleMoveInterface={this.handleMoveInterface}
handleSaveInterface={this.handleSaveInterface}
handleSaveInterfaceAndProperties={this.handleSaveInterfaceAndProperties}
handleUnlockInterface={this.handleUnlockInterface}
/>
<InterfaceSummary
@ -118,6 +120,7 @@ class InterfaceEditor extends Component<
active={true}
editable={editable}
stateChangeHandler={this.summaryStateChange}
handleChangeInterface={this.handleChangeInterface}
/>
<RequestPropertyList
properties={this.state.properties}
@ -195,9 +198,28 @@ class InterfaceEditor extends Component<
this.setState({ properties })
}
}
handleSaveInterface = (e: any) => {
handleChangeInterface = (newItf: any) => {
this.setState({
itf: {
...this.state.itf,
...newItf,
},
})
}
handleSaveInterfaceAndProperties = (e: any) => {
e.preventDefault()
const { updateProperties } = this.props
const { itf } = this.state
const { updateProperties, updateInterface } = this.props
updateInterface({
id: itf.id,
name: itf.name,
url: itf.url,
method: itf.method,
status: itf.status,
description: itf.description,
}, () => {
/** empty */
})
updateProperties(this.state.itf.id, this.state.properties, this.state.summaryState, () => {
this.handleUnlockInterface()
})
@ -208,7 +230,6 @@ class InterfaceEditor extends Component<
})
}
handleMoveInterfaceSubmit = () => {
console.log('submit')
/** empty */
}
handleLockInterface = () => {
@ -230,6 +251,7 @@ const mapDispatchToProps = {
lockInterface,
unlockInterface,
updateProperties,
updateInterface,
}
export default connect(
mapStateToProps,

@ -32,7 +32,7 @@ interface Props {
editable: boolean,
itfId: number,
moveInterface: any
handleSaveInterface: any
handleSaveInterfaceAndProperties: any
handleUnlockInterface: any
handleMoveInterface: any
handleLockInterface: any
@ -40,7 +40,7 @@ interface Props {
function InterfaceEditorToolbar(props: Props) {
const { editable, locker, auth, repository, handleLockInterface, handleMoveInterface,
handleSaveInterface, handleUnlockInterface } = props
handleSaveInterfaceAndProperties, handleUnlockInterface } = props
const isOwned = repository.owner.id === auth.id
const isJoined = repository.members.find((item: any) => item.id === auth.id)
const loading = useSelector((state: RootState) => state.loading)
@ -50,7 +50,14 @@ function InterfaceEditorToolbar(props: Props) {
if (editable) {
return (
<div className="InterfaceEditorToolbar">
<LoadingButton className={classes.button} onClick={handleSaveInterface} variant="contained" color="primary" disabled={loading} label="保存">
<LoadingButton
className={classes.button}
onClick={handleSaveInterfaceAndProperties}
variant="contained"
color="primary"
disabled={loading}
label="保存"
>
<Save className={classes.rightIcon} />
</LoadingButton>
<Button className={classes.button} onClick={handleUnlockInterface} variant="contained">

@ -1,3 +0,0 @@
.InterfaceList
max-height: 500px;
overflow-y: auto;

@ -1,10 +1,17 @@
import React, { Component } from 'react'
import { connect, Link, replace, StoreStateRouterLocationURI } from '../../family'
import {
connect,
Link,
replace,
StoreStateRouterLocationURI
} from '../../family'
import { sortInterfaceList } from '../../actions/interface'
import { RModal, RSortable } from '../utils'
import InterfaceForm from './InterfaceForm'
import { GoPencil, GoTrashcan, GoRocket, GoLock } from 'react-icons/go'
import { GoPencil, GoTrashcan, GoLock } from 'react-icons/go'
import { getCurrentInterface } from '../../selectors/interface'
import PropTypes from 'prop-types'
import Button from '@material-ui/core/Button'
import './InterfaceList.css'
import { RootState } from 'actions/types'
@ -28,36 +35,51 @@ class InterfaceBase extends Component<InterfaceProps, InterfaceState> {
const isJoined = repository.members.find((i: any) => i.id === auth.id)
return (
<div className="Interface clearfix">
<span className="name">
{itf.locker ? (
<span className="locked mr5">
<GoLock />
</span>
) : null}
<span>
<Link
to={selectHref}
onClick={e => {
if (
this.props.curItf &&
this.props.curItf.locker &&
!window.confirm('编辑模式下切换接口,会导致编辑中的资料丢失,是否确定切换接口?')
!window.confirm(
'编辑模式下切换接口,会导致编辑中的资料丢失,是否确定切换接口?'
)
) {
e.preventDefault()
}
}}
>
<span>{itf.name}</span>
<div className="name">{itf.name}</div>
<div className="url">{itf.url}</div>
</Link>
</span>
{isOwned || isJoined ? (
<div className="toolbar">
{itf.locker ? (
<span className="locked mr5">
<GoLock />
</span>
) : null}
{!itf.locker || itf.locker.id === auth.id ? (
<span className="fake-link" onClick={() => this.setState({ update: true })}>
<span
className="fake-link"
onClick={() => this.setState({ update: true })}
>
<GoPencil />
</span>
) : null}
<RModal when={this.state.update} onClose={() => this.setState({ update: false })} onResolve={this.handleUpdate}>
<InterfaceForm title="修改接口" repository={repository} mod={mod} itf={itf} />
<RModal
when={this.state.update}
onClose={() => this.setState({ update: false })}
onResolve={this.handleUpdate}
>
<InterfaceForm
title="修改接口"
repository={repository}
mod={mod}
itf={itf}
/>
</RModal>
{!itf.locker ? (
<Link to="" onClick={e => this.handleDelete(e, itf)}>
@ -71,21 +93,29 @@ class InterfaceBase extends Component<InterfaceProps, InterfaceState> {
}
handleDelete = (e: any, itf: any) => {
e.preventDefault()
const message = `接口被删除后不可恢复!\n确认继续删除『#${itf.id} ${itf.name}』吗?`
const message = `接口被删除后不可恢复!\n确认继续删除『#${itf.id} ${
itf.name
}`
if (window.confirm(message)) {
const { onDeleteInterface } = this.context
onDeleteInterface(itf.id, () => {
const { store } = this.context
const uri = StoreStateRouterLocationURI(store)
const deleteHref = this.props.active ? uri.removeSearch('itf').href() : uri.href()
const deleteHref = this.props.active
? uri.removeSearch('itf').href()
: uri.href()
store.dispatch(replace(deleteHref))
})
}
};
handleUpdate = () => { /** test */ }
handleUpdate = () => {
/** test */
};
}
const Interface = connect((state: any) => ({ router: state.router }))(InterfaceBase)
const Interface = connect((state: any) => ({ router: state.router }))(
InterfaceBase
)
type InterfaceListProps = any
type InterfaceListState = any
class InterfaceList extends Component<InterfaceListProps, InterfaceListState> {
@ -95,46 +125,76 @@ class InterfaceList extends Component<InterfaceListProps, InterfaceListState> {
}
render() {
const { auth, repository, mod, itfs = [], itf, curItf } = this.props
if (!mod.id) { return null }
if (!mod.id) {
return null
}
const isOwned = repository.owner.id === auth.id
const isJoined = repository.members.find((i: any) => i.id === auth.id)
return (
<article className="InterfaceList">
{isOwned || isJoined ? (
<div className="header">
<Button
variant="outlined"
fullWidth={true}
color="primary"
onClick={() => this.setState({ create: true })}
>
</Button>
<RModal
when={this.state.create}
onClose={() => this.setState({ create: false })}
onResolve={this.handleCreate}
>
<InterfaceForm
title="新建接口"
repository={repository}
mod={mod}
/>
</RModal>
</div>
) : null}
<RSortable onChange={this.handleSort} disabled={!isOwned && !isJoined}>
<ul className="body">
{itfs.map((item: any) => (
<li key={item.id} className={item.id === itf.id ? 'active sortable' : 'sortable'} data-id={item.id}>
<Interface repository={repository} mod={mod} itf={item} active={item.id === itf.id} auth={auth} curItf={curItf} />
<li
key={item.id}
className={item.id === itf.id ? 'active sortable' : 'sortable'}
data-id={item.id}
>
<Interface
repository={repository}
mod={mod}
itf={item}
active={item.id === itf.id}
auth={auth}
curItf={curItf}
/>
</li>
))}
</ul>
</RSortable>
{isOwned || isJoined ? (
<div className="footer">
<span className="fake-link" onClick={() => this.setState({ create: true })}>
<span className="fontsize-14">
<GoRocket />
</span>{' '}
</span>
<RModal when={this.state.create} onClose={() => this.setState({ create: false })} onResolve={this.handleCreate}>
<InterfaceForm title="新建接口" repository={repository} mod={mod} />
</RModal>
</div>
) : null}
</article>
)
}
handleCreate = () => { /** empty */ }
handleCreate = () => {
/** empty */
};
handleSort = (_: any, sortable: any) => {
const { onSortInterfaceList } = this.context
onSortInterfaceList(sortable.toArray())
const { onSortInterfaceList, mod } = this.props
onSortInterfaceList(sortable.toArray(), mod.id)
};
}
const mapStateToProps = (state: RootState) => ({
const mapStateToProps = (state: RootState) => ({
auth: state.auth,
curItf: getCurrentInterface(state),
router: state.router,
})
const mapDispatchToProps = {}
export default connect(mapStateToProps, mapDispatchToProps)(InterfaceList)
const mapDispatchToProps = {
onSortInterfaceList: sortInterfaceList,
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(InterfaceList)

@ -62,8 +62,8 @@ class Previewer extends Component<any, any> {
console.warn(Assert.message(i))
}
return (
<div className="Previewer row">
<div className="result-template col-6">
<div className="Previewer">
<div className="result-template">
<div className="header">
<span className="title">{label}</span>
{scope === 'response'
@ -76,8 +76,9 @@ class Previewer extends Component<any, any> {
if (v !== undefined && v !== null && v.exec) { return v.toString() } else { return v }
}, 2)
}</pre>
</div>
<div className="result-mocked col-6">
<div className="result-mocked">
<div className="header">
<span className="title">{label}</span>
{scope === 'response'

@ -11,4 +11,6 @@
list-style: none
margin: 0
padding: 0
margin-bottom: 5px
margin-bottom: 5px
.nav-tabs
margin-top: 20px

@ -1,12 +1,13 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Link, replace, StoreStateRouterLocationURI, PropTypes } from '../../family'
import { DialogController } from '../utils'
import { replace, StoreStateRouterLocationURI, PropTypes } from '../../family'
import { serve } from '../../relatives/services/constant'
import InterfaceForm from './InterfaceForm'
import { METHODS, STATUS_LIST } from './InterfaceForm'
import { CopyToClipboard } from '../utils/'
import { getRelativeUrl } from '../../utils/URLUtils'
import './InterfaceSummary.css'
import { RootState } from 'actions/types'
import { TextField, Select, FormControl, InputLabel, Input, MenuItem } from '@material-ui/core'
export const BODY_OPTION = {
FORM_DATA: 'FORM_DATA',
@ -30,6 +31,7 @@ export function rptFromStr2Num(rpt: any) {
}
type InterfaceSummaryProps = {
store: object,
handleChangeInterface: (itf: any) => void,
[x: string]: any,
}
type InterfaceSummaryState = {
@ -92,47 +94,125 @@ class InterfaceSummary extends Component<InterfaceSummaryProps, InterfaceSummary
})
}
render() {
const { repository = {}, mod = {}, itf = {}, editable } = this.props
const { repository = {}, itf = {}, editable, handleChangeInterface } = this.props
const { requestParamsType } = this.state
if (!itf.id) { return null }
return (
<div className="InterfaceSummary">
<div className="header">
<span className="title">{itf.name}</span>
<span className="hide">
<DialogController content={<InterfaceForm title="修改接口" repository={repository} mod={mod} itf={itf} />} onResolved={this.handleUpdate}>
<Link to="" onClick={e => e.preventDefault()} title="修改接口" className="edit">
</Link>
</DialogController>
<Link to="" onClick={e => this.handleDelete(e, itf)} className="delete">
</Link>
</span>
</div>
{!editable && <div className="header">
<CopyToClipboard text={itf.name}>
<span className="title">{itf.name}</span>
</CopyToClipboard>
</div>}
<ul className="summary">
<li>
<span className="label"></span>
<a href={`${serve}/app/mock/${repository.id}${getRelativeUrl(itf.url || '')}`} target="_blank" rel="noopener noreferrer">
{itf.url}
</a>
</li>
<li>
<span className="label"></span>
{itf.method}
</li>
<li>
<span className="label"></span>
{itf.status}
</li>
{itf.description && (
<li>
<span className="label"></span>
{itf.description}
{editable ? <>
<li style={{width: '50%'}}>
<TextField
style={{marginTop: 0}}
id="name"
label="名称"
value={itf.name}
fullWidth={true}
autoComplete="off"
onChange={(e) => {handleChangeInterface({name: e.target.value})}}
margin="normal"
/>
</li>
)}
{editable && (
<li style={{width: '50%'}}>
<TextField
id="url"
label="地址"
value={itf.url}
fullWidth={true}
autoComplete="off"
onChange={(e) => {handleChangeInterface({url: e.target.value})}}
margin="normal"
/>
</li>
<li style={{marginTop: 24}}>
<FormControl>
<InputLabel shrink={true} htmlFor="method-label-placeholder">
</InputLabel>
<Select
value={itf.method}
input={<Input name="method" id="method-label-placeholder" />}
onChange={(e) => {handleChangeInterface({method: e.target.value})}}
displayEmpty={true}
name="method"
>
{METHODS.map(method => <MenuItem key={method} value={method}>{method}</MenuItem>)}
</Select>
</FormControl>
<FormControl style={{marginLeft: 20}}>
<InputLabel shrink={true} htmlFor="status-label-placeholder">
</InputLabel>
<Select
value={itf.status}
input={<Input name="status" id="status-label-placeholder" />}
onChange={(e) => {handleChangeInterface({status: e.target.value})}}
displayEmpty={true}
name="status"
>
{STATUS_LIST.map(status => <MenuItem key={status} value={status}>{status}</MenuItem>)}
</Select>
</FormControl>
</li>
{itf.description && (
<li style={{width: '50%'}}>
<TextField
id="description"
label="描述(可多行)"
value={itf.description}
fullWidth={true}
multiline={true}
autoComplete="off"
onChange={(e) => {handleChangeInterface({description: e.target.value})}}
margin="normal"
/>
</li>
)}
</> : <>
<li>
<CopyToClipboard text={itf.url}>
<span>
<span className="label"></span>
<a href={`${serve}/app/mock/${repository.id}${getRelativeUrl(itf.url || '')}`} target="_blank" rel="noopener noreferrer">{itf.url}</a>
</span>
</CopyToClipboard>
</li>
<li>
<CopyToClipboard text={itf.method}>
<span>
<span className="label"></span>
<span>{itf.method}</span>
</span>
</CopyToClipboard>
</li>
<li>
<CopyToClipboard text={itf.status}>
<span>
<span className="label"></span>
<span>{itf.status}</span>
</span>
</CopyToClipboard>
</li>
{itf.description && (
<li>
<CopyToClipboard text={itf.description}>
<span>
<span className="label"></span>
<span>{itf.description}</span>
</span>
</CopyToClipboard>
</li>
)}
</>
}
</ul>
{editable && (
<ul className="nav nav-tabs" role="tablist">
<li className="nav-item" onClick={this.switchRequestParamsType(REQUEST_PARAMS_TYPE.HEADERS)}>
<button className={`nav-link ${requestParamsType === REQUEST_PARAMS_TYPE.HEADERS ? 'active' : ''}`} role="tab" data-toggle="tab">
@ -151,7 +231,6 @@ class InterfaceSummary extends Component<InterfaceSummaryProps, InterfaceSummary
</li>
</ul>
)}
</ul>
{editable && requestParamsType === REQUEST_PARAMS_TYPE.BODY_PARAMS ? (
<div className="body-options">
<div className="form-check form-check-inline" onClick={this.switchBodyOption(BODY_OPTION.FORM_DATA)}>

@ -4,7 +4,7 @@ import { RModal, RSortable } from '../utils'
import ModuleForm from './ModuleForm'
import { GoPencil, GoTrashcan, GoPackage } from 'react-icons/go'
import { RootState } from 'actions/types'
import { deleteModule } from '../../actions/module'
import { deleteModule, sortModuleList } from '../../actions/module'
class ModuleBase extends Component<any, any> {
constructor(props: any) {
@ -100,7 +100,7 @@ class ModuleList extends Component<any, any> {
replace(StoreStateRouterLocationURI(router).href())
}
handleSort = (_: any, sortable: any) => {
const { onSortModuleList } = this.context
const { onSortModuleList } = this.props
onSortModuleList(sortable.toArray())
}
}
@ -111,6 +111,7 @@ const mapStateToProps = (state: RootState) => ({
})
const mapDispatchToProps = ({
replace,
onSortModuleList: sortModuleList,
})
export default connect(
mapStateToProps,

@ -1,13 +1,14 @@
import React, { Component } from 'react'
import { PropTypes, Link } from '../../family'
import { Tree, SmartTextarea, RModal, RSortable } from '../utils'
import { Tree, SmartTextarea, RModal, RSortable, CopyToClipboard } from '../utils'
import PropertyForm from './PropertyForm'
import Importer from './Importer'
import Previewer from './InterfacePreviewer'
import { GoPlus, GoTrashcan, GoQuestion } from 'react-icons/go'
import { rptFromStr2Num } from './InterfaceSummary'
import './PropertyList.css'
import { ButtonGroup, Button } from '@material-ui/core'
import { ButtonGroup, Button, Checkbox } from '@material-ui/core'
import JSON5 from 'json5'
export const RequestPropertyListPreviewer = (props: any) => (
<Previewer {...props} />
@ -26,12 +27,24 @@ export const ResponsePropertyListPreviewer = (props: any) => (
class SortableTreeTableHeader extends Component<any, any> {
render() {
const { editable } = this.props
const { editable, handleClickCreatePropertyButton } = this.props
return (
<div className="SortableTreeTableHeader">
<div className="flex-row">
{/* DONE 2.1 每列增加帮助 Tip */}
{editable && <div className="th operations" />}
{editable && (
<div className="th operations">
<Link
to=""
onClick={e => {
e.preventDefault()
handleClickCreatePropertyButton()
}}
>
<GoPlus className="fontsize-14 color-6" />
</Link>
</div>
)}
<div className="th name"></div>
<div className="th type"></div>
<div className="th type"></div>
@ -47,7 +60,8 @@ class SortableTreeTableHeader extends Component<any, any> {
<GoQuestion />
</a>
</div>
<div className="th value"></div>{/* 对象和数组也允许设置初始值 */}
<div className="th value"></div>
{/* 对象和数组也允许设置初始值 */}
<div className="th desc"></div>
</div>
</div>
@ -58,11 +72,24 @@ class SortableTreeTableHeader extends Component<any, any> {
const PropertyLabel = (props: any) => {
const { pos } = props
if (pos === 1) {
return <label className="ml5 badge badge-danger">HEAD</label>
return <span className="badge badge-danger">HEAD</span>
} else if (pos === 3) {
return <label className="ml5 badge badge-primary">BODY</label>
return <span className="badge badge-primary">BODY</span>
} else {
return <label className="ml5 badge badge-secondary">QUERY</label>
return <span className="badge badge-secondary">QUERY</span>
}
}
const getFormattedValue = (itf: any) => {
if ((itf.type === 'Array' || itf.type === 'Object' || itf.type === 'String') && itf.value) {
try {
const formatted = JSON5.stringify(JSON5.parse(itf.value), undefined, 2)
return formatted
} catch (error) {
return itf.value || ''
}
} else {
return itf.value || ''
}
}
@ -91,10 +118,12 @@ class SortableTreeTableRow extends Component<any, any> {
}
<div className={`td payload name depth-${item.depth} nowrap`}>
{!editable
? <span className="nowrap">
{item.name}
{item.scope === 'request' && item.depth === 0 ? <PropertyLabel pos={item.pos} /> : null}
</span>
?
<>
<CopyToClipboard text={item.name}><span className="nowrap">{item.name}</span></CopyToClipboard>
{item.scope === 'request' && item.depth === 0 ?
<div style={{ float: 'right' }}><PropertyLabel pos={item.pos} /></div> : null}
</>
: <input
value={item.name}
onChange={e => handleChangePropertyField(item.id, 'name', e.target.value)}
@ -104,20 +133,27 @@ class SortableTreeTableRow extends Component<any, any> {
/>
}
</div>
<div className={`td payload type depth-${item.depth} nowrap`}>
{!editable
? <span className="nowrap">{item.required ? '✔️' : ''}</span>
: <input
type="checkbox"
checked={!!item.required}
onChange={e => handleChangePropertyField(item.id, 'required', e.target.checked)}
/>
}
<div className={`td payload required type depth-${item.depth} nowrap`}>
<Checkbox
checked={!!item.required}
disabled={!editable}
onChange={e =>
handleChangePropertyField(
item.id,
'required',
e.target.checked
)
}
color="primary"
inputProps={{
'aria-label': '必选',
}}
/>
</div>
<div className="td payload type">
{!editable
? <span className="nowrap">{item.type}</span>
? <CopyToClipboard text={item.type.toLowerCase()}><span className="nowrap">{item.type}</span></CopyToClipboard>
: <select
value={item.type}
onChange={e => handleChangePropertyField(item.id, 'type', e.target.value)}
@ -143,7 +179,7 @@ class SortableTreeTableRow extends Component<any, any> {
</div>
<div className="td payload value">
{!editable
? <span>{item.value}</span>
? <CopyToClipboard text={item.value}><span className="value-container">{getFormattedValue(item)}</span></CopyToClipboard>
: <SmartTextarea
value={item.value || ''}
onChange={(e: any) => handleChangePropertyField(item.id, 'value', e.target.value)}
@ -156,7 +192,7 @@ class SortableTreeTableRow extends Component<any, any> {
</div>
<div className="td payload desc">
{!editable
? <span>{item.description}</span>
? <CopyToClipboard text={item.description}><span>{item.description}</span></CopyToClipboard>
: <SmartTextarea
value={item.description || ''}
onChange={(e: any) => handleChangePropertyField(item.id, 'description', e.target.value)}
@ -233,7 +269,7 @@ class PropertyList extends Component<any, any> {
<Button key={1} onClick={this.handleClickCreatePropertyButton}></Button>,
<Button key={2} onClick={this.handleClickImporterButton}></Button>,
]}
<Button onClick={this.handleClickPreviewerButton}>
<Button className={this.state.previewer ? 'checked-button' : ''} onClick={this.handleClickPreviewerButton}>
</Button>
</ButtonGroup>
@ -247,6 +283,7 @@ class PropertyList extends Component<any, any> {
handleDeleteMemoryProperty={this.handleDeleteMemoryProperty}
handleChangePropertyField={this.handleChangePropertyField}
handleSortProperties={this.handleSortProperties}
handleClickCreatePropertyButton={this.handleClickCreatePropertyButton}
/>
</div>
<div className="footer">

@ -15,11 +15,33 @@
a, .fake-link
margin-right: 1rem;
> .desc
margin-top: .5rem;
margin: 1rem 0 .5rem;
color: #666;
> .body
// padding: 0 2rem;
.RelatedProjects
display: flex;
flex-wrap: wrap;
align-items: stretch;
margin: 1rem 0 -1rem;
color: #888;
> .Project
border: 1px solid #E6E6E6;
background: #FFF;
padding: 0 1rem;
margin-bottom: 1rem;
margin-right: 1rem;
line-height: 2.44;
.title
margin-right: .7rem;
svg
transform: scale(1.2) translateY(-1px);
i
color: #888;
font-style: normal;
margin-left: .5rem
.RepositorySearcher.dropdown
position: absolute;
top: 2rem;
@ -101,7 +123,7 @@
border-bottom-color: transparent;
background-color: white;
cursor: default;
border-color: #e36209 #e1e4e8 transparent #e1e4e8;
border-color: #3f51b5 #e1e4e8 transparent #e1e4e8;
&.active:hover
background-color: white;
> .Module
@ -133,35 +155,50 @@
background-color: #FFFFFF;
padding: 2rem;
.InterfaceList
flex-basis: 16rem;
width: 23rem;
flex-shrink: 0;
.InterfaceEditor
overflow-x: hidden;
padding: 0 1px;
flex-grow: 1;
.InterfaceList
.header
margin-bottom: 1rem;
ul.body
margin: 0;
padding: 0;
border: 1px solid #d1d5da;
border: 1px solid rgba(63, 81, 181, 0.5);
border-radius: .4rem;
list-style: none;
max-height: 150vh;
overflow-y: auto;
> li
position: relative;
padding: 1rem 1rem;
border-bottom: 1px solid #d1d5da;
border-bottom: 1px solid rgba(63, 81, 181, 0.5);
&:first-child
border-top-left-radius: .3rem;
&:last-child
border-bottom: 0;
border-bottom-left-radius: .3rem;
.Interface
position: relative;
padding-right: 4rem;
.name
position: relative;
float: left;
// width: 10rem;
max-width: 30rem;
font-size: 1.3rem;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
.url
font-size: 1rem;
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: rgba(0, 0, 0, 0.54);
.toolbar
display: none;
position: absolute;
@ -188,16 +225,15 @@
.toolbar
display: block;
> li.active
&::before
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 2px;
content: "";
background-color: #e36209;
.footer
padding: 0.75rem 1rem;
border-left: 2px solid #3f51b5;
// &::before
// position: absolute;
// top: 0;
// bottom: 0;
// left: 0;
// width: 2px;
// content: "";
// background-color: #3f51b5;
.InterfaceEditor
margin-left: 2rem;
@ -223,7 +259,6 @@
.InterfaceSummary
margin-bottom: 2rem;
padding-right: 13rem;
> .header
margin-bottom: .5rem;
> .title
@ -253,6 +288,8 @@
margin-right: 1rem;
> .toolbar
float: right;
.checked-button
background-color: rgba(63, 81, 181, 0.17)
.preview
margin: 0;
input
@ -261,14 +298,20 @@
margin-bottom: 1rem;
> .footer
> .Previewer
display: flex;
justify-content: space-between;
flex-wrap: wrap;
margin-top: 1rem;
> .result-template,
> .result-mocked
width: 49%;
> .header
margin-bottom: .5rem;
.title
margin-right: .75rem
margin-right: .75rem;
> pre.body
max-height: 90vh;
overflow: auto;
> .result-valid
padding-top: 2.5rem;
text-align: center;
@ -334,10 +377,21 @@
height: auto;
line-height: 1.5;
&.payload.name
justify-content: space-between
@for $i from 0 through 42
&.depth-#{$i}
padding-left: $i * 1rem + 0.75rem;
&.payload.value
max-height: 30vh;
overflow: auto;
hyphens: auto;
overflow-wrap: break-word;
span.value-container
white-space: pre;
margin: auto 0;
&.payload.required
padding: 0;
&.payload.desc
word-break: break-word;
.SortableTreeTable.editable
.flex-row
@ -348,6 +402,9 @@
@for $i from 0 through 10
&.depth-#{$i}
padding-left: $i * 1rem;
&.payload.value
max-height: unset;
overflow-wrap: break-word;
input.editable,
select.editable,
textarea.editable

@ -12,7 +12,7 @@ import { addRepository, updateRepository, clearRepository, fetchRepository } fro
import { addModule, updateModule, deleteModule, sortModuleList } from '../../actions/module'
import { addInterface, updateInterface, deleteInterface, lockInterface, unlockInterface, sortInterfaceList } from '../../actions/interface'
import { addProperty, updateProperty, deleteProperty, updateProperties, sortPropertyList } from '../../actions/property'
import { GoRepo, GoPencil, GoPlug, GoDatabase, GoJersey, GoLinkExternal } from 'react-icons/go'
import { GoRepo, GoPencil, GoVersions, GoPlug, GoDatabase, GoJersey, GoLinkExternal } from 'react-icons/go'
import './RepositoryEditor.css'
import ExportPostmanForm from '../repository/ExportPostmanForm'
@ -71,13 +71,13 @@ class RepositoryEditor extends Component<any, any> {
render() {
const { location: { params }, auth } = this.props
let { repository } = this.props
if (!repository.fetching && !repository.data) { return <div className="p100 fontsize-40 text-center">404</div> }
if (!repository.fetching && !repository.data) { return <div className="p100 fontsize-30 text-center"></div> }
if (repository.fetching || !repository.data || !repository.data.id) { return <Spin /> }
repository = repository.data
if (repository.name) {
document.title = `RAP2 ${repository.name}`
}
if (!repository.id) { return <Spin /> } // // DONE 2.2 每次获取仓库都显示加载动画不合理,应该只在初始加载时显示动画。
const mod = repository && repository.modules && repository.modules.length
? (repository.modules.find((item: any) => item.id === +params.mod) || repository.modules[0]) : {}
@ -125,6 +125,7 @@ class RepositoryEditor extends Component<any, any> {
</div>
<RepositorySearcher repository={repository} />
<div className="desc">{repository.description}</div>
{this.renderRelatedProjects()}
<DuplicatedInterfacesWarning repository={repository} />
</div>
<div className="body">
@ -137,6 +138,30 @@ class RepositoryEditor extends Component<any, any> {
</article>
)
}
renderRelatedProjects() {
const { repository } = this.props
const { collaborators } = repository.data
return (
<div className="RelatedProjects">
{collaborators &&
Array.isArray(collaborators) &&
collaborators.map(collab => (
<div
className="CollabProject Project"
key={`collab-${collab.id}`}
>
<span className="title">
<GoVersions className="mr5" />
</span>
<Link to={`/repository/editor?id=${collab.id}`}>
{collab.name}
</Link>
</div>
))}
</div>
)
}
handleUpdate = () => {
const { pathname, hash, search } = this.props.router.location
this.props.replace(pathname + search + hash)

@ -33,6 +33,9 @@ const useStyles = makeStyles((theme: Theme) =>
toolbar: {
display: 'flex',
justifyContent: 'space-between',
'& :not(.logo)': {
fontSize: '1.4rem',
},
},
logo: {
marginRight: theme.spacing(2),
@ -56,7 +59,7 @@ export default function MainMenu(props: Props) {
<Toolbar className={classes.toolbar}>
<div>
<Link to="/" className={classes.logo}><Logo /> </Link>
<Link to="/" className={classes.link}><Button color="inherit"> </Button></Link>
<Link to="/" className={classes.link}><Button color="inherit"> </Button></Link>
<Link to="/repository/joined" className={classes.link}><Button color="inherit"> </Button></Link>
<Link to="/organization/joined" className={classes.link}><Button color="inherit"> </Button></Link>
<Link to="/api" className={classes.link}><Button color="inherit"> </Button></Link>

@ -0,0 +1,3 @@
.rc-tooltip-content .rc-tooltip-inner
min-height: unset
padding: 0

@ -0,0 +1,65 @@
import copy from 'clipboard-copy'
import * as React from 'react'
import Tooltip from 'rc-tooltip'
import { withSnackbar, WithSnackbarProps } from 'notistack'
import 'rc-tooltip/assets/bootstrap.css'
import './CopyToClipboard.sass'
type Props = {
children: React.ReactElement<any>
text: string
} & WithSnackbarProps
interface OwnState {
showTooltip: boolean
}
class CopyToClipboard extends React.Component<Props, OwnState> {
public state: OwnState = { showTooltip: false }
public render() {
return (
<Tooltip
placement="right"
overlay={
<div
style={{ cursor: 'pointer', color: '#fff', padding: '8px 10px' }}
onClick={() => this.onCopy(this.props.text)}
>
</div>
}
mouseLeaveDelay={0.4}
mouseEnterDelay={0.4}
visible={this.state.showTooltip}
onVisibleChange={this.handleVisibleChange}
>
{this.props.children}
</Tooltip>
)
}
private onCopy = (content: string) => {
copy(content).then(() => {
const maxLength = 30
const cutContent = content.length > maxLength ? content.substr(0, maxLength) + '...' : content
this.props.enqueueSnackbar(`成功复制 ${cutContent} 到剪贴板`, {
variant: 'success',
autoHideDuration: 1000,
})
}).catch(() => {
this.props.enqueueSnackbar(`复制失败`, {
variant: 'error',
autoHideDuration: 1000,
})
})
this.setState({ showTooltip: false })
};
private handleVisibleChange = (visible: boolean) => {
this.setState({showTooltip: visible})
}
}
export default withSnackbar<Props>(CopyToClipboard)

@ -11,14 +11,15 @@ class RSortable extends Component<RSortableProps, RSortableState> {
return this.props.children
}
componentDidMount() {
const { onChange } = this.props
// onChange 不能传入 sortable会有冲突
const { onChange, ...restProps } = this.props
const $sortable = Sortable.create(findDOMNode(this) as any, {
handle: '.sortable',
animation: 150,
onEnd: (e: any) => {
if (onChange) { onChange(e, $sortable) }
},
...this.props,
...restProps,
})
this.$sortable = $sortable
}

@ -11,6 +11,7 @@ import Tree from './Tree'
import RSortable from './RSortable'
import RParsley from './RParsley'
import RChart from './RChart'
import CopyToClipboard from './CopyToClipboard'
export {
NoMatch,
@ -25,5 +26,6 @@ export {
Tree,
RSortable,
RParsley,
RChart
RChart,
CopyToClipboard
}

@ -5,6 +5,7 @@ import { Provider } from 'react-redux'
import { History } from 'history'
import { ConnectedRouter } from 'connected-react-router'
import MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider'
import { SnackbarProvider } from 'notistack'
import { withStyles } from '@material-ui/core'
import GlobalStyles from '../components/common/GlobalStyles'
import ThemeProvider from '@material-ui/core/styles/MuiThemeProvider'
@ -28,11 +29,13 @@ const start = (
return (
<ThemeProvider theme={MuiTheme}>
<MuiThemeProvider theme={MuiTheme}>
<Provider store={store}>
<ConnectedRouter history={history}>
<Routes/>
</ConnectedRouter>
</Provider>
<SnackbarProvider maxSnack={3}>
<Provider store={store}>
<ConnectedRouter history={history}>
<Routes/>
</ConnectedRouter>
</Provider>
</SnackbarProvider>
</MuiThemeProvider>
</ThemeProvider>
)

@ -172,6 +172,42 @@ export default {
),
},
}
case 'INTERFACE_LIST_SORT_SUCCEEDED':
modules = state.data.modules
const iftIds = action.ids
const itfIdsMap: any = {}
iftIds.forEach((id: number, index: number) => {
itfIdsMap[id] = index
})
const moduleId = action.moduleId
return {
...state,
data: {
...state.data,
modules: modules.map((mod: any) =>
mod.id === moduleId
? {
...mod,
interfaces: [...mod.interfaces].sort((a: any, b: any) => itfIdsMap[a.id] - itfIdsMap[b.id]),
}
: mod
),
},
}
case 'MODULE_LIST_SORT_SUCCEEDED':
modules = state.data.modules
const moduleIds = action.ids
const moduleIdsMap: any = {}
moduleIds.forEach((id: number, index: number) => {
moduleIdsMap[id] = index
})
return {
...state,
data: {
...state.data,
modules: [...modules].sort((a: any, b: any) => moduleIdsMap[a.id] - moduleIdsMap[b.id]),
},
}
default:
return state
}
@ -406,6 +442,8 @@ export default {
RepositoryEffects.updateRepository,
[RepositoryAction.fetchRepository({ id: undefined, repository: undefined })
.type]: RepositoryEffects.fetchRepository,
REPOSITORY_LOCATION_CHANGE:
RepositoryEffects.handleRepositoryLocationChange,
[RepositoryAction.fetchRepositoryCount().type]:
RepositoryEffects.fetchRepositoryCount,
[RepositoryAction.fetchRepositoryList().type]:
@ -420,18 +458,33 @@ export default {
ModuleEffects.sortModuleList,
[InterfaceAction.fetchInterfaceCount().type]:
InterfaceEffects.fetchInterfaceCount,
[InterfaceAction.sortInterfaceList(undefined, undefined).type]:
INTERFACE_LIST_SORT:
InterfaceEffects.sortInterfaceList,
[PropertyAction.sortPropertyList(undefined, undefined).type]:
PropertyEffects.sortPropertyList,
},
listeners: {
'/repository': [RepositoryAction.fetchOwnedRepositoryList, RepositoryAction.fetchJoinedRepositoryList],
'/repository/joined': [RepositoryAction.fetchOwnedRepositoryList, RepositoryAction.fetchJoinedRepositoryList],
'/repository': [
RepositoryAction.fetchOwnedRepositoryList,
RepositoryAction.fetchJoinedRepositoryList,
],
'/repository/joined': [
RepositoryAction.fetchOwnedRepositoryList,
RepositoryAction.fetchJoinedRepositoryList,
],
'/repository/editor': [
// REPOSITORY_LOCATION_CHANGE 判断了如果是当前 repo 的模块或接口切换就不重新获取
// 如果 repo 的 id 发生变化再进行 repo 的重新拉取
RepositoryAction.repositoryLocationChange,
],
'/organization/repository/editor': [
// REPOSITORY_LOCATION_CHANGE 判断了如果是当前 repo 的模块或接口切换就不重新获取
// 如果 repo 的 id 发生变化再进行 repo 的重新拉取
RepositoryAction.repositoryLocationChange,
],
'/repository/all': [RepositoryAction.fetchRepositoryList],
'/repository/tester': [RepositoryAction.fetchRepository],
'/repository/checker': [RepositoryAction.fetchRepository],
'/organization/repository': [RepositoryAction.fetchRepositoryList],
'/organization/repository/editor': [RepositoryAction.fetchRepository],
},
}

@ -94,7 +94,7 @@ export function* unlockInterface(action: any) {
export function* sortInterfaceList(action: any) {
try {
const count = yield call(EditorService.sortInterfaceList, action.ids)
yield put(InterfaceAction.sortInterfaceListSucceeded(count))
yield put(InterfaceAction.sortInterfaceListSucceeded(count, action.ids, action.moduleId))
if (action.onResolved) { action.onResolved() }
} catch (e) {
console.error(e.message)

@ -63,7 +63,7 @@ export function* deleteModule(action: any) {
export function* sortModuleList(action: any) {
try {
const count = yield call(EditorService.sortModuleList, action.ids)
yield put(ModuleAction.sortModuleListSucceeded(count))
yield put(ModuleAction.sortModuleListSucceeded(count, action.ids))
if (action.onResolved) { action.onResolved() }
} catch (e) {
console.error(e.message)

@ -1,6 +1,7 @@
import { call, put } from 'redux-saga/effects'
import { call, put, select } from 'redux-saga/effects'
import * as RepositoryAction from '../../actions/repository'
import RepositoryService from '../services/Repository'
import { RootState } from 'actions/types'
//
export function* fetchRepositoryCount(action: any) {
@ -76,6 +77,13 @@ export function* fetchRepository(action: any) {
}
}
export function* handleRepositoryLocationChange(action: any) {
const repositoryId = yield select((state: RootState) => state.repository && state.repository.data && state.repository.data.id)
if (Number(action.id) !== repositoryId) {
yield put(RepositoryAction.fetchRepository(action))
}
}
export function* fetchOwnedRepositoryList(action: any) {
try {
const repositories = yield call(RepositoryService.fetchOwnedRepositoryList, action)

@ -1,5 +1,7 @@
import React from 'react'
import { Bundle, Switch, Route } from './family'
import React, { lazy, Suspense } from 'react'
import { Switch, Route } from './family'
import { NoMatch, Spin } from './components/utils'
import Header from './components/common/Header'
import Footer from './components/common/Footer'
@ -10,77 +12,39 @@ import Message from 'components/common/Message'
import { useSelector } from 'react-redux'
import { RootState } from 'actions/types'
const UserList = (props: any) => (
<Bundle load={(cb: any) => import('./components/account/UserList').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} /> : null}
</Bundle>
)
const JoinedRepositoryList = (props: any) => (
<Bundle load={(cb: any) => import('./components/repository/JoinedRepositoryList').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} /> : null}
</Bundle>
)
const JoinedRepositoryListWithCreateForm = (props: any) => (
<Bundle load={(cb: any) => import('./components/repository/JoinedRepositoryList').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} create={true} /> : null}
</Bundle>
)
const AllRepositoryList = (props: any) => (
<Bundle load={(cb: any) => import('./components/repository/AllRepositoryList').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} /> : null}
</Bundle>
)
const RepositoryEditor = (props: any) => (
<Bundle load={(cb: any) => import('./components/editor/RepositoryEditor').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} /> : null}
</Bundle>
)
const RepositoryTester = (props: any) => (
<Bundle load={(cb: any) => import('./components/tester/Tester').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} /> : null}
</Bundle>
)
const RepositoryChecker = (props: any) => (
<Bundle load={(cb: any) => import('./components/checker/Checker').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} /> : null}
</Bundle>
)
const JoinedOrganizationList = (props: any) => (
<Bundle load={(cb: any) => import('./components/organization/JoinedOrganizationList').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} /> : null}
</Bundle>
)
const AllOrganizationList = (props: any) => (
<Bundle load={(cb: any) => import('./components/organization/AllOrganizationList').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} /> : null}
</Bundle>
)
const OrganizationRepositoryList = (props: any) => (
<Bundle load={(cb: any) => import('./components/organization/OrganizationRepositoryList').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} /> : null}
</Bundle>
)
const Status = (props: any) => (
<Bundle load={(cb: any) => import('./components/status/Status').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} /> : null}
</Bundle>
)
const API = (props: any) => (
<Bundle load={(cb: any) => import('./components/api/API').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} /> : null}
</Bundle>
)
// import Utils from './components/utils/Utils'
const Utils = (props: any) => (
<Bundle load={(cb: any) => import('./components/utils/Utils').then(comp => cb(comp))}>
{(CustomComponent: any) => CustomComponent ? <CustomComponent {...props} /> : null}
</Bundle>
)
const UserList = lazy(() => import(/* webpackChunkName: "./components/account/UserList" */ './components/account/UserList'))
const JoinedRepositoryList =
lazy(() => import(/* webpackChunkName: "./components/repository/JoinedRepositoryList" */ './components/repository/JoinedRepositoryList'))
const JoinedRepositoryListWithCreateForm =
lazy(() => import(/* webpackChunkName: "./components/repository/JoinedRepositoryList" */ './components/repository/JoinedRepositoryList'))
const AllRepositoryList =
lazy(() => import(/* webpackChunkName: "./components/repository/AllRepositoryList" */ './components/repository/AllRepositoryList'))
const RepositoryEditor =
lazy(() => import(/* webpackChunkName: "./components/repository/RepositoryEditor" */ './components/editor/RepositoryEditor'))
const RepositoryTester = lazy(() => import(/* webpackChunkName: "./components/tester/Tester" */ './components/tester/Tester'))
const RepositoryChecker = lazy(() => import(/* webpackChunkName: "./components/checker/Checker" */ './components/checker/Checker'))
const JoinedOrganizationList =
lazy(() => import(/* webpackChunkName: "./components/organization/JoinedOrganizationList" */ './components/organization/JoinedOrganizationList'))
const AllOrganizationList =
lazy(() => import(/* webpackChunkName: "./components/organization/AllOrganizationList" */ './components/organization/AllOrganizationList'))
const OrganizationRepositoryList =
lazy(() =>
import(/* webpackChunkName: "./components/organization/OrganizationRepositoryList" */ './components/organization/OrganizationRepositoryList'))
const Status = lazy(() => import(/* webpackChunkName: "./components/status/Status" */ './components/status/Status'))
const API = lazy(() => import(/* webpackChunkName: "./components/api/API */ './components/api/API'))
const Utils = lazy(() => import(/* webpackChunkName: "./components/utils/Utils" */ './components/utils/Utils'))
const Routes = () => {
const auth = useSelector((state: RootState) => state.auth)
@ -103,6 +67,7 @@ const Routes = () => {
<div className="btn-top" onClick={() => { console.log('hahaha'); window.scrollTo(0, 0) }}> </div>
<Route component={Header} />
<div className="body">
<Suspense fallback={<Spin/>}>
<Switch>
<Route exact={true} path="/" component={Home} />
<Route
@ -173,6 +138,7 @@ const Routes = () => {
/>
<Route component={NoMatch} />
</Switch>
</Suspense>
</div>
<Footer />
<div id="portal" />

Loading…
Cancel
Save