mirror of
https://gitee.com/dcren/cloud-native-app-initializer.git
synced 2026-08-25 19:06:31 +08:00
Project initial
This commit is contained in:
346
initializer-page/src/components/Application.js
Normal file
346
initializer-page/src/components/Application.js
Normal file
@@ -0,0 +1,346 @@
|
||||
import BodyClassName from 'react-body-classname'
|
||||
import get from 'lodash.get'
|
||||
import React, {lazy, Suspense, useContext, useEffect, useRef, useState,} from 'react'
|
||||
import {toast} from 'react-toastify'
|
||||
|
||||
import useHash from './utils/Hash'
|
||||
import useWindowsUtils from './utils/WindowsUtils'
|
||||
import {AppContext} from './reducer/App'
|
||||
import {Button, Form, RadioGroup} from './common/form'
|
||||
import {
|
||||
Control,
|
||||
FieldError,
|
||||
FieldInput,
|
||||
FieldRadio,
|
||||
List,
|
||||
Loading,
|
||||
PanelMore,
|
||||
QuickSearch,
|
||||
Tabs,
|
||||
Warnings,
|
||||
} from './common/builder'
|
||||
import {Footer, Layout} from './common/layout'
|
||||
import {InitializrContext} from './reducer/Initializr'
|
||||
import {getConfig, getInfo, getProject, getQueryString} from './utils/ApiUtils'
|
||||
|
||||
const Explore = lazy(() => import('./common/explore/Explore.js'))
|
||||
const Share = lazy(() => import('./common/share/Share.js'))
|
||||
const Fetch = lazy(() => import('./common/fetch/Fetch.js'))
|
||||
const HotKeys = lazy(() => import('./common/builder/HotKeys.js'))
|
||||
|
||||
export default function Application() {
|
||||
const {
|
||||
complete,
|
||||
config,
|
||||
more,
|
||||
tab,
|
||||
dispatch,
|
||||
theme,
|
||||
fetch: fetchOpen,
|
||||
share: shareOpen,
|
||||
explore: exploreOpen,
|
||||
dependencies,
|
||||
} = useContext(AppContext)
|
||||
const { values, share, dispatch: dispatchInitializr, errors } = useContext(InitializrContext)
|
||||
|
||||
const [positionShare, setPositionShare] = useState({ x: 0, y: 0 })
|
||||
|
||||
const [blob, setBlob] = useState(null)
|
||||
|
||||
|
||||
const inputMore = useRef(null)
|
||||
const inputQuickSearch = useRef(null)
|
||||
|
||||
const windowsUtils = useWindowsUtils()
|
||||
useHash()
|
||||
|
||||
useEffect(() => {
|
||||
if (windowsUtils.origin) {
|
||||
const url = `${windowsUtils.origin}/metadata/client`
|
||||
getInfo(url).then(jsonConfig => {
|
||||
const response = getConfig(jsonConfig)
|
||||
dispatchInitializr({ type: 'COMPLETE', payload: { ...response } })
|
||||
dispatch({ type: 'COMPLETE', payload: response })
|
||||
})
|
||||
}
|
||||
}, [dispatch, dispatchInitializr, windowsUtils.origin])
|
||||
|
||||
const onSubmit = async () => {
|
||||
dispatch({ type: 'FETCH_UPDATE', payload: { open: true } })
|
||||
}
|
||||
|
||||
const onExplore = async () => {
|
||||
const url = `${windowsUtils.origin}/starter.zip`
|
||||
dispatch({ type: 'EXPLORE_UPDATE', payload: { open: true } })
|
||||
const project = await getProject(
|
||||
url,
|
||||
values,
|
||||
get(dependencies, 'list')
|
||||
).catch(() => {
|
||||
toast.error(`Could not connect to server. Please check your network.`)
|
||||
})
|
||||
setBlob(project)
|
||||
}
|
||||
|
||||
const update = args => {
|
||||
dispatchInitializr({ type: 'UPDATE', payload: args })
|
||||
}
|
||||
|
||||
const onRunInSandbox = () => {
|
||||
const origin = `${windowsUtils.origin}`
|
||||
const config = get(dependencies, 'list')
|
||||
const params = getQueryString(values, config, ["cloudshell"])
|
||||
|
||||
const gitUrl = `${origin}/${params}/${values.meta.artifact}.git`
|
||||
|
||||
const handsOnUrl = `${origin}/handson?${params}`
|
||||
|
||||
window.open(handsOnUrl)
|
||||
|
||||
}
|
||||
|
||||
const onExport = () => {
|
||||
const origin = `${windowsUtils.origin}`
|
||||
const config = get(dependencies, 'list')
|
||||
const params = getQueryString(values, config)
|
||||
|
||||
const codeupUrl = `${origin}/codeup?${params}`
|
||||
|
||||
window.open(codeupUrl)
|
||||
}
|
||||
|
||||
let shareSrc = get(values, 'share')
|
||||
if(shareSrc){
|
||||
shareSrc = `${shareSrc}/`
|
||||
} else {
|
||||
shareSrc = ''
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<BodyClassName className={theme} />
|
||||
<Suspense fallback=''>
|
||||
<HotKeys onSubmit={onSubmit} onExplore={onExplore} />
|
||||
</Suspense>
|
||||
<Form onSubmit={onSubmit}>
|
||||
{!complete ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<>
|
||||
<Warnings />
|
||||
<Control text='项目构建方式'>
|
||||
<RadioGroup
|
||||
name='project'
|
||||
selected={get(values, 'project')}
|
||||
options={get(config, 'lists.project')}
|
||||
onChange={value => {
|
||||
update({ project: value })
|
||||
}}
|
||||
/>
|
||||
</Control>
|
||||
<Control text='开发语言'>
|
||||
<RadioGroup
|
||||
name='language'
|
||||
selected={get(values, 'language')}
|
||||
options={get(config, 'lists.language')}
|
||||
onChange={value => {
|
||||
update({ language: value })
|
||||
}}
|
||||
/>
|
||||
</Control>
|
||||
<Control text='Spring Boot版本'>
|
||||
<RadioGroup
|
||||
name='boot'
|
||||
selected={get(values, 'boot')}
|
||||
error={get(errors, 'boot.value', '')}
|
||||
options={get(config, 'lists.boot')}
|
||||
onChange={value => {
|
||||
dispatchInitializr({
|
||||
type: 'UPDATE',
|
||||
payload: { boot: value },
|
||||
config: get(dependencies, 'list'),
|
||||
})
|
||||
dispatch({
|
||||
type: 'UPDATE_DEPENDENCIES',
|
||||
payload: { boot: value },
|
||||
})
|
||||
}}
|
||||
/>
|
||||
{get(errors, 'boot') && (
|
||||
<FieldError>
|
||||
Spring Boot {get(errors, 'boot.value')} is not supported.
|
||||
Please select a valid version
|
||||
</FieldError>
|
||||
)}
|
||||
</Control>
|
||||
<Control text='项目基本信息' variant='md'>
|
||||
<FieldInput
|
||||
id='input-group'
|
||||
value={get(values, 'meta.group')}
|
||||
text='Group'
|
||||
onChange={event => {
|
||||
update({ meta: { group: event.target.value } })
|
||||
}}
|
||||
/>
|
||||
<FieldInput
|
||||
id='input-artifact'
|
||||
value={get(values, 'meta.artifact')}
|
||||
text='Artifact'
|
||||
onChange={event => {
|
||||
update({ meta: { artifact: event.target.value } })
|
||||
}}
|
||||
/>
|
||||
<PanelMore fieldFocusOnOpen={inputMore}>
|
||||
<FieldInput
|
||||
id='input-name'
|
||||
value={get(values, 'meta.name')}
|
||||
text='项目名称'
|
||||
disabled={!more}
|
||||
inputRef={inputMore}
|
||||
onChange={event => {
|
||||
update({ meta: { name: event.target.value } })
|
||||
}}
|
||||
/>
|
||||
<FieldInput
|
||||
id='input-description'
|
||||
value={get(values, 'meta.description')}
|
||||
text='项目描述'
|
||||
disabled={!more}
|
||||
onChange={event => {
|
||||
update({ meta: { description: event.target.value } })
|
||||
}}
|
||||
/>
|
||||
<FieldInput
|
||||
id='input-packageName'
|
||||
value={get(values, 'meta.packageName')}
|
||||
text='基础包'
|
||||
disabled={!more}
|
||||
onChange={event => {
|
||||
update({ meta: { packageName: event.target.value } })
|
||||
}}
|
||||
/>
|
||||
<FieldRadio
|
||||
id='input-packaging'
|
||||
value={get(values, 'meta.packaging')}
|
||||
text='打包方式'
|
||||
disabled={!more}
|
||||
options={get(config, 'lists.meta.packaging')}
|
||||
onChange={value => {
|
||||
update({ meta: { packaging: value } })
|
||||
}}
|
||||
/>
|
||||
<FieldRadio
|
||||
id='input-java'
|
||||
value={get(values, 'meta.java')}
|
||||
text='Java版本'
|
||||
disabled={!more}
|
||||
options={get(config, 'lists.meta.java')}
|
||||
onChange={value => {
|
||||
update({ meta: { java: value } })
|
||||
}}
|
||||
/>
|
||||
</PanelMore>
|
||||
</Control>
|
||||
<Control text='应用架构'>
|
||||
<RadioGroup
|
||||
name='architecture'
|
||||
selected={get(values, 'architecture')}
|
||||
options={get(config, 'lists.architecture')}
|
||||
onChange={value => {
|
||||
update({ architecture: value })
|
||||
|
||||
get(values, 'dependencies', []).forEach(item => {
|
||||
dispatchInitializr({
|
||||
type: 'REMOVE_DEPENDENCY',
|
||||
payload: { id:item },
|
||||
})
|
||||
})
|
||||
|
||||
get(config, 'lists.architecture', [])
|
||||
.filter(item => item.key === value)
|
||||
.forEach(arch => {
|
||||
get(arch, 'dependencies', []).forEach(dep => {
|
||||
dispatchInitializr({
|
||||
type: 'ADD_DEPENDENCY',
|
||||
payload: { id:dep },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
}}
|
||||
/>
|
||||
</Control>
|
||||
<Control text='组件依赖' variant={tab === 'quicksearch' ? 'xl' : 'xxl'}>
|
||||
<Tabs
|
||||
changeTab={newTab => {
|
||||
if (
|
||||
newTab === 'quicksearch' &&
|
||||
get(inputQuickSearch, 'current')
|
||||
) {
|
||||
get(inputQuickSearch, 'current').focus()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{tab === 'quicksearch' && (
|
||||
<QuickSearch submit={onSubmit} input={inputQuickSearch} />
|
||||
)}
|
||||
{tab === 'list' && <List />}
|
||||
</Control>
|
||||
<Footer>
|
||||
<Button
|
||||
id='generate-project'
|
||||
variant='primary'
|
||||
onClick={onSubmit}
|
||||
hotkey={`${windowsUtils.symb} + ⏎`}
|
||||
>
|
||||
获取代码
|
||||
</Button>
|
||||
<Button
|
||||
id='explore-project'
|
||||
onClick={onExplore}
|
||||
hotkey='Ctrl + Space'
|
||||
>
|
||||
浏览代码
|
||||
</Button>
|
||||
</Footer>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
<Suspense fallback=''>
|
||||
<Share
|
||||
open={shareOpen || false}
|
||||
shareUrl={share}
|
||||
shareSrc={shareSrc}
|
||||
position={positionShare}
|
||||
onClose={() => {
|
||||
dispatch({
|
||||
type: 'SHARE_UPDATE',
|
||||
payload: { open: false },
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<Explore
|
||||
projectName={`${get(values, 'meta.artifact')}.zip`}
|
||||
blob={blob}
|
||||
open={exploreOpen || false}
|
||||
onClose={() => {
|
||||
dispatch({
|
||||
type: 'EXPLORE_UPDATE',
|
||||
payload: { open: false },
|
||||
})
|
||||
setBlob(null)
|
||||
}}
|
||||
/>
|
||||
<Fetch
|
||||
open={fetchOpen || false}
|
||||
onClose={() => {
|
||||
dispatch({
|
||||
type: 'FETCH_UPDATE',
|
||||
payload: { open: false },
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
43
initializer-page/src/components/common/builder/Control.js
Normal file
43
initializer-page/src/components/common/builder/Control.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
const Control = ({ text, children, variant }) => {
|
||||
if (variant === 'xl' || variant === 'xxl') {
|
||||
return (
|
||||
<div className='colset'>
|
||||
<div className='left'>
|
||||
<div className='sticky-label'>{text}</div>
|
||||
</div>
|
||||
<div
|
||||
className={`dependencies-box ${variant === 'xl' ? 'list' : 'large'}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='colset'>
|
||||
<div className='left'>
|
||||
<>{text}</>
|
||||
</div>
|
||||
<div className={`right ${variant === 'md' ? 'right-md' : ''}`}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Control.defaultProps = {
|
||||
children: null,
|
||||
variant: '',
|
||||
}
|
||||
|
||||
Control.propTypes = {
|
||||
children: PropTypes.node,
|
||||
variant: PropTypes.string,
|
||||
text: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export default Control
|
||||
18
initializer-page/src/components/common/builder/FieldError.js
Normal file
18
initializer-page/src/components/common/builder/FieldError.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
function FieldError({ children }) {
|
||||
return (
|
||||
<div className='control-error'>
|
||||
<p className='title'>
|
||||
<strong>{children}</strong>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
FieldError.propTypes = {
|
||||
children: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export default FieldError
|
||||
36
initializer-page/src/components/common/builder/FieldInput.js
Normal file
36
initializer-page/src/components/common/builder/FieldInput.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
const FieldInput = ({ id, text, value, onChange, disabled, inputRef }) => (
|
||||
<div className='control'>
|
||||
<label htmlFor={id}>{text}</label>
|
||||
<input
|
||||
type='text'
|
||||
id={id}
|
||||
className='control-input'
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
ref={inputRef}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
FieldInput.defaultProps = {
|
||||
disabled: false,
|
||||
inputRef: null,
|
||||
}
|
||||
|
||||
FieldInput.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
inputRef: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
||||
]),
|
||||
disabled: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default FieldInput
|
||||
38
initializer-page/src/components/common/builder/FieldRadio.js
Normal file
38
initializer-page/src/components/common/builder/FieldRadio.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
import {RadioGroup} from '../form'
|
||||
|
||||
const FieldRadio = ({ id, text, value, onChange, disabled, options }) => (
|
||||
<div className='control'>
|
||||
<label htmlFor={id}>{text}</label>
|
||||
<RadioGroup
|
||||
name='packaging'
|
||||
disabled={disabled}
|
||||
selected={value}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
FieldRadio.defaultProps = {
|
||||
disabled: false,
|
||||
options: [],
|
||||
}
|
||||
|
||||
FieldRadio.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
})
|
||||
),
|
||||
}
|
||||
|
||||
export default FieldRadio
|
||||
26
initializer-page/src/components/common/builder/HotKeys.js
Normal file
26
initializer-page/src/components/common/builder/HotKeys.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import {GlobalHotKeys} from 'react-hotkeys'
|
||||
|
||||
function HotKeys({ onSubmit, onExplore }) {
|
||||
return (
|
||||
<GlobalHotKeys
|
||||
keyMap={{
|
||||
SUBMIT: ['command+enter', 'ctrl+enter'],
|
||||
EXPLORE: ['ctrl+space'],
|
||||
}}
|
||||
handlers={{
|
||||
SUBMIT: onSubmit,
|
||||
EXPLORE: onExplore,
|
||||
}}
|
||||
global
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
HotKeys.propTypes = {
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
onExplore: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default HotKeys
|
||||
42
initializer-page/src/components/common/builder/Loading.js
Normal file
42
initializer-page/src/components/common/builder/Loading.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import React from 'react'
|
||||
|
||||
import Control from './Control'
|
||||
import {Footer} from '../layout'
|
||||
import {Placeholder} from '../form'
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<>
|
||||
<Control text='项目构建方式'>
|
||||
<Placeholder type='radios' count={2} width='133px' />
|
||||
</Control>
|
||||
<Control text='开发语言'>
|
||||
<Placeholder type='radios' count={3} width='73px' />
|
||||
</Control>
|
||||
<Control text='Spring Boot 版本'>
|
||||
<Placeholder type='radios' count={5} width='105px' />
|
||||
</Control>
|
||||
<Control text='项目元信息' special='md'>
|
||||
<div>
|
||||
<div className='control'>
|
||||
<Placeholder type='input' />
|
||||
</div>
|
||||
<div className='control'>
|
||||
<Placeholder type='input' />
|
||||
</div>
|
||||
<div className='control'>
|
||||
<Placeholder type='dropdown' />
|
||||
</div>
|
||||
</div>
|
||||
</Control>
|
||||
<Control text='组件依赖'>
|
||||
<Placeholder type='tabs' count={2} />
|
||||
</Control>
|
||||
<Footer>
|
||||
<Placeholder type='button' width='189px' />
|
||||
<Placeholder type='button' width='212px' />
|
||||
<Placeholder type='button' width='110px' />
|
||||
</Footer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
59
initializer-page/src/components/common/builder/PanelMore.js
Normal file
59
initializer-page/src/components/common/builder/PanelMore.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import get from 'lodash.get'
|
||||
import React, {useContext, useEffect} from 'react'
|
||||
|
||||
import {AppContext} from '../../reducer/App'
|
||||
import {IconChevronRight} from '../icons'
|
||||
|
||||
const PanelMore = ({ children, fieldFocusOnOpen }) => {
|
||||
const { more, dispatch } = useContext(AppContext)
|
||||
|
||||
useEffect(() => {
|
||||
if (more && fieldFocusOnOpen) {
|
||||
setTimeout(() => {
|
||||
get(fieldFocusOnOpen, 'current').focus()
|
||||
}, 300)
|
||||
}
|
||||
}, [more, fieldFocusOnOpen])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='more'>
|
||||
<div className='wrap'>
|
||||
<a
|
||||
href='/'
|
||||
onClick={event => {
|
||||
event.preventDefault()
|
||||
dispatch({
|
||||
type: 'UPDATE',
|
||||
payload: { more: !more },
|
||||
})
|
||||
}}
|
||||
className={more ? 'toggle' : ''}
|
||||
>
|
||||
<IconChevronRight />
|
||||
{!more ? '高级选项' : '高级选项'}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`panel ${more ? 'panel-active' : ''}`}>
|
||||
<div className='panel-wrap'>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
PanelMore.defaultProps = {
|
||||
children: null,
|
||||
fieldFocusOnOpen: null,
|
||||
}
|
||||
|
||||
PanelMore.propTypes = {
|
||||
children: PropTypes.node,
|
||||
fieldFocusOnOpen: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
||||
]),
|
||||
}
|
||||
|
||||
export default PanelMore
|
||||
73
initializer-page/src/components/common/builder/Tabs.js
Normal file
73
initializer-page/src/components/common/builder/Tabs.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import get from 'lodash.get'
|
||||
import React, {useContext, useEffect, useState} from 'react'
|
||||
|
||||
import {AppContext} from '../../reducer/App'
|
||||
import {IconList, IconSearch} from '../icons'
|
||||
import {InitializrContext} from '../../reducer/Initializr'
|
||||
|
||||
const Tabs = ({ changeTab }) => {
|
||||
const { dependencies: deps, dispatch, tab } = useContext(AppContext)
|
||||
const { values } = useContext(InitializrContext)
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
setCount(
|
||||
values.dependencies.filter(d => {
|
||||
return get(deps, 'list', []).find(a => a.id === d).valid
|
||||
}).length
|
||||
)
|
||||
}, [deps, values.dependencies])
|
||||
|
||||
const change = newTab => {
|
||||
dispatch({
|
||||
type: 'UPDATE',
|
||||
payload: { tab: newTab },
|
||||
})
|
||||
setTimeout(() => {
|
||||
changeTab(newTab)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='tab'>
|
||||
<div className='tab-container'>
|
||||
<a
|
||||
href='/'
|
||||
aria-label='Search'
|
||||
onClick={event => {
|
||||
event.preventDefault()
|
||||
change('quicksearch')
|
||||
}}
|
||||
className={`quick-search ${tab === 'quicksearch' ? 'active' : ''}`}
|
||||
>
|
||||
<IconSearch /><span>搜索</span>
|
||||
</a>
|
||||
<a
|
||||
href='/'
|
||||
aria-label='List'
|
||||
onClick={event => {
|
||||
event.preventDefault()
|
||||
change('list')
|
||||
}}
|
||||
className={`list ${tab === 'list' ? 'active' : ''}`}
|
||||
>
|
||||
<IconList /><span>分组列表</span>
|
||||
</a>
|
||||
{count > 0 && (
|
||||
<>
|
||||
<strong>
|
||||
<span>{count}</span> 项已选择
|
||||
</strong>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Tabs.propTypes = {
|
||||
changeTab: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default Tabs
|
||||
83
initializer-page/src/components/common/builder/Warnings.js
Normal file
83
initializer-page/src/components/common/builder/Warnings.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import get from 'lodash.get'
|
||||
import React, {useContext} from 'react'
|
||||
|
||||
import {IconTimes} from '../icons'
|
||||
import {InitializrContext} from '../../reducer/Initializr'
|
||||
|
||||
function Warnings() {
|
||||
const { warnings, dispatch } = useContext(InitializrContext)
|
||||
if (Object.keys(warnings).length > 0) {
|
||||
return (
|
||||
<div className='colset'>
|
||||
<div className='left' />
|
||||
<div className='right'>
|
||||
<div className='warnings'>
|
||||
<a
|
||||
className='close'
|
||||
href='/#'
|
||||
onClick={event => {
|
||||
event.preventDefault()
|
||||
dispatch({
|
||||
type: 'CLEAR_WARNINGS',
|
||||
})
|
||||
}}
|
||||
>
|
||||
<IconTimes />
|
||||
</a>
|
||||
The following attributes could not be handled:
|
||||
<ul>
|
||||
{get(warnings, 'project') && (
|
||||
<li>
|
||||
<strong>{get(warnings, 'project.value')}</strong> is not a
|
||||
valid project type,{' '}
|
||||
<strong>{get(warnings, 'project.select')}</strong> has been
|
||||
selected.
|
||||
</li>
|
||||
)}
|
||||
{get(warnings, 'language') && (
|
||||
<li>
|
||||
<strong>{get(warnings, 'language.value')}</strong> is not a
|
||||
valid language,{' '}
|
||||
<strong>{get(warnings, 'language.select')}</strong> has been
|
||||
selected.
|
||||
</li>
|
||||
)}
|
||||
{get(warnings, 'boot') && (
|
||||
<li>
|
||||
Spring Boot <strong>{get(warnings, 'boot.value')}</strong> is
|
||||
not available, <strong>{get(warnings, 'boot.select')}</strong>{' '}
|
||||
has been selected.
|
||||
</li>
|
||||
)}
|
||||
{get(warnings, 'meta.java') && (
|
||||
<li>
|
||||
<strong>{get(warnings, 'meta.java.value')}</strong> is not a
|
||||
valid Java version,{' '}
|
||||
<strong>{get(warnings, 'meta.java.select')}</strong> has been
|
||||
selected.
|
||||
</li>
|
||||
)}
|
||||
{get(warnings, 'meta.packaging') && (
|
||||
<li>
|
||||
<strong>{get(warnings, 'meta.packaging.value')}</strong> is
|
||||
not a valid packaging,{' '}
|
||||
<strong>{get(warnings, 'meta.packaging.select')}</strong> has
|
||||
been selected.
|
||||
</li>
|
||||
)}
|
||||
{get(warnings, 'dependencies') && (
|
||||
<li>
|
||||
The following dependencies are not supported:{' '}
|
||||
<strong>{get(warnings, 'dependencies.value')}</strong>.
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <></>
|
||||
}
|
||||
|
||||
export default Warnings
|
||||
10
initializer-page/src/components/common/builder/index.js
Normal file
10
initializer-page/src/components/common/builder/index.js
Normal file
@@ -0,0 +1,10 @@
|
||||
export { default as Control } from './Control'
|
||||
export { default as FieldInput } from './FieldInput'
|
||||
export { default as FieldRadio } from './FieldRadio'
|
||||
export { default as FieldError } from './FieldError'
|
||||
export { default as PanelMore } from './PanelMore'
|
||||
export { default as Tabs } from './Tabs'
|
||||
export { default as QuickSearch } from './quick-search/QuickSearch'
|
||||
export { default as List } from './list/List'
|
||||
export { default as Loading } from './Loading'
|
||||
export { default as Warnings } from './Warnings'
|
||||
47
initializer-page/src/components/common/builder/list/List.js
Normal file
47
initializer-page/src/components/common/builder/list/List.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import get from 'lodash.get'
|
||||
import React, {useContext} from 'react'
|
||||
|
||||
import ListGroup from './ListGroup'
|
||||
import {AppContext} from '../../../reducer/App'
|
||||
import {InitializrContext} from '../../../reducer/Initializr'
|
||||
|
||||
const List = () => {
|
||||
const { dependencies, dispatch, groupsOpened } = useContext(AppContext)
|
||||
const { values, dispatch: dispatchInitializr } = useContext(InitializrContext)
|
||||
return (
|
||||
<div className='groups'>
|
||||
{get(dependencies, 'groups').map(group => {
|
||||
return (
|
||||
<ListGroup
|
||||
group={group.group}
|
||||
key={group.group}
|
||||
dependencyGroup={group}
|
||||
add={id => {
|
||||
dispatchInitializr({
|
||||
type: 'ADD_DEPENDENCY',
|
||||
payload: { id },
|
||||
})
|
||||
}}
|
||||
remove={id => {
|
||||
dispatchInitializr({
|
||||
type: 'REMOVE_DEPENDENCY',
|
||||
payload: { id },
|
||||
})
|
||||
}}
|
||||
toggle={id => {
|
||||
dispatch({
|
||||
type: 'TOGGLE_GROUP',
|
||||
payload: { id },
|
||||
})
|
||||
}}
|
||||
itemsSelected={get(values, 'dependencies', [])}
|
||||
isClose={groupsOpened.indexOf(group.group) < 0}
|
||||
items={group.items}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default List
|
||||
@@ -0,0 +1,88 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
import ListItem from './ListItem'
|
||||
import {IconChevronRight} from '../../icons'
|
||||
|
||||
function ListGroup({
|
||||
group,
|
||||
items,
|
||||
itemsSelected,
|
||||
isClose,
|
||||
add,
|
||||
remove,
|
||||
toggle,
|
||||
}) {
|
||||
const toggleGroup = event => {
|
||||
event.preventDefault()
|
||||
toggle(group)
|
||||
}
|
||||
const onKeyDown = event => {
|
||||
const keyPressed = event.key
|
||||
if (keyPressed === 'Enter' || keyPressed === ' ') {
|
||||
toggleGroup(event)
|
||||
}
|
||||
}
|
||||
const isItemSelected = item => {
|
||||
return !!itemsSelected.find(o => o === item.id)
|
||||
}
|
||||
return (
|
||||
<div className='group'>
|
||||
<div className='group-title'>
|
||||
<a
|
||||
href='/'
|
||||
onClick={toggleGroup}
|
||||
className={!isClose ? 'toggleGroupItems' : ''}
|
||||
tabIndex={0}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
<IconChevronRight />
|
||||
{group}
|
||||
</a>
|
||||
</div>
|
||||
{!isClose && (
|
||||
<div className='group-items' key={`links${group}`}>
|
||||
{items.map(item => (
|
||||
<ListItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
name={item.name}
|
||||
description={item.description}
|
||||
valid={item.valid}
|
||||
message={item.message}
|
||||
selected={isItemSelected(item)}
|
||||
onChange={value => {
|
||||
if (value) {
|
||||
add(item.id)
|
||||
} else {
|
||||
remove(item.id)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ListGroup.propTypes = {
|
||||
group: PropTypes.string.isRequired,
|
||||
add: PropTypes.func.isRequired,
|
||||
remove: PropTypes.func.isRequired,
|
||||
toggle: PropTypes.func.isRequired,
|
||||
itemsSelected: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
isClose: PropTypes.bool.isRequired,
|
||||
items: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
description: PropTypes.string.isRequired,
|
||||
group: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
keywords: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
valid: PropTypes.bool.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
}
|
||||
|
||||
export default ListGroup
|
||||
@@ -0,0 +1,77 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
import {IconCheck, IconPlus, IconTimes} from '../../icons'
|
||||
|
||||
function ListItem({
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
valid,
|
||||
message,
|
||||
onChange,
|
||||
selected,
|
||||
}) {
|
||||
const toggle = event => {
|
||||
event.preventDefault()
|
||||
if (valid) {
|
||||
onChange(!selected)
|
||||
}
|
||||
}
|
||||
const onKeyDown = event => {
|
||||
const keyPressed = event.key
|
||||
if (keyPressed === 'Enter' || keyPressed === ' ') {
|
||||
toggle(event)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<a
|
||||
href='/'
|
||||
onClick={toggle}
|
||||
tabIndex={!valid ? -1 : ''}
|
||||
className={`${!valid ? 'invalid' : ''} ${selected ? 'checked' : ''}`}
|
||||
key={id}
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
<div key={`d${id}`}>
|
||||
<input
|
||||
type='checkbox'
|
||||
value={id}
|
||||
key={`ck${id}`}
|
||||
checked={selected}
|
||||
disabled={!valid}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
<strong key={`ck1${id}`}>{name}</strong>
|
||||
<br key={`br${id}`} />
|
||||
{valid && <span key={`ck2${id}`}>{description}</span>}
|
||||
<span key={`ck3${id}`} className='icon'>
|
||||
<IconPlus key={`ck4${id}`} />
|
||||
<IconTimes key={`ck5${id}`} />
|
||||
<IconCheck key={`ck6${id}`} />
|
||||
</span>
|
||||
{!valid && (
|
||||
<span className='warning' key={`warning${id}`}>
|
||||
{message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
ListItem.defaultProps = {
|
||||
message: '',
|
||||
}
|
||||
|
||||
ListItem.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
valid: PropTypes.bool.isRequired,
|
||||
message: PropTypes.string,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
selected: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
export default ListItem
|
||||
@@ -0,0 +1,75 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import {CSSTransition, TransitionGroup} from 'react-transition-group'
|
||||
|
||||
import {IconCheck, IconTimes} from '../../icons'
|
||||
|
||||
function Dependencies({ list, remove }) {
|
||||
return (
|
||||
<TransitionGroup
|
||||
component='ul'
|
||||
className='dependencies-list dependencies-list-checked'
|
||||
>
|
||||
{list.map(item => {
|
||||
return (
|
||||
<CSSTransition timeout={500} classNames='fade' key={`f${item.id}`}>
|
||||
<li>
|
||||
<span className='dependency-item-span'>
|
||||
<a
|
||||
className={`dependency-item checked ${
|
||||
!item.valid ? 'invalid' : ''
|
||||
}`}
|
||||
href='/'
|
||||
onClick={event => {
|
||||
event.preventDefault()
|
||||
remove(item)
|
||||
}}
|
||||
key={item.id}
|
||||
>
|
||||
<div key={`d1${item.id}`}>
|
||||
<strong key={`d2${item.id}`}>{item.name}</strong>
|
||||
<br key={`d3${item.id}`} />
|
||||
{item.valid && (
|
||||
<span key={`d4${item.id}`} className='description'>
|
||||
{item.description}
|
||||
</span>
|
||||
)}
|
||||
<span key={`d5${item.id}`} className='icon'>
|
||||
<IconTimes key={`d6${item.id}`} />
|
||||
<IconCheck key={`d7${item.id}`} />
|
||||
</span>
|
||||
{!item.valid && (
|
||||
<span className='warning' key={`warning${item.id}`}>
|
||||
{item.message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</li>
|
||||
</CSSTransition>
|
||||
)
|
||||
})}
|
||||
</TransitionGroup>
|
||||
)
|
||||
}
|
||||
|
||||
Dependencies.defaultProps = {
|
||||
list: [],
|
||||
}
|
||||
|
||||
Dependencies.propTypes = {
|
||||
list: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
group: PropTypes.string.isRequired,
|
||||
valid: PropTypes.bool.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
message: PropTypes.string,
|
||||
})
|
||||
),
|
||||
remove: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default Dependencies
|
||||
@@ -0,0 +1,189 @@
|
||||
import * as JsSearch from 'js-search'
|
||||
import PropTypes from 'prop-types'
|
||||
import get from 'lodash.get'
|
||||
import React, {useContext, useEffect, useState} from 'react'
|
||||
|
||||
import Dependencies from './Dependencies'
|
||||
import Result from './Result'
|
||||
import {AppContext} from '../../../reducer/App'
|
||||
import {InitializrContext} from '../../../reducer/Initializr'
|
||||
|
||||
const sortResult = dependencies => {
|
||||
return dependencies.sort((a, b) => {
|
||||
if (a.valid && !b.valid) {
|
||||
return -1
|
||||
}
|
||||
if (!a.valid && b.valid) {
|
||||
return 1
|
||||
}
|
||||
return b.weight - a.weight
|
||||
})
|
||||
}
|
||||
|
||||
const QuickSearch = ({ submit, input }) => {
|
||||
const { values, dispatch } = useContext(InitializrContext)
|
||||
const { dependencies: dependenciesContext } = useContext(AppContext)
|
||||
const [query, setQuery] = useState('')
|
||||
const [selected, setSelected] = useState(0)
|
||||
const [dependencies, setDependencies] = useState([])
|
||||
const [result, setResult] = useState([])
|
||||
const [count, setCount] = useState(0)
|
||||
const [search, setSearch] = useState(null)
|
||||
|
||||
const add = id => {
|
||||
dispatch({
|
||||
type: 'ADD_DEPENDENCY',
|
||||
payload: { id },
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const newDeps = get(values, 'dependencies', []).map(item => {
|
||||
return get(dependenciesContext, 'list', []).find(d => d.id === item)
|
||||
})
|
||||
setDependencies(newDeps)
|
||||
const jsSearchUp = new JsSearch.Search('name')
|
||||
jsSearchUp.addIndex('name')
|
||||
jsSearchUp.addIndex('id')
|
||||
jsSearchUp.addIndex('description')
|
||||
jsSearchUp.addIndex('group')
|
||||
jsSearchUp.addDocuments(get(dependenciesContext, 'list'))
|
||||
setSearch(jsSearchUp)
|
||||
}, [values, dependenciesContext, values.dependencies])
|
||||
|
||||
const onFocus = () => {
|
||||
setSelected(0)
|
||||
}
|
||||
|
||||
const onBlur = () => {
|
||||
setSelected(-1)
|
||||
}
|
||||
|
||||
const onKeyDown = event => {
|
||||
switch (event.keyCode) {
|
||||
case 40: // Down
|
||||
event.preventDefault()
|
||||
setSelected(Math.min(selected + 1, result.length - 1, 4))
|
||||
break
|
||||
case 38: // Up
|
||||
event.preventDefault()
|
||||
setSelected(Math.max(selected - 1, 0))
|
||||
break
|
||||
case 13: // Enter
|
||||
event.preventDefault()
|
||||
if (result.length > 0) {
|
||||
add(result[selected].id)
|
||||
setQuery('')
|
||||
} else {
|
||||
submit()
|
||||
}
|
||||
break
|
||||
case 27: // Escape
|
||||
event.preventDefault()
|
||||
setQuery('')
|
||||
break
|
||||
case 39: // Right
|
||||
case 37: // Left
|
||||
break
|
||||
default:
|
||||
// Default
|
||||
setSelected(0)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const onSearch = () => {
|
||||
if (!search) {
|
||||
return
|
||||
}
|
||||
let vals = search
|
||||
.search(query)
|
||||
.filter(
|
||||
item => !get(values, 'dependencies', []).find(o => o === item.id)
|
||||
)
|
||||
setCount(vals.length)
|
||||
if (vals.length > 5) {
|
||||
vals = vals.slice(0, 5)
|
||||
}
|
||||
vals = sortResult(vals)
|
||||
setResult(vals)
|
||||
}
|
||||
onSearch()
|
||||
}, [values, query, search, setResult])
|
||||
|
||||
return (
|
||||
<div className='colset-2'>
|
||||
<div className='column'>
|
||||
<label className='search-label' htmlFor='input-quicksearch'>
|
||||
{/* eslint-disable-line */}
|
||||
搜索依赖组件
|
||||
</label>
|
||||
<input
|
||||
type='text'
|
||||
className='control-input'
|
||||
placeholder='Web, Security, JPA, Actuator, Devtools...'
|
||||
value={query}
|
||||
onBlur={onBlur}
|
||||
onFocus={onFocus}
|
||||
onChange={event => {
|
||||
setQuery(event.target.value)
|
||||
setSelected(0)
|
||||
}}
|
||||
id='input-quicksearch'
|
||||
ref={input}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
<Result
|
||||
list={result}
|
||||
add={item => {
|
||||
add(item.id)
|
||||
setQuery('')
|
||||
if (input) {
|
||||
get(input, 'current').focus()
|
||||
}
|
||||
}}
|
||||
selected={selected}
|
||||
select={setSelected}
|
||||
/>
|
||||
{count > 5 && (
|
||||
<div className='search-more-warning'>
|
||||
<p>
|
||||
More than 5 results found.
|
||||
<br />
|
||||
Refine your search if necessary.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='column'>
|
||||
{/* eslint-disable-next-line */}
|
||||
<label>已选组件</label>
|
||||
{get(values, 'dependencies', []).length === 0 ? (
|
||||
<div className='search-no-selected'>暂未选择任何组件</div>
|
||||
) : (
|
||||
<Dependencies
|
||||
list={dependencies}
|
||||
remove={item => {
|
||||
dispatch({
|
||||
type: 'REMOVE_DEPENDENCY',
|
||||
payload: { id: item.id },
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
QuickSearch.defaultProps = {}
|
||||
|
||||
QuickSearch.propTypes = {
|
||||
submit: PropTypes.func.isRequired,
|
||||
input: PropTypes.oneOfType([
|
||||
PropTypes.func,
|
||||
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
|
||||
]).isRequired,
|
||||
}
|
||||
|
||||
export default QuickSearch
|
||||
@@ -0,0 +1,93 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import {CSSTransition, TransitionGroup} from 'react-transition-group'
|
||||
|
||||
import {IconPlus} from '../../icons'
|
||||
|
||||
function Result({ list, selected, select, add }) {
|
||||
return (
|
||||
<TransitionGroup component='ul' className='dependencies-list'>
|
||||
{list.map((item, index) => {
|
||||
return (
|
||||
<CSSTransition timeout={500} classNames='fade' key={`f${item.id}`}>
|
||||
<li>
|
||||
<span className='dependency-item-span'>
|
||||
<a
|
||||
href='/'
|
||||
className={`dependency-item dependency-item-gray ${
|
||||
!item.valid ? 'invalid' : ''
|
||||
} ${selected === index ? 'selected' : ''}`}
|
||||
key={`item${item.id}`}
|
||||
selected={selected === index}
|
||||
disabled={!item.valid}
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
if (item.valid) {
|
||||
add(item)
|
||||
}
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
if (item.valid) {
|
||||
select(index)
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
if (item.valid) {
|
||||
select(-1)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<strong className='title' key={`item${item.id}`}>
|
||||
{item.name}
|
||||
</strong>
|
||||
<br />
|
||||
{item.valid && (
|
||||
<span key={`d1${item.id}`}>
|
||||
<span className='description' key={`d2${item.id}`}>
|
||||
{item.description}
|
||||
</span>
|
||||
<span key={`d3${item.id}`} className='icon'>
|
||||
<IconPlus key={`d4${item.id}`} />
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
{!item.valid && (
|
||||
<span className='warning' key={`warning${item.id}`}>
|
||||
{item.message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
</span>
|
||||
</li>
|
||||
</CSSTransition>
|
||||
)
|
||||
})}
|
||||
</TransitionGroup>
|
||||
)
|
||||
// }
|
||||
}
|
||||
|
||||
Result.defaultProps = {
|
||||
list: [],
|
||||
selected: null,
|
||||
}
|
||||
|
||||
Result.propTypes = {
|
||||
list: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
group: PropTypes.string.isRequired,
|
||||
description: PropTypes.string.isRequired,
|
||||
versionRange: PropTypes.string,
|
||||
versionRequirement: PropTypes.string,
|
||||
})
|
||||
),
|
||||
add: PropTypes.func.isRequired,
|
||||
selected: PropTypes.number,
|
||||
select: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default Result
|
||||
82
initializer-page/src/components/common/explore/Code.js
Normal file
82
initializer-page/src/components/common/explore/Code.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import Highlight from 'prism-react-renderer'
|
||||
import Prism from 'prism-react-renderer/prism'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import get from 'lodash.get'
|
||||
|
||||
const ReactMarkdown = require('react-markdown')
|
||||
|
||||
if (typeof global !== 'undefined') {
|
||||
global.Prism = Prism
|
||||
require('prismjs/components/prism-java') // eslint-disable-line
|
||||
require('prismjs/components/prism-kotlin') // eslint-disable-line
|
||||
require('prismjs/components/prism-properties') // eslint-disable-line
|
||||
require('prismjs/components/prism-groovy') // eslint-disable-line
|
||||
require('prismjs/components/prism-git') // eslint-disable-line
|
||||
}
|
||||
|
||||
function Code({ item }) {
|
||||
const code = get(item, 'content', '').replace(/\t/g, ' ')
|
||||
const language = get(item, 'language')
|
||||
if (language === 'markdown' && !get(item, 'force', false)) {
|
||||
return (
|
||||
<div className='markdown'>
|
||||
<ReactMarkdown linkTarget='_blank' source={code} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<Highlight Prism={Prism} code={code} language={language} theme={null}>
|
||||
{({ className, style, tokens, getLineProps, getTokenProps }) => {
|
||||
let groupLine = tokens.length > 9 ? '2' : '1'
|
||||
groupLine = tokens.length > 99 ? '3' : groupLine
|
||||
groupLine = tokens.length > 999 ? '4' : groupLine
|
||||
return (
|
||||
<pre className={`${className} line-${groupLine}`} style={style}>
|
||||
{tokens.map((line, i) => {
|
||||
const props = getLineProps({ line, key: i })
|
||||
return (
|
||||
<div
|
||||
key={get(props, 'key')}
|
||||
className={get(props, 'className')}
|
||||
>
|
||||
<span data-value={i + 1} className='explorer-number' />
|
||||
{line.map((token, key) => {
|
||||
const props2 = getTokenProps({ token, key })
|
||||
return (
|
||||
<span
|
||||
className={get(props2, 'className')}
|
||||
style={get(props2, 'style')}
|
||||
key={get(props2, 'key')}
|
||||
>
|
||||
{get(props2, 'children')}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</pre>
|
||||
)
|
||||
}}
|
||||
</Highlight>
|
||||
)
|
||||
}
|
||||
|
||||
Code.defaultProps = {
|
||||
item: {
|
||||
content: '',
|
||||
force: false,
|
||||
language: 'md',
|
||||
},
|
||||
}
|
||||
|
||||
Code.propTypes = {
|
||||
item: PropTypes.shape({
|
||||
content: PropTypes.string,
|
||||
force: PropTypes.bool,
|
||||
language: PropTypes.string,
|
||||
}),
|
||||
}
|
||||
|
||||
export default Code
|
||||
199
initializer-page/src/components/common/explore/Explore.js
Normal file
199
initializer-page/src/components/common/explore/Explore.js
Normal file
@@ -0,0 +1,199 @@
|
||||
import '../../../styles/explore.scss'
|
||||
|
||||
import FileSaver from 'file-saver'
|
||||
import JSZip from 'jszip'
|
||||
import Modal from 'react-responsive-modal'
|
||||
import PropTypes from 'prop-types'
|
||||
import get from 'lodash.get'
|
||||
import React, {useContext, useEffect, useState} from 'react'
|
||||
import {CopyToClipboard} from 'react-copy-to-clipboard'
|
||||
import {toast} from 'react-toastify'
|
||||
|
||||
import Code from './Code'
|
||||
import Loading from './Loading'
|
||||
import Tree from './Tree'
|
||||
import {AppContext} from '../../reducer/App'
|
||||
import {IconFile, IconTimes} from '../icons'
|
||||
import {createTree, findRoot} from '../../utils/Zip'
|
||||
|
||||
function Explore({ open, onClose, projectName, blob }) {
|
||||
const [button, setButton] = useState('Copy')
|
||||
const [tree, setTree] = useState(null)
|
||||
const [selected, setSelected] = useState(null)
|
||||
const { dispatch, explore } = useContext(AppContext)
|
||||
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
try {
|
||||
const zipJs = new JSZip()
|
||||
const { files } = await zipJs.loadAsync(blob).catch(() => {
|
||||
throw Error(`Could not load the ZIP project.`)
|
||||
})
|
||||
const path = `${findRoot({ files })}/`
|
||||
const result = await createTree(files, path, path, zipJs).catch(() => {
|
||||
throw Error(`Could not read the ZIP project.`)
|
||||
})
|
||||
setSelected(result.selected)
|
||||
setTree(result.tree)
|
||||
} catch (e) {
|
||||
dispatch({ type: 'EXPLORE_UPDATE', payload: { open: false } })
|
||||
toast.error(e.message)
|
||||
}
|
||||
}
|
||||
if (explore && blob) {
|
||||
load()
|
||||
}
|
||||
}, [explore, blob, dispatch])
|
||||
|
||||
const onCopy = () => {
|
||||
setButton('Copied!')
|
||||
setTimeout(() => {
|
||||
setButton('Copy!')
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
const download = file => {
|
||||
const blobFile = new Blob([file.content], {
|
||||
type: 'text/plain;charset=utf-8',
|
||||
})
|
||||
FileSaver.saveAs(blobFile, file.filename)
|
||||
}
|
||||
|
||||
const downloadZip = () => {
|
||||
FileSaver.saveAs(blob, projectName)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={() => {
|
||||
setSelected(null)
|
||||
onClose()
|
||||
}}
|
||||
showCloseIcon={false}
|
||||
classNames={{ modal: 'modal-explorer', overlay: 'overlay' }}
|
||||
>
|
||||
{tree && selected ? (
|
||||
<div className='colset-explorer'>
|
||||
<div className='left'>
|
||||
<div className='head'>
|
||||
<strong>{projectName}</strong>
|
||||
</div>
|
||||
<div className='explorer-content'>
|
||||
<Tree
|
||||
selected={selected}
|
||||
onClickItem={item => {
|
||||
setSelected(item)
|
||||
// onSelected(item)
|
||||
}}
|
||||
tree={tree}
|
||||
/>
|
||||
</div>
|
||||
<div className='foot'>
|
||||
<a
|
||||
href='/#'
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
downloadZip()
|
||||
}}
|
||||
className='action'
|
||||
>
|
||||
下载源码包
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className='right'>
|
||||
{selected && (
|
||||
<>
|
||||
<div className='head'>
|
||||
<strong>
|
||||
<IconFile />
|
||||
{get(selected, 'filename')}
|
||||
</strong>
|
||||
<div className='actions'>
|
||||
<span className='divider'>|</span>
|
||||
<a
|
||||
href='/#'
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
download(selected)
|
||||
}}
|
||||
className='action'
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
<span className='divider'>|</span>
|
||||
<CopyToClipboard
|
||||
onCopy={onCopy}
|
||||
text={get(selected, 'content', '')}
|
||||
>
|
||||
<a
|
||||
href='/#'
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
}}
|
||||
className='action'
|
||||
>
|
||||
{button}
|
||||
</a>
|
||||
</CopyToClipboard>
|
||||
{get(selected, 'language') === 'markdown' && (
|
||||
<>
|
||||
<span className='divider'>|</span>
|
||||
<a
|
||||
href='/#'
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
const newSelected = { ...selected }
|
||||
newSelected.force = !get(selected, 'force', false)
|
||||
setSelected(newSelected)
|
||||
}}
|
||||
className='action'
|
||||
>
|
||||
{get(selected, 'force', false)
|
||||
? 'Preview'
|
||||
: 'View source'}
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<a
|
||||
href='/#'
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
onClose()
|
||||
}}
|
||||
className='close'
|
||||
>
|
||||
<IconTimes />
|
||||
</a>
|
||||
</div>
|
||||
<div className='explorer-content'>
|
||||
<Code item={selected} onChange={() => {}} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Loading onClose={onClose} />
|
||||
)}
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Explore.defaultProps = {
|
||||
projectName: '',
|
||||
blob: null,
|
||||
}
|
||||
|
||||
Explore.propTypes = {
|
||||
open: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
projectName: PropTypes.string,
|
||||
blob: PropTypes.instanceOf(Blob),
|
||||
}
|
||||
|
||||
export default Explore
|
||||
84
initializer-page/src/components/common/explore/Loading.js
Normal file
84
initializer-page/src/components/common/explore/Loading.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
import {IconTimes} from '../icons'
|
||||
import {Placeholder} from '../form'
|
||||
|
||||
function Loading({ onClose }) {
|
||||
return (
|
||||
<>
|
||||
<a
|
||||
href='/#'
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
onClose()
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
</a>
|
||||
<div className='colset-explorer'>
|
||||
<div className='left'>
|
||||
<div className='head'>
|
||||
<Placeholder width='70px' type='text' />
|
||||
</div>
|
||||
<div className='explorer-content'>
|
||||
<ul className='explorer-ul-placeholder'>
|
||||
<li>
|
||||
<Placeholder type='text' width='66px' />
|
||||
</li>
|
||||
<li>
|
||||
<Placeholder type='text' width='60px' />
|
||||
</li>
|
||||
<li>
|
||||
<Placeholder type='text' width='45px' />
|
||||
</li>
|
||||
<li>
|
||||
<Placeholder type='text' width='87px' />
|
||||
</li>
|
||||
<li>
|
||||
<Placeholder type='text' width='80px' />
|
||||
</li>
|
||||
<li>
|
||||
<Placeholder type='text' width='94px' />
|
||||
</li>
|
||||
<li>
|
||||
<Placeholder type='text' width='86px' />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className='foot'>
|
||||
<Placeholder type='text' width='120px' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='right'>
|
||||
<>
|
||||
<div className='head'>
|
||||
<Placeholder width='86px' type='text' />
|
||||
<div className='actions'>
|
||||
<Placeholder width='74px' type='text' />
|
||||
<Placeholder width='43px' type='text' />
|
||||
</div>
|
||||
<a
|
||||
href='/#'
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
onClose()
|
||||
}}
|
||||
className='close'
|
||||
>
|
||||
<IconTimes />
|
||||
</a>
|
||||
</div>
|
||||
<div className='content' />
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Loading.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default Loading
|
||||
122
initializer-page/src/components/common/explore/Tree.js
Normal file
122
initializer-page/src/components/common/explore/Tree.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import get from 'lodash.get'
|
||||
import React, {useEffect, useState} from 'react'
|
||||
|
||||
import {IconCaretDown, IconFile, IconFolder} from '../icons'
|
||||
|
||||
function Tree({ tree, selected, onClickItem }) {
|
||||
const [folders, setFolders] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
const treeToArray = map => {
|
||||
const recursive = (mapRec, acc) => {
|
||||
mapRec.forEach(item => {
|
||||
if (item.type === 'folder') {
|
||||
acc.push({
|
||||
filename: get(item, 'filename'),
|
||||
path: get(item, 'path'),
|
||||
hidden: get(item, 'hidden', false),
|
||||
})
|
||||
if (get(item, 'children')) {
|
||||
recursive(item.children, acc)
|
||||
}
|
||||
}
|
||||
})
|
||||
return acc
|
||||
}
|
||||
return recursive(map, [])
|
||||
}
|
||||
setFolders(treeToArray(tree.children))
|
||||
}, [tree, setFolders])
|
||||
|
||||
const renderItem = (item, depth = 0) => {
|
||||
if (item.type === 'folder') {
|
||||
const folder = folders.find(f => f.path === item.path)
|
||||
const isHidden = get(folder, `hidden`, true)
|
||||
return (
|
||||
<li
|
||||
key={`li${item.path}`}
|
||||
className={`li-folder ${isHidden ? 'folder-hide' : ''}`}
|
||||
>
|
||||
<a
|
||||
href='/#'
|
||||
key={`s1${item.path}`}
|
||||
className={`folder level-${depth}`}
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
const newFolders = [...folders]
|
||||
const newFolder = newFolders.find(f => f.path === item.path)
|
||||
if (newFolder) {
|
||||
newFolder.hidden = !get(newFolder, `hidden`, true)
|
||||
setFolders(newFolders)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span key={`s2${item.path}`} className='text'>
|
||||
{get(item, 'children.length', 0) > 0 && <IconCaretDown />}
|
||||
<span key={`s3${item.path}`} className='icon'>
|
||||
<IconFolder key={`s4${item.path}`} />
|
||||
</span>
|
||||
{item.filename}
|
||||
</span>
|
||||
</a>
|
||||
{get(item, 'children') && (
|
||||
<ul className='ul' key={`ul${item.path}`}>
|
||||
{item.children.map(it => renderItem(it, depth + 1))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
// File
|
||||
const isDisabled = get(item, 'language') === null
|
||||
const isSelected = get(selected, 'path') === get(item, 'path')
|
||||
return (
|
||||
<li key={`li${item.path}`} className='li-file'>
|
||||
<a
|
||||
href='/#'
|
||||
key={`s1${item.path}`}
|
||||
tabIndex={`${isDisabled ? -1 : ''}`}
|
||||
className={`file level-${depth} ${isDisabled ? 'disabled' : ''} ${
|
||||
isSelected ? 'selected' : ''
|
||||
}`}
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
if (!isDisabled) {
|
||||
onClickItem(item)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span key={`s2${item.path}`} className='text'>
|
||||
<span key={`s3${item.path}`} className='icon'>
|
||||
<IconFile key={`s4${item.path}`} />
|
||||
</span>
|
||||
{item.filename}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className='explorer-ul'>
|
||||
{tree.children.map(item => renderItem(item, 0))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
Tree.propTypes = {
|
||||
tree: PropTypes.shape({
|
||||
children: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
type: PropTypes.string,
|
||||
})
|
||||
),
|
||||
}).isRequired,
|
||||
selected: PropTypes.shape({
|
||||
path: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
onClickItem: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default Tree
|
||||
1
initializer-page/src/components/common/explore/index.js
Normal file
1
initializer-page/src/components/common/explore/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Explore } from './Explore'
|
||||
100
initializer-page/src/components/common/fetch/Fetch.js
Normal file
100
initializer-page/src/components/common/fetch/Fetch.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import '../../../styles/fetch.scss'
|
||||
|
||||
import get from 'lodash.get'
|
||||
import PropTypes from 'prop-types'
|
||||
import React, {useContext, useRef, useState,} from 'react'
|
||||
import {toast} from 'react-toastify'
|
||||
import Modal from 'react-responsive-modal'
|
||||
import {CopyToClipboard} from 'react-copy-to-clipboard'
|
||||
import FileSaver from 'file-saver'
|
||||
import useWindowsUtils from '../../utils/WindowsUtils'
|
||||
import {InitializrContext} from '../../reducer/Initializr'
|
||||
import {getProject, getQueryString} from '../../utils/ApiUtils'
|
||||
import {AppContext} from '../../reducer/App'
|
||||
|
||||
function Fetch({ open, onClose }) {
|
||||
const windowsUtils = useWindowsUtils()
|
||||
const origin = `${windowsUtils.origin}`
|
||||
const { values } = useContext(InitializrContext)
|
||||
const { dependencies } = useContext(AppContext)
|
||||
|
||||
const config = get(dependencies, 'list')
|
||||
|
||||
const params = getQueryString(values, config)
|
||||
|
||||
const gitUrl = 'git clone "' + `${origin}/${params}/${values.meta.artifact}.git` + '" ' + `${values.meta.artifact}`
|
||||
const downloadUrl = `${origin}/starter.zip?${params}`
|
||||
|
||||
const [button, setButton] = useState('复制')
|
||||
const input = useRef(null)
|
||||
const onCopy = () => {
|
||||
setButton('已复制!')
|
||||
setTimeout(() => {
|
||||
onClose()
|
||||
setButton('复制')
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const onDownload = async () => {
|
||||
const url = `${windowsUtils.origin}/starter.zip`
|
||||
const project = await getProject(
|
||||
url,
|
||||
values,
|
||||
get(dependencies, 'list')
|
||||
).catch(() => {
|
||||
toast.error(`Could not connect to server. Please check your network.`)
|
||||
})
|
||||
FileSaver.saveAs(project, `${get(values, 'meta.artifact')}.zip`)
|
||||
onClose()
|
||||
setButton('复制')
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={() => {
|
||||
onClose()
|
||||
setButton('复制')
|
||||
}}
|
||||
classNames={{ modal: 'modal-fetch', overlay: 'overlay' }}
|
||||
center
|
||||
>
|
||||
<div>
|
||||
<h2>获取完整项目代码</h2>
|
||||
<div>
|
||||
<div class="fetch-method control">
|
||||
<span>下载代码包:</span>
|
||||
<input class="control-input" value={downloadUrl} readonly />
|
||||
<a
|
||||
href='/#'
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
onDownload()
|
||||
}}
|
||||
>下载</a>
|
||||
</div>
|
||||
<div class="fetch-method control">
|
||||
<span>Git Clone 命令:</span>
|
||||
<input class="control-input" value={gitUrl} readonly />
|
||||
<CopyToClipboard onCopy={onCopy} text={gitUrl}>
|
||||
<a
|
||||
href='/#'
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
}}
|
||||
>{button}</a>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Fetch.propTypes = {
|
||||
open: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default Fetch
|
||||
42
initializer-page/src/components/common/form/Button.js
Normal file
42
initializer-page/src/components/common/form/Button.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
function Button({ id, onClick, children, variant, hotkey }) {
|
||||
return (
|
||||
<button
|
||||
className={`button ${variant === 'primary' ? 'primary' : ''}`}
|
||||
type='button'
|
||||
id={id}
|
||||
onClick={event => {
|
||||
if (onClick) {
|
||||
onClick(event)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{hotkey && (
|
||||
<>
|
||||
{' '}
|
||||
<span className='desktop-only'>- {hotkey}</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
Button.defaultProps = {
|
||||
onClick: null,
|
||||
children: null,
|
||||
variant: '',
|
||||
hotkey: '',
|
||||
}
|
||||
|
||||
Button.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
variant: PropTypes.string,
|
||||
hotkey: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
children: PropTypes.node,
|
||||
}
|
||||
|
||||
export default Button
|
||||
29
initializer-page/src/components/common/form/Close.js
Normal file
29
initializer-page/src/components/common/form/Close.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
import {IconTimes} from '../icons'
|
||||
|
||||
const Close = ({ onClose }) => (
|
||||
<a
|
||||
href='/#'
|
||||
className='toast-close'
|
||||
onClick={event => {
|
||||
event.preventDefault()
|
||||
if (onClose) {
|
||||
onClose()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<IconTimes />
|
||||
</a>
|
||||
)
|
||||
|
||||
Close.defaultProps = {
|
||||
onClose: null,
|
||||
}
|
||||
|
||||
Close.propTypes = {
|
||||
onClose: PropTypes.func,
|
||||
}
|
||||
|
||||
export default Close
|
||||
29
initializer-page/src/components/common/form/Form.js
Normal file
29
initializer-page/src/components/common/form/Form.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
const Form = ({ onSubmit, children }) => (
|
||||
<form onSubmit={onSubmit} autoComplete='off'>
|
||||
<input
|
||||
style={{ display: 'none' }}
|
||||
type='text'
|
||||
name='fakeusernameremembered'
|
||||
/>
|
||||
<input
|
||||
style={{ display: 'none' }}
|
||||
type='password'
|
||||
name='fakepasswordremembered'
|
||||
/>
|
||||
{children}
|
||||
</form>
|
||||
)
|
||||
|
||||
Form.defaultProps = {
|
||||
children: null,
|
||||
}
|
||||
|
||||
Form.propTypes = {
|
||||
onSubmit: PropTypes.func.isRequired,
|
||||
children: PropTypes.node,
|
||||
}
|
||||
|
||||
export default Form
|
||||
49
initializer-page/src/components/common/form/Placeholder.js
Normal file
49
initializer-page/src/components/common/form/Placeholder.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
function Placeholder({ type, count, width }) {
|
||||
if (type === 'radios') {
|
||||
const options = Array.from({ length: count }, (item, i) => {
|
||||
const id = `p${i}`
|
||||
return <span key={id} style={{ width }} className='placeholder-radio' />
|
||||
})
|
||||
return <span className='placeholder-radios'>{options}</span>
|
||||
}
|
||||
if (type === 'input') {
|
||||
return <span className='placeholder-input' />
|
||||
}
|
||||
if (type === 'dropdown') {
|
||||
return <span className='placeholder-dropdown' />
|
||||
}
|
||||
if (type === 'text') {
|
||||
return <span style={{ width }} className='placeholder-text' />
|
||||
}
|
||||
if (type === 'button') {
|
||||
return <span style={{ width }} className='placeholder-button' />
|
||||
}
|
||||
if (type === 'tabs') {
|
||||
const options = Array.from({ length: count }, (item, i) => {
|
||||
return <span key={`p${i}`} className='placeholder-tab' />
|
||||
})
|
||||
return (
|
||||
<span className='placeholder-tabs'>
|
||||
<span className='placeholder-header'>{options}</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
Placeholder.defaultProps = {
|
||||
width: '150px',
|
||||
type: 'radios',
|
||||
count: 3,
|
||||
}
|
||||
|
||||
Placeholder.propTypes = {
|
||||
type: PropTypes.string,
|
||||
count: PropTypes.number,
|
||||
width: PropTypes.string,
|
||||
}
|
||||
|
||||
export default Placeholder
|
||||
45
initializer-page/src/components/common/form/Radio.js
Normal file
45
initializer-page/src/components/common/form/Radio.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
function Radio({ handler, value, disabled, error, checked, text }) {
|
||||
const onClick = event => {
|
||||
event.preventDefault()
|
||||
handler(value)
|
||||
}
|
||||
if (disabled || error) {
|
||||
return (
|
||||
<span
|
||||
className={`radio disabled ${checked ? 'checked' : ''} ${
|
||||
error ? 'err' : ''
|
||||
}`}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<a
|
||||
href='/'
|
||||
className={`radio ${checked ? 'checked' : ''}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{text}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
Radio.defaultProps = {
|
||||
disabled: false,
|
||||
error: false,
|
||||
}
|
||||
|
||||
Radio.propTypes = {
|
||||
checked: PropTypes.bool.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
handler: PropTypes.func.isRequired,
|
||||
disabled: PropTypes.bool,
|
||||
error: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default Radio
|
||||
63
initializer-page/src/components/common/form/RadioGroup.js
Normal file
63
initializer-page/src/components/common/form/RadioGroup.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
import Radio from './Radio'
|
||||
|
||||
function RadioGroup({ onChange, options, error, selected, disabled }) {
|
||||
const onChangeHandler = value => {
|
||||
if (onChange) {
|
||||
onChange(value)
|
||||
}
|
||||
}
|
||||
const allOptions = options.map(option => {
|
||||
return (
|
||||
<Radio
|
||||
key={option.key}
|
||||
checked={!error && selected === option.key}
|
||||
text={option.text}
|
||||
value={option.key}
|
||||
disabled={disabled}
|
||||
handler={onChangeHandler}
|
||||
/>
|
||||
)
|
||||
})
|
||||
if (error) {
|
||||
allOptions.push(
|
||||
<Radio
|
||||
key={allOptions.length + 1}
|
||||
checked
|
||||
text={error}
|
||||
value={error}
|
||||
disabled={disabled}
|
||||
handler={onChangeHandler}
|
||||
error
|
||||
/>
|
||||
)
|
||||
}
|
||||
return <div className='group-radio'>{allOptions}</div>
|
||||
}
|
||||
|
||||
RadioGroup.defaultProps = {
|
||||
selected: '',
|
||||
error: null,
|
||||
onChange: null,
|
||||
disabled: false,
|
||||
options: {
|
||||
error: '',
|
||||
},
|
||||
}
|
||||
|
||||
RadioGroup.propTypes = {
|
||||
selected: PropTypes.string,
|
||||
error: PropTypes.string,
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
key: PropTypes.string.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
})
|
||||
),
|
||||
onChange: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default RadioGroup
|
||||
33
initializer-page/src/components/common/form/Switch.js
Normal file
33
initializer-page/src/components/common/form/Switch.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
const Switch = ({ id, isOn, onChange }) => {
|
||||
return (
|
||||
<span className='switch'>
|
||||
<input
|
||||
checked={isOn}
|
||||
onChange={onChange}
|
||||
className='switch-checkbox'
|
||||
id={id}
|
||||
name='switch-new'
|
||||
type='checkbox'
|
||||
/>
|
||||
{/* eslint-disable-next-line */}
|
||||
<label className='switch-label' htmlFor={id}>
|
||||
<span className='switch-button' />
|
||||
</label>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
Switch.defaultProps = {
|
||||
isOn: false,
|
||||
onChange: null,
|
||||
}
|
||||
|
||||
Switch.propTypes = {
|
||||
isOn: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
}
|
||||
|
||||
export default Switch
|
||||
7
initializer-page/src/components/common/form/index.js
Normal file
7
initializer-page/src/components/common/form/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export { default as Radio } from './Radio'
|
||||
export { default as RadioGroup } from './RadioGroup'
|
||||
export { default as Switch } from './Switch'
|
||||
export { default as Close } from './Close'
|
||||
export { default as Placeholder } from './Placeholder'
|
||||
export { default as Form } from './Form'
|
||||
export { default as Button } from './Button'
|
||||
22
initializer-page/src/components/common/icons/IconSpring.js
Normal file
22
initializer-page/src/components/common/icons/IconSpring.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
|
||||
export const IconSpring = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='spring'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
className='icon-spring'
|
||||
viewBox='0 0 248.7 248.38'
|
||||
>
|
||||
<g>
|
||||
<g>
|
||||
<path
|
||||
d='M226.28,14.15a114.05,114.05,0,0,1-13.22,23.44A124.1,124.1,0,1,0,39.75,215.14l2.72,2.4A10.61,10.61,0,1,1,57.39,216a10.62,10.62,0,0,1-14.92,1.55l1.85,1.64a123.56,123.56,0,0,0,79.86,29.19c65.45,0,119.21-50.94,123.81-115.24C251.4,101.66,242.1,61.69,226.28,14.15Zm-.53,164.69c-30.61,40.78-96,27-137.92,29,0,0-7.42.42-14.89,1.65,0,0,2.83-1.21,6.43-2.45,29.44-10.19,43.35-12.23,61.25-21.42,33.61-17.22,67.06-54.72,73.87-93.69-12.8,37.48-51.71,69.73-87.14,82.82-24.25,9-68.11,17.66-68.11,17.67s-1.75-.92-1.78-.94C27.64,177,26.78,112.36,81,91.54c23.75-9.14,46.47-4.12,72.14-10.23,27.38-6.51,59.1-27,72-53.88C239.53,70.3,256.89,137.36,225.75,178.84Z'
|
||||
fill='currentColor'
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
339
initializer-page/src/components/common/icons/Icons.js
Normal file
339
initializer-page/src/components/common/icons/Icons.js
Normal file
@@ -0,0 +1,339 @@
|
||||
import React from 'react'
|
||||
|
||||
export const IconCaretDown = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='caret-down'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 320 512'
|
||||
className='icon-caret-down'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconCheck = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='check'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 512 512'
|
||||
className='icon-check'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconFolder = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='folder'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
className='icon-folder'
|
||||
viewBox='0 0 512 512'
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
fill='#86ad5c'
|
||||
d='M430.1,192H81.9c-17.7,0-18.6,9.2-17.6,20.5l13,183c0.9,11.2,3.5,20.5,21.1,20.5h316.2c18,0,20.1-9.2,21.1-20.5l12.1-185.3 C448.7,199,447.8,192,430.1,192z'
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
fill='#718c50'
|
||||
d='M426.2,143.3c-0.5-12.4-4.5-15.3-15.1-15.3c0,0-121.4,0-143.2,0c-21.8,0-24.4,0.3-40.9-17.4C213.3,95.8,218.7,96,190.4,96 c-22.6,0-75.3,0-75.3,0c-17.4,0-23.6-1.5-25.2,16.6c-1.5,16.7-5,57.2-5.5,63.4h343.4L426.2,143.3z'
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconFile = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='file'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
className='icon-file'
|
||||
viewBox='0 0 1792 1792'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M1596 380q28 28 48 76t20 88v1152q0 40-28 68t-68 28h-1344q-40 0-68-28t-28-68v-1600q0-40 28-68t68-28h896q40 0 88 20t76 48zm-444-244v376h376q-10-29-22-41l-313-313q-12-12-41-22zm384 1528v-1024h-416q-40 0-68-28t-28-68v-416h-768v1536h1280z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconChevronLeft = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='chevron-left'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 320 512'
|
||||
className='icon-chevron-left'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconChevronRight = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='chevron-right'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 320 512'
|
||||
className='icon-chevron-right'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconChevronDown = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='chevron-down'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 448 512'
|
||||
className='icon-chevron-down'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconChevronUp = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='chevron-up'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 448 512'
|
||||
className='icon-chevron-up'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M240.971 130.524l194.343 194.343c9.373 9.373 9.373 24.569 0 33.941l-22.667 22.667c-9.357 9.357-24.522 9.375-33.901.04L224 227.495 69.255 381.516c-9.379 9.335-24.544 9.317-33.901-.04l-22.667-22.667c-9.373-9.373-9.373-24.569 0-33.941L207.03 130.525c9.372-9.373 24.568-9.373 33.941-.001z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconGithub = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='github'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 496 512'
|
||||
className='icon-github'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconList = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='list'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 512 512'
|
||||
className='icon-list'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M128 116V76c0-8.837 7.163-16 16-16h352c8.837 0 16 7.163 16 16v40c0 8.837-7.163 16-16 16H144c-8.837 0-16-7.163-16-16zm16 176h352c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H144c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h352c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H144c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zM16 144h64c8.837 0 16-7.163 16-16V64c0-8.837-7.163-16-16-16H16C7.163 48 0 55.163 0 64v64c0 8.837 7.163 16 16 16zm0 160h64c8.837 0 16-7.163 16-16v-64c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v64c0 8.837 7.163 16 16 16zm0 160h64c8.837 0 16-7.163 16-16v-64c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v64c0 8.837 7.163 16 16 16z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconPlus = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-prefix='fas'
|
||||
data-icon='plus'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 448 512'
|
||||
className='icon-plus'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconSearch = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='search'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 512 512'
|
||||
className='icon-search'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconTimes = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-prefix='fas'
|
||||
data-icon='times'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 352 512'
|
||||
className='icon-times'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconTwitter = () => (
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
focusable='false'
|
||||
data-icon='twitter'
|
||||
role='img'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 512 512'
|
||||
className='icon-twitter'
|
||||
>
|
||||
<path
|
||||
fill='currentColor'
|
||||
d='M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z'
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconHelp = () => (
|
||||
<svg
|
||||
t="1583993417464"
|
||||
className='icon-help'
|
||||
focusable='false'
|
||||
aria-hidden='true'
|
||||
role='img'
|
||||
data-icon='help'
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="1960"
|
||||
width="18"
|
||||
height="18">
|
||||
<path
|
||||
p-id="1961"
|
||||
fill='currentColor'
|
||||
d="M0.12992 518.398052C-6.268452 236.869666 224.072954 6.528259 505.601341 0.129887c281.528386-6.398372 511.869793 223.943034 518.268165 505.471421 6.398372 281.528386-223.943034 511.869793-505.47142 518.268165-281.528386 6.398372-511.869793-223.943034-518.268166-505.471421zM595.178555 767.934577c0-44.788607-38.390234-83.178841-83.178842-83.178842-44.788607 0-83.178841 38.390234-83.178841 83.178842 0 44.788607 38.390234 83.178841 83.178841 83.178841 44.788607 0 83.178841-38.390234 83.178842-83.178841zM300.853423 345.641997v12.796745c0 31.991862 6.398372 63.983724 63.983725 63.983724S435.219244 384.032232 435.219244 345.641997c0-12.796745 38.390234-44.788607 76.780469-44.788607s83.178841 19.195117 83.178842 76.780469c0 38.390234-63.983724 76.780469-95.975587 108.772331-70.382097 63.983724-63.983724 89.577214-63.983724 89.577214 0 38.390234 12.796745 76.780469 70.382097 70.382097 57.585352-6.398372 166.357683-153.560938 166.357682-153.560938 51.186979-57.585352 51.186979-127.967448 51.18698-127.967448 0-121.569076-95.975586-185.5528-211.14629-185.5528-115.170703-6.398372-204.747917 51.186979-211.14629 166.357682z">
|
||||
</path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconFeedBack = () => (
|
||||
<svg
|
||||
t="1589424011678"
|
||||
class="icon-feedback"
|
||||
focusable='false'
|
||||
aria-hidden='true'
|
||||
viewBox="0 0 1070 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="1374"
|
||||
width="32"
|
||||
height="32">
|
||||
<path
|
||||
fill='currentColor'
|
||||
p-id="1375"
|
||||
d="M1021.713308 633.948781a48.653015 48.653015 0 0 1-48.653014-48.653014l-0.389224-487.673493-728.578895 0.413551a48.653015 48.653015 0 0 1 0-97.30603l678.709554-0.389224v0.413551h51.693829a97.306029 97.306029 0 0 1 95.359908 77.844823L1070.366323 585.295767a48.653015 48.653015 0 0 1-48.653015 48.653014zM924.407279 229.544923l0.413551 307.584359c0 1.216325-0.267592 2.432651-0.364898 3.600323l0.291918 200.912624a96.965458 96.965458 0 0 1-96.138357 86.213142h-51.693828l-270.024232 0.316245-182.448805 181.086521a48.653015 48.653015 0 1 1-68.795362-68.795363l189.162921-187.703331a48.653015 48.653015 0 0 1 40.576614-21.893856h1.946121l340.157552-0.316245v-76.506866 10.217133-17.75835L827.10125 486.5788c0-1.289305 0.267592-2.432651 0.364897-3.721956l-0.218938-164.155271c-1.021713-64.07602-9.000808-73.247114-64.659857-74.487766l64.173326 0.437877 0.486531 70.668504L827.10125 244.019195h-71.665891 7.10334-15.495985l-587.314867 0.316245c-51.085665 1.727182-60.645983 10.922602-62.397491 57.434884l0.291918 382.388368c2.432651 38.533188 14.085048 45.101345 65.851855 46.220364l-65.827529-0.437877v0.997387l194.903977-0.340571a48.653015 48.653015 0 0 1 0 97.306029l-143.526393 0.243265v-0.291918H97.306029a97.111417 97.111417 0 0 1-96.892478-93.073217L0 243.484012a97.062764 97.062764 0 0 1 92.927258-96.332969l736.120112-0.437877A96.989785 96.989785 0 0 1 924.407279 229.544923z m-827.10125 14.912149v33.424621l0.218939-33.230009 31.259562-0.218938H97.306029z m669.708747 485.873331c51.085665-1.240652 59.040433-9.730603 60.18378-65.997814l-0.462204 65.68157z m0 0c-10.460398 0.243265-22.502019 0.243265-36.927638 0.243265z m-603.51632 0l32.281275 0.218939c-12.211907 0-22.84259 0-32.232622-0.194612z m741.958474-632.099966l65.803202 0.437877 0.364898 54.613009c-1.483917-46.512282-10.72799-53.90754-66.192427-55.02656z m0 0l-32.402908-0.170286c12.284886 0 22.939896 0 32.378581 0.194612z m66.435691 97.135743l-0.291918-42.036204c0.364898 11.482111 0.291918 25.202262 0.291918 42.036204z">
|
||||
</path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconCloudTookit = () => (
|
||||
<svg
|
||||
t="1587979013746"
|
||||
className='icon-cloud-tookit'
|
||||
focusable='false'
|
||||
aria-hidden='true'
|
||||
role='img'
|
||||
data-icon='help'
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="679"
|
||||
width="25"
|
||||
height="25">
|
||||
<path
|
||||
p-id="680"
|
||||
fill='currentColor'
|
||||
d="M228.616 662.377c-18.254-4.236-32.293-19.763-32.293-39.526V405.455c1.404-19.764 14.041-35.291 32.293-39.527l200.78-43.76 21.062-86.112H217.384c-82.839 0-148.83 66.347-148.83 148.225v256.923c0 81.876 67.395 149.636 148.83 149.636h233.074l-21.061-86.112-200.781-42.351z m584.092-426.323H578.226l21.062 86.111 200.785 43.761c18.253 4.237 32.287 19.764 32.287 39.526V622.85c-1.4 19.763-14.035 35.29-32.287 39.526l-200.785 43.762-21.062 86.11h234.482c81.436 0 148.83-66.347 148.83-149.635V384.278c-1.41-81.877-67.394-148.224-148.83-148.224zM429.396 502.86H600.69v21.174H429.396V502.86z">
|
||||
</path>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconInitialzrTutorial = () => (
|
||||
<svg t="1603192889200" class="icon-cloud-tookit" viewBox="0 0 1376 1024" version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg" p-id="1399" width="200" height="200" style={{left: '20px'}}>
|
||||
<path
|
||||
d="M596.800316 1023.986434q-3.66285 0-7.447794-0.217058c-24.581792-1.356611-50.303136-9.048595-80.04005-17.934397-110.238211-32.789288-153.134251-29.397761-187.565038-26.630275-51.008574 4.069833-91.842566 15.926613-127.88772 26.345386-56.177262 16.279332-104.689672 30.293124-147.029502-6.783055C18.802629 974.239508 3.065941 934.6129 0.054264 880.972501V186.04851c0-91.313487 68.49529-165.750733 153.161384-167.093778a1093.645533 1093.645533 0 0 1 405.463899 0 151.831904 151.831904 0 0 1 108.094765 49.774058c28.990777 31.554772 44.958089 73.256995 44.958089 117.31972V879.683721c-3.147338 53.002792-18.870459 92.819325-46.748815 118.36431-19.00612 17.418885-41.403768 25.938403-68.18327 25.938403z m-243.376015-93.60616c69.187162 0 124.021379 16.360729 169.400017 29.845442 57.561005 17.161129 86.294026 24.418998 110.034719 2.713222 18.354947-16.821977 28.922947-45.58213 31.378413-85.466493V186.04851c0-32.165247-11.517627-62.404107-32.43657-85.195171a105.476506 105.476506 0 0 0-75.631063-34.430788h-2.19771l-2.157012-0.406983a1046.245545 1046.245545 0 0 0-391.884223 0l-2.19771 0.420549h-2.238408c-28.326038 0-55.159804 12.209499-75.563233 34.444354-20.918942 22.763933-32.436569 53.016358-32.436569 85.195171v692.943339c2.333371 40.060723 12.630049 68.346063 30.591578 84.109883 10.337376 9.048595 21.488718 12.52152 37.320369 11.626156 17.635943-1.003892 39.72157-7.379964 65.239423-14.759928 36.302911-10.513735 81.491623-23.591465 137.275469-28.068281 12.236631-1.031024 24.066279-1.478706 35.516076-1.478706z"
|
||||
p-id="1400" fill='currentColor'></path>
|
||||
<path
|
||||
d="M1261.064898 1023.986434c-2.4419 0-4.924498-0.067831-7.447794-0.217058-24.581792-1.356611-50.303136-9.048595-80.04005-17.934397-110.251777-32.789288-153.174949-29.397761-187.605737-26.630275-51.008574 4.069833-91.842566 15.926613-127.88772 26.345386-56.177262 16.279332-104.689672 30.293124-147.029501-6.783055-28.027584-24.527527-43.764271-64.154135-46.775948-117.794534l47.413555-2.713222c2.26554 40.521971 12.575784 69.010802 30.605144 84.828887 10.337376 9.048595 21.502285 12.52152 37.320369 11.626156 17.635943-1.003892 39.72157-7.379964 65.239424-14.759928 36.302911-10.513735 81.491623-23.591465 137.275468-28.068282 86.199064-6.932282 152.02183 12.697879 204.916094 28.488832 57.561005 17.161129 86.294026 24.418998 110.034719 2.713222 18.354947-16.821977 28.922947-45.58213 31.378412-85.466494V186.04851c0-32.165247-11.517627-62.404107-32.436569-85.195171a105.476506 105.476506 0 0 0-75.64463-34.430788h-2.19771l-2.157011-0.406983a1046.245545 1046.245545 0 0 0-391.884223 0l-2.19771 0.420549h-2.238408c-28.326038 0-55.159804 12.209499-75.563234 34.444354-20.918942 22.763933-32.436569 53.016358-32.436569 85.195171h-47.481385c0-91.313487 68.49529-165.750733 153.161383-167.093778a1093.645533 1093.645533 0 0 1 405.463899 0 151.831904 151.831904 0 0 1 108.216861 49.746926c28.990777 31.554772 44.958089 73.256995 44.958088 117.31972V879.683721c-3.147338 53.002792-18.870459 92.819325-46.748815 118.36431-19.087517 17.418885-41.444466 25.938403-68.210402 25.938403zM121.362421 229.43293h334.974391v47.481386H121.362421zM121.362421 384.507135h334.974391v47.481386H121.362421z"
|
||||
p-id="1401" fill='currentColor'></path>
|
||||
<path
|
||||
d="M942.342707 431.988521a23.740693 23.740693 0 0 1-23.740693-23.740693V14.681407h47.481386v342.788471l60.626946-50.655856a23.740693 23.740693 0 0 1 30.442351 0l55.376862 46.260436V23.689304h47.481385V403.87954a23.740693 23.740693 0 0 1-38.961868 18.219286l-79.131121-66.12122-84.367638 70.543772a23.740693 23.740693 0 0 1-15.20761 5.467143z"
|
||||
p-id="1402" fill='currentColor'></path>
|
||||
</svg>
|
||||
)
|
||||
export const IconHot = () => (
|
||||
<svg t="1603192889200" class="icon-cloud-tookit" viewBox="0 0 1376 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1399" width="200" height="200">
|
||||
p-id="2096" width="200" height="200">
|
||||
<path
|
||||
d="M498.463465 545.489466c-32.862322 0-49.168055 24.584027-49.168055 61.460069 0 41.015189 16.431161 61.460069 49.168055 61.460068 32.862322 0 49.168055-20.44488 49.168055-61.460068-4.013719-36.876041-16.305732-57.44635-49.168055-61.460069z m0 0"
|
||||
fill="#FF0000" p-id="2097"></path>
|
||||
<path
|
||||
d="M674.816233 164.060755c20.44488 49.168055 24.584027 123.045566-16.431161 164.060754C588.646709 57.44635 412.29394 0 412.29394 0c20.570309 139.476727-73.752082 291.245468-168.074473 406.012739-4.139147-57.44635-8.152866-94.322391-36.876041-147.629593-8.152866 102.475257-82.030377 184.505634-106.614405 282.967172-16.431161 86.169525-4.139147 164.060755 57.320921 233.799119 98.461538 94.322391 352.705537 213.228809 611.088682 4.139147 237.812837-242.077413-36.876041-574.212641-94.322391-615.227829zM379.557047 705.411073h-45.154337v-77.89123H252.372333V705.411073h-45.154336V512.627144h45.154336v77.891229h86.169525v-77.891229h45.154336V705.411073h-4.139147z m118.906418 4.139147c-61.460069-4.139147-94.322391-36.876041-98.461538-98.461538 4.139147-65.599216 36.876041-98.461538 98.461538-102.475257 61.460069 4.139147 90.183244 36.876041 94.322391 102.475257 0 61.460069-32.862322 94.322391-94.322391 98.461538z m221.507104-160.047035v159.921607h-45.154336V549.503185h-61.460068v-36.876041H781.556067v36.876041h-61.585498z m0 0"
|
||||
fill="#FF0000" p-id="2098"></path>
|
||||
</svg>
|
||||
)
|
||||
21
initializer-page/src/components/common/icons/index.js
Normal file
21
initializer-page/src/components/common/icons/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
export { IconTimes } from './Icons'
|
||||
export { IconPlus } from './Icons'
|
||||
export { IconGithub } from './Icons'
|
||||
export { IconTwitter } from './Icons'
|
||||
export { IconCaretDown } from './Icons'
|
||||
export { IconList } from './Icons'
|
||||
export { IconSearch } from './Icons'
|
||||
export { IconCheck } from './Icons'
|
||||
export { IconChevronLeft } from './Icons'
|
||||
export { IconChevronRight } from './Icons'
|
||||
export { IconChevronUp } from './Icons'
|
||||
export { IconChevronDown } from './Icons'
|
||||
export { IconFolder } from './Icons'
|
||||
export { IconFile } from './Icons'
|
||||
export { IconHelp } from './Icons'
|
||||
export { IconCloudTookit } from './Icons'
|
||||
export { IconInitialzrTutorial } from './Icons'
|
||||
export { IconFeedBack } from './Icons'
|
||||
export { IconHot } from './Icons'
|
||||
|
||||
export { IconSpring } from './IconSpring'
|
||||
44
initializer-page/src/components/common/layout/Footer.js
Normal file
44
initializer-page/src/components/common/layout/Footer.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
const Footer = ({ children }) => (
|
||||
<div className='sticky'>
|
||||
<div className='colset colset-submit'>
|
||||
<div className='left nopadding'>
|
||||
<footer className='footer'>
|
||||
<div className='footer-container'>
|
||||
© 1999-{new Date().getFullYear()} Aliyun.com
|
||||
<br />
|
||||
start.aliyun.com is powered by
|
||||
<br />
|
||||
<span>
|
||||
<a
|
||||
tabIndex='-1'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
href='https://www.aliyun.com'
|
||||
>
|
||||
Aliyun.com
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
{children && (
|
||||
<div className='right nopadding'>
|
||||
<div className='submit'>{children}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Footer.defaultProps = {
|
||||
children: null,
|
||||
}
|
||||
|
||||
Footer.propTypes = {
|
||||
children: PropTypes.node,
|
||||
}
|
||||
|
||||
export default Footer
|
||||
26
initializer-page/src/components/common/layout/Header.js
Normal file
26
initializer-page/src/components/common/layout/Header.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
const Header = ({ children }) => (
|
||||
<div className='header'>
|
||||
<h1 className='logo'>
|
||||
<a href='/bootstrap.html'>
|
||||
<span className='title'>
|
||||
<strong>云原生</strong>应用脚手架
|
||||
</span>
|
||||
<span className='description'>Cloud Native App Initializer</span>
|
||||
</a>
|
||||
</h1>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
Header.defaultProps = {
|
||||
children: null,
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
children: PropTypes.node,
|
||||
}
|
||||
|
||||
export default Header
|
||||
20
initializer-page/src/components/common/layout/Layout.js
Normal file
20
initializer-page/src/components/common/layout/Layout.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
import Header from './Header'
|
||||
import QuickLinks from './QuickLinks'
|
||||
|
||||
const Layout = ({ children }) => (
|
||||
<>
|
||||
<Header>
|
||||
<QuickLinks />
|
||||
</Header>
|
||||
<main>{children}</main>
|
||||
</>
|
||||
)
|
||||
|
||||
Layout.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
}
|
||||
|
||||
export default Layout
|
||||
30
initializer-page/src/components/common/layout/Logo.js
Normal file
30
initializer-page/src/components/common/layout/Logo.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
const Logo = ({ className }) => {
|
||||
return (
|
||||
<svg
|
||||
t="1583838227729"
|
||||
className={className}
|
||||
viewBox="0 0 1024 1024"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="6979" width="65" height="65">
|
||||
<path
|
||||
fill="#FF6A00"
|
||||
p-id="6980"
|
||||
d="M853.333333 852.48h-225.706666l54.613333-77.226667 164.693333-50.346666c29.866667-9.386667 49.92-37.12 49.92-68.266667V410.026667c0-31.146667-20.053333-58.88-49.493333-68.266667L682.666667 291.413333l-54.613334-77.226666H853.333333c94.293333 0 170.666667 76.373333 170.666667 170.666666v297.386667c0 93.866667-76.373333 170.24-170.666667 170.24zM341.333333 492.8h341.333334v76.8H341.333333v-76.8zM177.066667 341.333333c-29.866667 9.386667-49.92 37.12-49.493334 68.266667v247.04c0 31.146667 20.053333 58.88 49.493334 68.266667l164.693333 50.346666 54.613333 77.226667H170.666667c-94.293333 0-170.666667-76.373333-170.666667-170.666667V384.426667c0-94.293333 76.373333-170.666667 170.666667-170.666667h225.706666L341.76 290.986667 177.066667 341.333333z">
|
||||
</path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
Logo.defaultProps = {
|
||||
className: '',
|
||||
}
|
||||
|
||||
Logo.propTypes = {
|
||||
className: PropTypes.string,
|
||||
}
|
||||
|
||||
export default Logo
|
||||
122
initializer-page/src/components/common/layout/QuickLinks.js
Normal file
122
initializer-page/src/components/common/layout/QuickLinks.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import get from 'lodash.get'
|
||||
import React, {useContext, useEffect, useRef, useState} from 'react'
|
||||
import {CSSTransition, TransitionGroup} from 'react-transition-group'
|
||||
|
||||
import {AppContext} from '../../reducer/App'
|
||||
import {IconCaretDown, IconHelp} from '../icons'
|
||||
import {Switch} from '../form'
|
||||
|
||||
const QuickLinks = () => {
|
||||
const {theme, dispatch, language} = useContext(AppContext)
|
||||
const [help, setHelp] = useState(false)
|
||||
const wrapper = useRef(null)
|
||||
const toggleTheme = () => {
|
||||
const newTheme = theme === 'dark' ? 'light' : 'dark'
|
||||
dispatch({
|
||||
type: 'UPDATE',
|
||||
payload: {
|
||||
theme: newTheme,
|
||||
},
|
||||
})
|
||||
}
|
||||
const toggleLanguage = () => {
|
||||
const newLanguage = language === 'en' ? 'zh' : 'en'
|
||||
dispatch({
|
||||
type: 'UPDATE',
|
||||
payload: {
|
||||
language: newLanguage,
|
||||
},
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
const clickOutside = event => {
|
||||
const children = get(wrapper, 'current')
|
||||
if (children && !children.contains(event.target)) {
|
||||
setHelp(false)
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', clickOutside)
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', clickOutside)
|
||||
}
|
||||
}, [setHelp])
|
||||
|
||||
/*
|
||||
<li>
|
||||
<span className='switch-language'>
|
||||
<Switch id='language-switch' isOn={language === 'en'} onChange={toggleLanguage} />
|
||||
{language === 'en' ? 'English' : '中文'}
|
||||
</span>
|
||||
</li>
|
||||
*/
|
||||
return (
|
||||
<ul className='quick-links'>
|
||||
<li>
|
||||
<span className='switch-mode'>
|
||||
<Switch id='theme-switch' isOn={theme === 'dark'} onChange={toggleTheme}/>
|
||||
{theme === 'dark' ? '深色' : '浅色'} 主题
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href='/'
|
||||
className='dropdown'
|
||||
tabIndex='-1'
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
setHelp(!help)
|
||||
}}
|
||||
ref={wrapper}
|
||||
>
|
||||
<IconHelp/>
|
||||
帮助信息
|
||||
<IconCaretDown className='caret'/>
|
||||
</a>
|
||||
|
||||
<TransitionGroup component={null}>
|
||||
{help && (
|
||||
<CSSTransition classNames='nav-anim' timeout={500}>
|
||||
<ul className='dropdown-menu'>
|
||||
<li>
|
||||
<a
|
||||
id='ql-help-projects'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
href='https://spring.io/projects'
|
||||
tabIndex='-1'
|
||||
>
|
||||
Spring Projects
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
id='ql-help-guides'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
tabIndex='-1'
|
||||
href='https://spring.io/projects/spring-cloud/'
|
||||
>
|
||||
Spring Cloud
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
id='ql-help-guides'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
tabIndex='-1'
|
||||
href='https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md'
|
||||
>
|
||||
Spring Cloud Alibaba
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</CSSTransition>
|
||||
)}
|
||||
</TransitionGroup>
|
||||
</li>
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default QuickLinks
|
||||
3
initializer-page/src/components/common/layout/index.js
Normal file
3
initializer-page/src/components/common/layout/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as Layout } from './Layout'
|
||||
export { default as Footer } from './Footer'
|
||||
export { default as Header } from './Header'
|
||||
@@ -0,0 +1,27 @@
|
||||
import '../../../styles/sandbox.scss'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
function SandboxShell({position}) {
|
||||
return (
|
||||
<button
|
||||
className='button primary share-ghost'
|
||||
type='button'
|
||||
style={{
|
||||
top: `${position.y}px`,
|
||||
left: `${position.x}px`,
|
||||
}}
|
||||
>
|
||||
跑一下 ^_^
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
SandboxShell.propTypes = {
|
||||
position: PropTypes.shape({
|
||||
x: PropTypes.number.isRequired,
|
||||
y: PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
export default SandboxShell
|
||||
35
initializer-page/src/components/common/share/Overlay.js
Normal file
35
initializer-page/src/components/common/share/Overlay.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import {CSSTransition, TransitionGroup} from 'react-transition-group'
|
||||
|
||||
import {noScroll} from '../../utils/NoScroll'
|
||||
|
||||
function Overlay({ open }) {
|
||||
const onEnter = () => {
|
||||
noScroll.on()
|
||||
}
|
||||
|
||||
const onEnded = () => {
|
||||
noScroll.off()
|
||||
}
|
||||
return (
|
||||
<TransitionGroup component={null}>
|
||||
{open && (
|
||||
<CSSTransition
|
||||
onEnter={onEnter}
|
||||
onExit={onEnded}
|
||||
classNames='overlay'
|
||||
timeout={500}
|
||||
>
|
||||
<div className='popup-share-overlay' />
|
||||
</CSSTransition>
|
||||
)}
|
||||
</TransitionGroup>
|
||||
)
|
||||
}
|
||||
|
||||
Overlay.propTypes = {
|
||||
open: PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
export default Overlay
|
||||
146
initializer-page/src/components/common/share/Popover.js
Normal file
146
initializer-page/src/components/common/share/Popover.js
Normal file
@@ -0,0 +1,146 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import get from 'lodash.get'
|
||||
import React, {useEffect, useRef, useState} from 'react'
|
||||
import {CSSTransition, TransitionGroup} from 'react-transition-group'
|
||||
import {CopyToClipboard} from 'react-copy-to-clipboard'
|
||||
|
||||
import useWindowsUtils from '../../utils/WindowsUtils'
|
||||
import {IconTimes} from '../icons'
|
||||
|
||||
function Popover({ shareUrl, shareSrc, open, onClose, position }) {
|
||||
const [button, setButton] = useState('Copy')
|
||||
const wrapper = useRef(null)
|
||||
const input = useRef(null)
|
||||
const link = useRef(null)
|
||||
const windowsUtils = useWindowsUtils()
|
||||
useEffect(() => {
|
||||
const clickOutside = event => {
|
||||
const children = get(wrapper, 'current')
|
||||
if (children && !children.contains(event.target)) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
document.addEventListener('mousedown', clickOutside)
|
||||
if (get(input, 'current')) {
|
||||
get(input, 'current').focus()
|
||||
}
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', clickOutside)
|
||||
}
|
||||
}, [onClose, input])
|
||||
|
||||
const onEnter = () => {
|
||||
setButton('复制')
|
||||
}
|
||||
|
||||
const onCopy = () => {
|
||||
setButton('已复制!')
|
||||
input.current.focus()
|
||||
setTimeout(() => {
|
||||
onClose()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const urlToShare = `${windowsUtils.origin}/bootstrap.html/${shareSrc}#!${shareUrl}`
|
||||
return (
|
||||
<>
|
||||
<TransitionGroup component={null}>
|
||||
{open && (
|
||||
<CSSTransition onEnter={onEnter} classNames='popup' timeout={500}>
|
||||
<div
|
||||
className='popup-share'
|
||||
style={{
|
||||
top: `${position.y - 200}px`,
|
||||
left: `${position.x - 200}px`,
|
||||
}}
|
||||
>
|
||||
<div ref={wrapper}>
|
||||
<div className='popup-header'>
|
||||
<h1>分享你的配置</h1>
|
||||
<a
|
||||
href='/#'
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
onClose()
|
||||
}}
|
||||
className='close'
|
||||
>
|
||||
<IconTimes />
|
||||
</a>
|
||||
</div>
|
||||
<div className='popup-content'>
|
||||
{/* eslint-disable-next-line */}
|
||||
<label htmlFor='input-share'>
|
||||
请复制下面的链接来分享你的项目配置;
|
||||
</label>
|
||||
{
|
||||
shareSrc === ''
|
||||
?
|
||||
<label htmlFor='input-share'>
|
||||
Tips: 您未登录,请<a href="https://account.aliyun.com/login/login.htm?oauth_callback=https%3A%2F%2Fstart.aliyun.com%2Fbootstrap.html">登录</a>后再分享链接,否则该分享链接的点击讲无法关联在您的账户下
|
||||
</label>
|
||||
:
|
||||
<label htmlFor='input-share'>
|
||||
Tips: 您已登录,您可以使用一下专属链接来分享项目配置,该分享链接的点击将被关联在您的账户下
|
||||
</label>
|
||||
}
|
||||
<div className='control'>
|
||||
<input
|
||||
onFocus={event => {
|
||||
event.target.select()
|
||||
}}
|
||||
id='input-share'
|
||||
className={`control-input ${
|
||||
button === 'Copied!' ? 'padding-lg' : ''
|
||||
}`}
|
||||
readOnly
|
||||
value={urlToShare}
|
||||
ref={input}
|
||||
/>
|
||||
<CopyToClipboard onCopy={onCopy} text={urlToShare}>
|
||||
<a
|
||||
href='/#'
|
||||
onClick={e => {
|
||||
e.preventDefault()
|
||||
}}
|
||||
className='link'
|
||||
ref={link}
|
||||
>
|
||||
{button}
|
||||
</a>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CSSTransition>
|
||||
)}
|
||||
</TransitionGroup>
|
||||
{open && (
|
||||
<button
|
||||
className='button primary share-ghost'
|
||||
type='button'
|
||||
style={{
|
||||
top: `${position.y}px`,
|
||||
left: `${position.x}px`,
|
||||
}}
|
||||
>
|
||||
分享...
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Popover.propTypes = {
|
||||
shareUrl: PropTypes.string.isRequired,
|
||||
shareSrc: PropTypes.string.isRequired,
|
||||
open: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
position: PropTypes.shape({
|
||||
x: PropTypes.number.isRequired,
|
||||
y: PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
export default Popover
|
||||
35
initializer-page/src/components/common/share/Share.js
Normal file
35
initializer-page/src/components/common/share/Share.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import '../../../styles/share.scss'
|
||||
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
|
||||
import Overlay from './Overlay'
|
||||
import Popover from './Popover'
|
||||
|
||||
function Share({ shareUrl, shareSrc, open, onClose, position }) {
|
||||
return (
|
||||
<>
|
||||
<Popover
|
||||
open={open || false}
|
||||
shareUrl={shareUrl}
|
||||
shareSrc={shareSrc}
|
||||
position={position}
|
||||
onClose={onClose}
|
||||
/>
|
||||
<Overlay open={open || false} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Share.propTypes = {
|
||||
shareUrl: PropTypes.string.isRequired,
|
||||
shareSrc: PropTypes.string.isRequired,
|
||||
open: PropTypes.bool.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
position: PropTypes.shape({
|
||||
x: PropTypes.number.isRequired,
|
||||
y: PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
export default Share
|
||||
1
initializer-page/src/components/common/share/index.js
Normal file
1
initializer-page/src/components/common/share/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Share } from './Share'
|
||||
134
initializer-page/src/components/reducer/App.js
Normal file
134
initializer-page/src/components/reducer/App.js
Normal file
@@ -0,0 +1,134 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import get from 'lodash.get'
|
||||
import set from 'lodash.set'
|
||||
import React, {useReducer} from 'react'
|
||||
|
||||
import useTheme from '../utils/Theme'
|
||||
import {isValidDependency} from '../utils/ApiUtils'
|
||||
import {rangeToText} from '../utils/Version'
|
||||
|
||||
export const defaultAppContext = {
|
||||
more: false,
|
||||
complete: false,
|
||||
explore: false,
|
||||
share: false,
|
||||
fetch: false,
|
||||
tab: 'quicksearch',
|
||||
theme: 'light',
|
||||
language: 'zh',
|
||||
config: {},
|
||||
groupsOpened: [],
|
||||
dependencies: {
|
||||
list: [],
|
||||
groups: [],
|
||||
},
|
||||
}
|
||||
|
||||
export function reduceDependencies(boot, items) {
|
||||
const groups = []
|
||||
const list = []
|
||||
const getParent = (m, name) => {
|
||||
return m.find(item => item.group === name)
|
||||
}
|
||||
for (let i = 0; i < items.length; i += 1) {
|
||||
let message = ''
|
||||
const dep = items[i]
|
||||
let parent = getParent(groups, dep.group)
|
||||
if (!parent) {
|
||||
parent = {
|
||||
group: dep.group,
|
||||
items: [],
|
||||
}
|
||||
groups.push(parent)
|
||||
}
|
||||
const valid = isValidDependency(boot, dep)
|
||||
if (!valid) {
|
||||
message = `Requires Spring Boot ${rangeToText(
|
||||
get(dep, 'versionRequirement')
|
||||
)}.`
|
||||
}
|
||||
parent.items.push({ ...dep, valid, message })
|
||||
list.push({ ...dep, valid, message })
|
||||
}
|
||||
return {
|
||||
list,
|
||||
groups,
|
||||
}
|
||||
}
|
||||
|
||||
export function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'UPDATE': {
|
||||
const newState = { ...state }
|
||||
const keysContext = Object.keys(defaultAppContext)
|
||||
const keys = Object.keys(get(action, 'payload', {}))
|
||||
keys.map(key => {
|
||||
if (keysContext.indexOf(key) === -1) {
|
||||
throw Error('Error AppProvider, invalid paylaod field action')
|
||||
}
|
||||
const value = get(action, `payload.${key}`)
|
||||
set(newState, key, value)
|
||||
if (key === 'theme') {
|
||||
localStorage.setItem('springtheme', value)
|
||||
}
|
||||
return key
|
||||
})
|
||||
return newState
|
||||
}
|
||||
case 'TOGGLE_GROUP': {
|
||||
const id = get(action, 'payload.id')
|
||||
let groupsOpened = [...state.groupsOpened]
|
||||
if (groupsOpened.indexOf(id) > -1) {
|
||||
groupsOpened = [...groupsOpened.filter(g => g !== id)]
|
||||
} else {
|
||||
groupsOpened = [...groupsOpened, id]
|
||||
}
|
||||
return { ...state, groupsOpened }
|
||||
}
|
||||
case 'UPDATE_DEPENDENCIES': {
|
||||
const dependencies = reduceDependencies(
|
||||
get(action, 'payload.boot'),
|
||||
get(state, 'config.lists.dependencies')
|
||||
)
|
||||
return { ...state, dependencies }
|
||||
}
|
||||
case 'SHARE_UPDATE': {
|
||||
const share = get(action, 'payload.open', false)
|
||||
return { ...state, share }
|
||||
}
|
||||
case 'EXPLORE_UPDATE': {
|
||||
const explore = get(action, 'payload.open', false)
|
||||
return { ...state, explore }
|
||||
}
|
||||
case 'FETCH_UPDATE': {
|
||||
const fetch = get(action, 'payload.open', false)
|
||||
return { ...state, fetch }
|
||||
}
|
||||
case 'COMPLETE': {
|
||||
const json = get(action, 'payload', {})
|
||||
const dependencies = reduceDependencies(
|
||||
get(json, 'defaultValues.boot'),
|
||||
get(json, 'lists.dependencies')
|
||||
)
|
||||
return { ...state, complete: true, config: json, dependencies }
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export const AppContext = React.createContext({ ...defaultAppContext })
|
||||
|
||||
export function AppProvider({ children }) {
|
||||
const theme = useTheme()
|
||||
const [state, dispatch] = useReducer(reducer, { ...defaultAppContext, theme })
|
||||
return (
|
||||
<AppContext.Provider value={{ ...state, dispatch }}>
|
||||
{children}
|
||||
</AppContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
AppProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
}
|
||||
132
initializer-page/src/components/reducer/Initializr.js
Normal file
132
initializer-page/src/components/reducer/Initializr.js
Normal file
@@ -0,0 +1,132 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import get from 'lodash.get'
|
||||
import set from 'lodash.set'
|
||||
import React, {useReducer} from 'react'
|
||||
|
||||
import {getShareUrl, parseParams} from '../utils/ApiUtils'
|
||||
|
||||
export const defaultInitializrContext = {
|
||||
values: {
|
||||
project: '',
|
||||
language: '',
|
||||
architecture: '',
|
||||
boot: '',
|
||||
meta: {
|
||||
name: '',
|
||||
group: '',
|
||||
artifact: '',
|
||||
description: '',
|
||||
packaging: '',
|
||||
packageName: '',
|
||||
java: '',
|
||||
},
|
||||
dependencies: [],
|
||||
share: [],
|
||||
},
|
||||
share: '',
|
||||
errors: {},
|
||||
warnings: {},
|
||||
}
|
||||
|
||||
export function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'COMPLETE': {
|
||||
const json = get(action, 'payload')
|
||||
const defaultValues = {
|
||||
...get(json, 'defaultValues'),
|
||||
meta: get(json, 'defaultValues.meta'),
|
||||
}
|
||||
return {
|
||||
values: defaultValues,
|
||||
share: getShareUrl(defaultValues),
|
||||
errors: {},
|
||||
warnings: {},
|
||||
}
|
||||
}
|
||||
case 'UPDATE': {
|
||||
const changes = get(action, 'payload')
|
||||
let errors = { ...state.errors }
|
||||
let meta = { ...get(state, 'values.meta') }
|
||||
if (get(changes, 'meta')) {
|
||||
meta = { ...meta, ...get(changes, 'meta') }
|
||||
}
|
||||
if (get(changes, 'boot')) {
|
||||
const { boot, ...err } = errors
|
||||
errors = err
|
||||
}
|
||||
if (get(changes, 'meta.group')) {
|
||||
set(
|
||||
meta,
|
||||
'packageName',
|
||||
`${get(meta, 'group')}.${get(meta, 'artifact')}`
|
||||
)
|
||||
}
|
||||
if (get(changes, 'meta.artifact')) {
|
||||
set(
|
||||
meta,
|
||||
'packageName',
|
||||
`${get(meta, 'group')}.${get(meta, 'artifact')}`
|
||||
)
|
||||
set(meta, 'name', `${get(meta, 'artifact')}`)
|
||||
}
|
||||
const values = {
|
||||
...get(state, 'values'),
|
||||
...changes,
|
||||
meta,
|
||||
}
|
||||
return { ...state, values, share: getShareUrl(values), errors }
|
||||
}
|
||||
case 'LOAD': {
|
||||
const params = get(action, 'payload.params')
|
||||
const lists = get(action, 'payload.lists')
|
||||
const { values, errors, warnings } = parseParams(
|
||||
state.values,
|
||||
params,
|
||||
lists
|
||||
)
|
||||
return { ...state, values, errors, warnings, share: getShareUrl(values) }
|
||||
}
|
||||
case 'ADD_DEPENDENCY': {
|
||||
const dependency = get(action, 'payload.id')
|
||||
const values = { ...get(state, 'values') }
|
||||
if(get(values, 'dependencies', []).filter(item => item === dependency).length == 0){
|
||||
values.dependencies = [...get(values, 'dependencies'), dependency]
|
||||
}
|
||||
return { ...state, values, share: getShareUrl(values) }
|
||||
}
|
||||
case 'REMOVE_DEPENDENCY': {
|
||||
const dependency = get(action, 'payload.id')
|
||||
const values = { ...get(state, 'values') }
|
||||
values.dependencies = [
|
||||
...get(values, 'dependencies').filter(dep => dep !== dependency),
|
||||
]
|
||||
return { ...state, values, share: getShareUrl(values) }
|
||||
}
|
||||
case 'CLEAR_WARNINGS': {
|
||||
return { ...state, warnings: {} }
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export const InitializrContext = React.createContext({
|
||||
...defaultInitializrContext,
|
||||
})
|
||||
|
||||
export function InitializrProvider({ children }) {
|
||||
const [state, dispatch] = useReducer(reducer, { ...defaultInitializrContext })
|
||||
return (
|
||||
<InitializrContext.Provider value={{ ...state, dispatch }}>
|
||||
{children}
|
||||
</InitializrContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
InitializrProvider.defaultProps = {
|
||||
children: null,
|
||||
}
|
||||
|
||||
InitializrProvider.propTypes = {
|
||||
children: PropTypes.node,
|
||||
}
|
||||
137
initializer-page/src/components/reducer/__tests__/App.js
Normal file
137
initializer-page/src/components/reducer/__tests__/App.js
Normal file
@@ -0,0 +1,137 @@
|
||||
import get from 'lodash.get'
|
||||
|
||||
import {reducer} from '../App'
|
||||
|
||||
let state = {}
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
more: false,
|
||||
complete: false,
|
||||
explore: false,
|
||||
share: false,
|
||||
tab: 'quicksearch',
|
||||
theme: 'light',
|
||||
config: {},
|
||||
groupsOpened: [],
|
||||
dependencies: {
|
||||
list: [],
|
||||
groups: [],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe('UPDATE action', () => {
|
||||
it('should reduce the state', () => {
|
||||
let result = reducer(state, { type: 'UPDATE', payload: { more: true } })
|
||||
expect(get(result, 'more')).toBe(true)
|
||||
result = reducer(state, { type: 'UPDATE', payload: { theme: 'dark' } })
|
||||
expect(get(result, 'theme')).toBe('dark')
|
||||
})
|
||||
it('should throw an error', () => {
|
||||
expect(() => {
|
||||
reducer(state, { type: 'UPDATE', payload: { foo: 'bar' } })
|
||||
}).toThrow(Error)
|
||||
})
|
||||
})
|
||||
|
||||
describe('TOGGLE_GROUP action', () => {
|
||||
it('should add/remove a group', () => {
|
||||
let result = reducer(state, {
|
||||
type: 'TOGGLE_GROUP',
|
||||
payload: { id: 'foo1' },
|
||||
})
|
||||
expect(get(result, 'groupsOpened').length).toBe(1)
|
||||
expect(get(result, 'groupsOpened[0]')).toBe('foo1')
|
||||
|
||||
result = reducer(result, {
|
||||
type: 'TOGGLE_GROUP',
|
||||
payload: { id: 'foo2' },
|
||||
})
|
||||
expect(get(result, 'groupsOpened').length).toBe(2)
|
||||
expect(get(result, 'groupsOpened[0]')).toBe('foo1')
|
||||
expect(get(result, 'groupsOpened[1]')).toBe('foo2')
|
||||
|
||||
result = reducer(result, {
|
||||
type: 'TOGGLE_GROUP',
|
||||
payload: { id: 'foo1' },
|
||||
})
|
||||
expect(get(result, 'groupsOpened').length).toBe(1)
|
||||
expect(get(result, 'groupsOpened[0]')).toBe('foo2')
|
||||
})
|
||||
})
|
||||
|
||||
describe('UPDATE_DEPENDENCIES action', () => {
|
||||
it('should update dependencies', () => {
|
||||
state.config = {
|
||||
lists: {
|
||||
dependencies: [
|
||||
{
|
||||
id: 'foo1',
|
||||
group: 'bar2',
|
||||
versionRange: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
const result = reducer(state, {
|
||||
type: 'UPDATE_DEPENDENCIES',
|
||||
payload: {
|
||||
boot: '1.0.0.RELEASE',
|
||||
},
|
||||
})
|
||||
expect(get(result, 'dependencies.list').length).toBe(1)
|
||||
expect(get(result, 'dependencies.list[0].id')).toBe('foo1')
|
||||
expect(get(result, 'dependencies.list[0].valid')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('SHARE_UPDATE/EXPLORE_UPDATE action', () => {
|
||||
it('should add/remove a group', () => {
|
||||
let result = reducer(state, {
|
||||
type: 'SHARE_UPDATE',
|
||||
payload: { open: true },
|
||||
})
|
||||
expect(get(result, 'share')).toBe(true)
|
||||
result = reducer(result, {
|
||||
type: 'SHARE_UPDATE',
|
||||
payload: { open: false },
|
||||
})
|
||||
expect(get(result, 'share')).toBe(false)
|
||||
result = reducer(result, {
|
||||
type: 'EXPLORE_UPDATE',
|
||||
payload: { open: true },
|
||||
})
|
||||
expect(get(result, 'explore')).toBe(true)
|
||||
result = reducer(result, {
|
||||
type: 'EXPLORE_UPDATE',
|
||||
payload: { open: false },
|
||||
})
|
||||
expect(get(result, 'explore')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('COMPLETE action', () => {
|
||||
it('should add/remove a group', () => {
|
||||
const result = reducer(state, {
|
||||
type: 'COMPLETE',
|
||||
payload: {
|
||||
defaultValues: {
|
||||
boot: '1.0.0',
|
||||
},
|
||||
lists: {
|
||||
dependencies: [
|
||||
{
|
||||
id: 'foo1',
|
||||
group: 'bar2',
|
||||
versionRange: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(get(result, 'complete')).toBe(true)
|
||||
expect(get(result, 'config.json')).not.toBe(null)
|
||||
expect(get(result, 'dependencies')).not.toBe(null)
|
||||
expect(get(result, 'dependencies.list').length).toBe(1)
|
||||
})
|
||||
})
|
||||
289
initializer-page/src/components/reducer/__tests__/Initializr.js
Normal file
289
initializer-page/src/components/reducer/__tests__/Initializr.js
Normal file
@@ -0,0 +1,289 @@
|
||||
import get from 'lodash.get'
|
||||
|
||||
import MockClient from '../../../../dev/api.json'
|
||||
import {getDefaultValues, getLists} from '../../utils/ApiUtils'
|
||||
import {reducer} from '../Initializr'
|
||||
|
||||
const defaultValues = getDefaultValues({ ...MockClient })
|
||||
|
||||
let state = {}
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
values: { ...defaultValues, meta: { ...defaultValues.meta } },
|
||||
share: '',
|
||||
errors: {},
|
||||
warnings: {},
|
||||
}
|
||||
})
|
||||
|
||||
describe('COMPLETE action', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
values: {
|
||||
project: '',
|
||||
language: '',
|
||||
architecture: '',
|
||||
boot: '',
|
||||
meta: {
|
||||
name: '',
|
||||
group: '',
|
||||
artifact: '',
|
||||
description: '',
|
||||
packaging: '',
|
||||
packageName: '',
|
||||
java: '',
|
||||
},
|
||||
dependencies: [],
|
||||
},
|
||||
share: '',
|
||||
errors: {},
|
||||
warnings: {},
|
||||
}
|
||||
})
|
||||
it('should reduce the state', () => {
|
||||
const result = reducer(state, {
|
||||
type: 'COMPLETE',
|
||||
payload: {
|
||||
defaultValues,
|
||||
},
|
||||
})
|
||||
expect(get(result, 'share')).toBe(
|
||||
'type=maven-project&language=java&architecture=none&platformVersion=2.2.0.RELEASE&packaging=jar&jvmVersion=1.8&groupId=com.example&artifactId=demo&name=demo&description=Demo%20project%20for%20Spring%20Boot&packageName=com.example.demo'
|
||||
)
|
||||
expect(get(result, 'values.project')).toBe('maven-project')
|
||||
expect(get(result, 'values.language')).toBe('java')
|
||||
expect(get(result, 'values.architecture')).toBe('none')
|
||||
expect(get(result, 'values.boot')).toBe('2.2.0.RELEASE')
|
||||
expect(get(result, 'values.meta.name')).toBe('demo')
|
||||
expect(get(result, 'values.meta.group')).toBe('com.example')
|
||||
expect(get(result, 'values.meta.artifact')).toBe('demo')
|
||||
expect(get(result, 'values.meta.description')).toBe(
|
||||
'Demo project for Spring Boot'
|
||||
)
|
||||
expect(get(result, 'values.meta.packaging')).toBe('jar')
|
||||
expect(get(result, 'values.meta.packageName')).toBe('com.example.demo')
|
||||
expect(get(result, 'values.meta.java')).toBe('1.8')
|
||||
expect(get(result, 'values.dependencies').length).toBe(0)
|
||||
expect(Object.keys(get(result, 'errors')).length).toBe(0)
|
||||
expect(Object.keys(get(result, 'warnings')).length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('UPDATE action', () => {
|
||||
it('should reduce the state (type)', () => {
|
||||
const result = reducer(state, {
|
||||
type: 'UPDATE',
|
||||
payload: {
|
||||
project: 'maven-project',
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.project')).toBe('maven-project')
|
||||
})
|
||||
it('should reduce the state (language)', () => {
|
||||
const result = reducer(state, {
|
||||
type: 'UPDATE',
|
||||
payload: {
|
||||
language: 'kotlin',
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.language')).toBe('kotlin')
|
||||
})
|
||||
it('should reduce the state (boot)', () => {
|
||||
const result = reducer(state, {
|
||||
type: 'UPDATE',
|
||||
payload: {
|
||||
boot: '2.1.10.BUILD-SNAPSHOT',
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.boot')).toBe('2.1.10.BUILD-SNAPSHOT')
|
||||
})
|
||||
it('should reduce the state (meta name)', () => {
|
||||
const result = reducer(state, {
|
||||
type: 'UPDATE',
|
||||
payload: {
|
||||
meta: {
|
||||
name: 'demo1',
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.meta.name')).toBe('demo1')
|
||||
})
|
||||
it('should reduce the state (meta group)', () => {
|
||||
state.values.meta.artifact = 'demo3'
|
||||
const result = reducer(state, {
|
||||
type: 'UPDATE',
|
||||
payload: {
|
||||
meta: {
|
||||
group: 'com.example1',
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.meta.group')).toBe('com.example1')
|
||||
expect(get(result, 'values.meta.packageName')).toBe('com.example1.demo3')
|
||||
})
|
||||
it('should reduce the state (meta artifact)', () => {
|
||||
state.values.meta.group = 'com.example3'
|
||||
const result = reducer(state, {
|
||||
type: 'UPDATE',
|
||||
payload: {
|
||||
meta: {
|
||||
artifact: 'demo2',
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.meta.artifact')).toBe('demo2')
|
||||
expect(get(result, 'values.meta.packageName')).toBe('com.example3.demo2')
|
||||
})
|
||||
it('should reduce the state (meta description)', () => {
|
||||
const result = reducer(state, {
|
||||
type: 'UPDATE',
|
||||
payload: {
|
||||
meta: {
|
||||
description: 'desc',
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.meta.description')).toBe('desc')
|
||||
})
|
||||
it('should reduce the state (meta packaging)', () => {
|
||||
const result = reducer(state, {
|
||||
type: 'UPDATE',
|
||||
payload: {
|
||||
meta: {
|
||||
packaging: 'war',
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.meta.packaging')).toBe('war')
|
||||
})
|
||||
it('should reduce the state (meta packageName)', () => {
|
||||
const result = reducer(state, {
|
||||
type: 'UPDATE',
|
||||
payload: {
|
||||
meta: {
|
||||
packageName: 'com1.example2.demo3',
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.meta.packageName')).toBe('com1.example2.demo3')
|
||||
})
|
||||
it('should reduce the state (meta java)', () => {
|
||||
const result = reducer(state, {
|
||||
type: 'UPDATE',
|
||||
payload: {
|
||||
meta: {
|
||||
java: '1.9',
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.meta.java')).toBe('1.9')
|
||||
})
|
||||
})
|
||||
|
||||
describe('LOAD action', () => {
|
||||
it('should reduce the state', () => {
|
||||
const json = { ...MockClient }
|
||||
const listsValues = getLists(json)
|
||||
const result = reducer(state, {
|
||||
type: 'LOAD',
|
||||
payload: {
|
||||
params: {
|
||||
artifactId: 'demo1',
|
||||
description: 'Demo1 project for Spring Boot',
|
||||
groupId: 'com.example1',
|
||||
jvmVersion: '1.8',
|
||||
language: 'java',
|
||||
architecture: 'none',
|
||||
name: 'demo1',
|
||||
packageName: 'com.example1.demo1',
|
||||
packaging: 'war',
|
||||
platformVersion: '2.2.0.RELEASE',
|
||||
type: 'gradle-project',
|
||||
},
|
||||
lists: listsValues,
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.project')).toBe('gradle-project')
|
||||
expect(get(result, 'values.language')).toBe('java')
|
||||
expect(get(result, 'values.boot')).toBe('2.2.0.RELEASE')
|
||||
expect(get(result, 'values.meta.name')).toBe('demo1')
|
||||
expect(get(result, 'values.meta.group')).toBe('com.example1')
|
||||
expect(get(result, 'values.meta.artifact')).toBe('demo1')
|
||||
expect(get(result, 'values.meta.description')).toBe(
|
||||
'Demo1 project for Spring Boot'
|
||||
)
|
||||
expect(get(result, 'values.meta.packaging')).toBe('war')
|
||||
expect(get(result, 'values.meta.packageName')).toBe('com.example1.demo1')
|
||||
expect(get(result, 'values.meta.java')).toBe('1.8')
|
||||
expect(get(result, 'values.dependencies').length).toBe(0)
|
||||
expect(Object.keys(get(result, 'errors')).length).toBe(0)
|
||||
expect(Object.keys(get(result, 'warnings')).length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ADD_DEPENDENCY action', () => {
|
||||
it('should reduce the state', () => {
|
||||
let result = reducer(state, {
|
||||
type: 'ADD_DEPENDENCY',
|
||||
payload: {
|
||||
id: 'foo1',
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.dependencies').length).toBe(1)
|
||||
expect(get(result, 'values.dependencies[0]')).toBe('foo1')
|
||||
result = reducer(result, {
|
||||
type: 'ADD_DEPENDENCY',
|
||||
payload: {
|
||||
id: 'foo2',
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.dependencies').length).toBe(2)
|
||||
expect(get(result, 'values.dependencies[0]')).toBe('foo1')
|
||||
expect(get(result, 'values.dependencies[1]')).toBe('foo2')
|
||||
})
|
||||
})
|
||||
|
||||
describe('REMOVE_DEPENDENCY action', () => {
|
||||
it('should reduce the state', () => {
|
||||
let result = reducer(state, {
|
||||
type: 'ADD_DEPENDENCY',
|
||||
payload: {
|
||||
id: 'foo1',
|
||||
},
|
||||
})
|
||||
result = reducer(result, {
|
||||
type: 'ADD_DEPENDENCY',
|
||||
payload: {
|
||||
id: 'foo2',
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.dependencies').length).toBe(2)
|
||||
expect(get(result, 'values.dependencies[0]')).toBe('foo1')
|
||||
expect(get(result, 'values.dependencies[1]')).toBe('foo2')
|
||||
result = reducer(result, {
|
||||
type: 'REMOVE_DEPENDENCY',
|
||||
payload: {
|
||||
id: 'foo1',
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.dependencies').length).toBe(1)
|
||||
expect(get(result, 'values.dependencies[0]')).toBe('foo2')
|
||||
result = reducer(result, {
|
||||
type: 'REMOVE_DEPENDENCY',
|
||||
payload: {
|
||||
id: 'foo2',
|
||||
},
|
||||
})
|
||||
expect(get(result, 'values.dependencies').length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('CLEAR_WARNINGS action', () => {
|
||||
it('should reduce the state', () => {
|
||||
state.warnings = { foo: 'bar' }
|
||||
const result = reducer(state, {
|
||||
type: 'CLEAR_WARNINGS',
|
||||
})
|
||||
expect(Object.keys(get(result, 'warnings')).length).toBe(0)
|
||||
})
|
||||
})
|
||||
326
initializer-page/src/components/utils/ApiUtils.js
Normal file
326
initializer-page/src/components/utils/ApiUtils.js
Normal file
@@ -0,0 +1,326 @@
|
||||
import get from 'lodash.get'
|
||||
import querystring from 'querystring'
|
||||
import set from 'lodash.set'
|
||||
|
||||
import Extend from '../../Extend.json'
|
||||
import {isInRange, parseReleases, parseVersion} from './Version'
|
||||
|
||||
const PROPERTIES_MAPPING_URL = {
|
||||
type: 'project',
|
||||
language: 'language',
|
||||
architecture: 'architecture',
|
||||
platformVersion: 'boot',
|
||||
packaging: 'meta.packaging',
|
||||
jvmVersion: 'meta.java',
|
||||
groupId: 'meta.group',
|
||||
artifactId: 'meta.artifact',
|
||||
name: 'meta.name',
|
||||
description: 'meta.description',
|
||||
packageName: 'meta.packageName',
|
||||
dependencies: 'dependencies',
|
||||
}
|
||||
|
||||
export const getInfo = function getInfo(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetch(`${url}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/vnd.initializr.v2.1+json',
|
||||
},
|
||||
})
|
||||
.then(
|
||||
response => response.json(),
|
||||
() => {
|
||||
reject()
|
||||
return null
|
||||
}
|
||||
)
|
||||
.then(data => {
|
||||
if (data) {
|
||||
resolve(data)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const getShareUrl = values => {
|
||||
const props = {}
|
||||
Object.keys(PROPERTIES_MAPPING_URL).forEach(key => {
|
||||
const key2 = get(PROPERTIES_MAPPING_URL, key)
|
||||
const value = get(values, key2)
|
||||
if (key !== 'dependencies') {
|
||||
set(props, key, value)
|
||||
}
|
||||
})
|
||||
let params = `${querystring.stringify(props)}`
|
||||
if (get(values, 'dependencies', []).length > 0) {
|
||||
params = `${params}&dependencies=${get(values, 'dependencies').join(',')}`
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
export const isValidParams = params => {
|
||||
return (
|
||||
Object.keys(params)
|
||||
.map(entry => {
|
||||
return !!get(PROPERTIES_MAPPING_URL, entry, null)
|
||||
})
|
||||
.filter(item => !!item).length > 0
|
||||
)
|
||||
}
|
||||
|
||||
export const parseParams = (values, queryParams, lists) => {
|
||||
const errors = {}
|
||||
const warnings = {}
|
||||
if (isValidParams(queryParams)) {
|
||||
Object.keys(queryParams).forEach(entry => {
|
||||
const key = get(PROPERTIES_MAPPING_URL, entry)
|
||||
if (key) {
|
||||
const value = get(queryParams, entry, '').toLowerCase()
|
||||
switch (key) {
|
||||
case 'project':
|
||||
case 'language':
|
||||
case 'architecture':
|
||||
case 'meta.packaging':
|
||||
case 'meta.java': {
|
||||
const list = get(lists, key, [])
|
||||
const res = list.find(a => a.key.toLowerCase() === value)
|
||||
if (res) {
|
||||
set(values, key, res.key)
|
||||
} else {
|
||||
const currentValue = list.find(
|
||||
a => a.key.toLowerCase() === get(values, key)
|
||||
)
|
||||
set(warnings, key, {
|
||||
value: get(queryParams, entry, ''),
|
||||
select: currentValue.text,
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'boot': {
|
||||
const list = get(lists, key, [])
|
||||
const res = list.find(a => a.key.toLowerCase() === value)
|
||||
let error = false
|
||||
if (res) {
|
||||
set(values, key, res.key)
|
||||
} else {
|
||||
error = true
|
||||
let versionMajor = value
|
||||
if (versionMajor.indexOf('.x') === -1) {
|
||||
versionMajor = get(parseVersion(versionMajor), 'major', '')
|
||||
}
|
||||
if (versionMajor.indexOf('.x') > -1) {
|
||||
const releases = parseReleases(list).filter(
|
||||
release =>
|
||||
release.major.toLowerCase() === versionMajor.toLowerCase()
|
||||
)
|
||||
if (releases.length > 0) {
|
||||
const release = releases.reduce((p, c) => {
|
||||
if (p.qualify > c.qualify) {
|
||||
return p
|
||||
}
|
||||
if (p.qualify === c.qualify) {
|
||||
if (p.minor > c.minor) {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return c
|
||||
}, releases[0])
|
||||
|
||||
if (release) {
|
||||
error = false
|
||||
set(values, key, release.version)
|
||||
const currentValue = list.find(
|
||||
a => a.key.toLowerCase() === release.version.toLowerCase()
|
||||
)
|
||||
set(warnings, key, {
|
||||
value: get(queryParams, entry, ''),
|
||||
select: currentValue.text,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (error) {
|
||||
set(errors, 'boot', {
|
||||
value: get(queryParams, entry, ''),
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'dependencies': {
|
||||
const depsWarning = []
|
||||
const newVal = value
|
||||
.split(',')
|
||||
.map(item => {
|
||||
const dep = get(lists, 'dependencies').find(
|
||||
d => d.id === item.trim()
|
||||
)
|
||||
if (dep) {
|
||||
return dep.id
|
||||
}
|
||||
depsWarning.push(item)
|
||||
return null
|
||||
})
|
||||
.filter(item => !!item)
|
||||
|
||||
if (depsWarning.length > 0) {
|
||||
set(warnings, key, {
|
||||
value: depsWarning.join(', '),
|
||||
})
|
||||
}
|
||||
set(values, key, newVal)
|
||||
break
|
||||
}
|
||||
default:
|
||||
set(values, key, get(queryParams, entry, ''))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return {
|
||||
values,
|
||||
errors,
|
||||
warnings,
|
||||
}
|
||||
}
|
||||
|
||||
export const getLists = json => {
|
||||
const deps = []
|
||||
get(json, 'dependencies.values', []).forEach(group => {
|
||||
group.values.forEach(item => {
|
||||
const extend = Extend.find(it => it.id === get(item, 'id', ''))
|
||||
const val = {
|
||||
id: `${get(item, 'id', '')}`,
|
||||
name: `${get(item, 'name', '')}`,
|
||||
group: `${group.name}`,
|
||||
description: `${get(item, 'description', '')}`,
|
||||
versionRange: `${get(item, 'versionRange', '')}`,
|
||||
versionRequirement: `${get(item, 'versionRange', '')}`,
|
||||
weight: get(extend, 'weight', 50),
|
||||
}
|
||||
deps.push(val)
|
||||
})
|
||||
})
|
||||
return {
|
||||
project: get(json, 'type.values', [])
|
||||
.filter(type => type.action === '/starter.zip')
|
||||
.map(type => ({
|
||||
key: `${type.id}`,
|
||||
text: `${type.name}`,
|
||||
})),
|
||||
language: get(json, 'language.values', []).map(language => ({
|
||||
key: `${language.id}`,
|
||||
text: `${language.name}`,
|
||||
})),
|
||||
architecture: get(json, 'architecture.values', []).map(architecture => ({
|
||||
key: `${architecture.id}`,
|
||||
text: `${architecture.name}`,
|
||||
dependencies: get(architecture, 'dependOn', []),
|
||||
})),
|
||||
boot: get(json, 'bootVersion.values', []).map(boot => ({
|
||||
key: `${boot.id}`,
|
||||
text: `${boot.name}`,
|
||||
})),
|
||||
meta: {
|
||||
java: get(json, 'javaVersion.values', []).map(java => ({
|
||||
key: `${java.id}`,
|
||||
text: `${java.name}`,
|
||||
})),
|
||||
packaging: get(json, 'packaging.values', []).map(packaging => ({
|
||||
key: `${packaging.id}`,
|
||||
text: `${packaging.name}`,
|
||||
})),
|
||||
},
|
||||
dependencies: deps,
|
||||
}
|
||||
}
|
||||
|
||||
export const getDefaultValues = json => {
|
||||
return {
|
||||
project: get(json, 'type.default'),
|
||||
language: get(json, 'language.default'),
|
||||
architecture: get(json, 'architecture.default'),
|
||||
boot: get(json, 'bootVersion.default'),
|
||||
meta: {
|
||||
name: get(json, 'name.default'),
|
||||
group: get(json, 'groupId.default'),
|
||||
artifact: get(json, 'artifactId.default'),
|
||||
description: get(json, 'description.default'),
|
||||
packaging: get(json, 'packaging.default'),
|
||||
packageName: get(json, 'packageName.default'),
|
||||
java: get(json, 'javaVersion.default'),
|
||||
},
|
||||
share: get(json, 'share.default'),
|
||||
dependencies: [],
|
||||
availableCount: [],
|
||||
}
|
||||
}
|
||||
|
||||
export const getConfig = json => {
|
||||
return {
|
||||
lists: getLists(json),
|
||||
defaultValues: getDefaultValues(json),
|
||||
}
|
||||
}
|
||||
|
||||
export const isValidDependency = function isValidDependency(boot, dependency) {
|
||||
if (!dependency) {
|
||||
return false
|
||||
}
|
||||
return get(dependency, 'versionRange')
|
||||
? isInRange(boot, get(dependency, 'versionRange'))
|
||||
: true
|
||||
}
|
||||
|
||||
export const getQueryString = function getQueryString(values, config) {
|
||||
const params = querystring.stringify({
|
||||
type: get(values, 'project'),
|
||||
language: get(values, 'language'),
|
||||
architecture: get(values, 'architecture'),
|
||||
bootVersion: get(values, 'boot'),
|
||||
baseDir: get(values, 'meta.artifact'),
|
||||
groupId: get(values, 'meta.group'),
|
||||
artifactId: get(values, 'meta.artifact'),
|
||||
name: get(values, 'meta.name'),
|
||||
description: get(values, 'meta.description'),
|
||||
packageName: get(values, 'meta.packageName'),
|
||||
packaging: get(values, 'meta.packaging'),
|
||||
javaVersion: get(values, 'meta.java'),
|
||||
})
|
||||
let paramsDependencies = get(values, 'dependencies', [])
|
||||
.map(dependency => {
|
||||
const dep = config.find(it => it.id === dependency)
|
||||
return isValidDependency(get(values, 'boot'), dep) ? dependency : null
|
||||
})
|
||||
.filter(dep => !!dep)
|
||||
.join(',')
|
||||
if (paramsDependencies) {
|
||||
paramsDependencies = `&dependencies=${paramsDependencies}`
|
||||
}
|
||||
|
||||
return `${params}${paramsDependencies}`
|
||||
}
|
||||
|
||||
export const getProject = function getProject(url, values, config) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const paramsStr = getQueryString(values, config)
|
||||
|
||||
fetch(`${url}?${paramsStr}`, {
|
||||
method: 'GET',
|
||||
}).then(
|
||||
response => {
|
||||
if (response.status === 200) {
|
||||
resolve(response.blob())
|
||||
return
|
||||
}
|
||||
reject()
|
||||
},
|
||||
() => {
|
||||
reject()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
52
initializer-page/src/components/utils/Hash.js
Normal file
52
initializer-page/src/components/utils/Hash.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import queryString from 'query-string'
|
||||
import {toast} from 'react-toastify'
|
||||
import {useContext, useEffect, useState} from 'react'
|
||||
|
||||
import {AppContext} from '../reducer/App'
|
||||
import {InitializrContext} from '../reducer/Initializr'
|
||||
import {isValidParams} from './ApiUtils'
|
||||
|
||||
const getHash = () => {
|
||||
return window.location.hash
|
||||
}
|
||||
|
||||
const clearHash = () => {
|
||||
if (window.location.hash) {
|
||||
if (window.history.pushState) {
|
||||
window.history.pushState(null, null, window.location.pathname)
|
||||
} else {
|
||||
window.history.hash = ``
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function useHash() {
|
||||
const [hash, setHash] = useState(getHash())
|
||||
|
||||
const { dispatch } = useContext(InitializrContext)
|
||||
const { config, complete } = useContext(AppContext)
|
||||
|
||||
useEffect(() => {
|
||||
const handler = () => {
|
||||
setHash(getHash())
|
||||
}
|
||||
window.addEventListener('hashchange', handler)
|
||||
return () => {
|
||||
window.removeEventListener('hashchange', handler)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (complete && hash) {
|
||||
const params = queryString.parse(`?${hash.substr(2)}`)
|
||||
dispatch({ type: 'LOAD', payload: { params, lists: config.lists } })
|
||||
clearHash()
|
||||
setHash('')
|
||||
if (isValidParams(params)) {
|
||||
toast.success(`分享配置 已加载.`)
|
||||
}
|
||||
}
|
||||
}, [complete, hash, dispatch, config])
|
||||
|
||||
return null
|
||||
}
|
||||
8
initializer-page/src/components/utils/NoScroll.js
Normal file
8
initializer-page/src/components/utils/NoScroll.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export const noScroll = {
|
||||
on: () => {
|
||||
document.documentElement.style.overflow = 'hidden'
|
||||
},
|
||||
off: () => {
|
||||
document.documentElement.style.overflow = ''
|
||||
},
|
||||
}
|
||||
18
initializer-page/src/components/utils/Theme.js
Normal file
18
initializer-page/src/components/utils/Theme.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import {useState} from 'react'
|
||||
|
||||
function getTheme() {
|
||||
const isDarkConfig =
|
||||
window.matchMedia &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
|
||||
const theme = localStorage.getItem('springtheme')
|
||||
if (!theme) {
|
||||
return isDarkConfig ? 'dark' : 'light'
|
||||
}
|
||||
return theme
|
||||
}
|
||||
|
||||
export default function useTheme() {
|
||||
const [darkTheme] = useState(getTheme())
|
||||
return darkTheme
|
||||
}
|
||||
115
initializer-page/src/components/utils/Version.js
Normal file
115
initializer-page/src/components/utils/Version.js
Normal file
@@ -0,0 +1,115 @@
|
||||
const strictRange = /\[(.*),(.*)\]/
|
||||
const halfopenRightRange = /\[(.*),(.*)\)/
|
||||
const halfopenLeftRange = /\((.*),(.*)\]/
|
||||
const qualifiers = ['M', 'RC', 'BUILD-SNAPSHOT', 'RELEASE']
|
||||
|
||||
export const parseQualifier = version => {
|
||||
const qual = (version || '')
|
||||
.replace(/\d+/g, '')
|
||||
.replace(/\./g, ' ')
|
||||
.replace(/\s/g, '')
|
||||
return qualifiers.indexOf(qual) > -1 ? qual : 'RELEASE'
|
||||
}
|
||||
|
||||
export const parseVersion = version => {
|
||||
const r = version.toString().split('.')
|
||||
if (r.length < 2) {
|
||||
return {
|
||||
version,
|
||||
}
|
||||
}
|
||||
return {
|
||||
version,
|
||||
short: `${r[0]}.${r[1]}.${r[2]}`,
|
||||
major: `${r[0]}.${r[1]}.x`,
|
||||
qualify: qualifiers.indexOf(parseQualifier(version)),
|
||||
minor: +r[2],
|
||||
}
|
||||
}
|
||||
|
||||
export const compare = (a, b) => {
|
||||
let result
|
||||
const versionA = a.split('.')
|
||||
const versionB = b.split('.')
|
||||
if (versionA.length === 3) {
|
||||
versionA[3] = ''
|
||||
}
|
||||
if (versionB.length === 3) {
|
||||
versionB[3] = ''
|
||||
}
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
result = parseInt(versionA[i], 10) - parseInt(versionB[i], 10)
|
||||
if (result !== 0) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
const qualify = version => qualifiers.indexOf(parseQualifier(version))
|
||||
result = qualify(a) - qualify(b)
|
||||
if (result !== 0) {
|
||||
return result
|
||||
}
|
||||
return versionA[3].localeCompare(versionB[3])
|
||||
}
|
||||
|
||||
export const parseReleases = releases => {
|
||||
return releases.map(release => {
|
||||
const version = parseVersion(release.key)
|
||||
return version
|
||||
})
|
||||
}
|
||||
|
||||
export const isInRange = (version, range) => {
|
||||
if (!range) {
|
||||
return true
|
||||
}
|
||||
const strickMatch = range.match(strictRange)
|
||||
if (strickMatch) {
|
||||
return (
|
||||
compare(strickMatch[1], version) <= 0 &&
|
||||
compare(strickMatch[2], version) >= 0
|
||||
)
|
||||
}
|
||||
const horMatch = range.match(halfopenRightRange)
|
||||
if (horMatch) {
|
||||
return (
|
||||
compare(horMatch[1], version) <= 0 && compare(horMatch[2], version) > 0
|
||||
)
|
||||
}
|
||||
const holMatch = range.match(halfopenLeftRange)
|
||||
if (holMatch) {
|
||||
return (
|
||||
compare(holMatch[1], version) < 0 && compare(holMatch[2], version) >= 0
|
||||
)
|
||||
}
|
||||
return compare(range, version) <= 0
|
||||
}
|
||||
|
||||
export const rangeToText = range => {
|
||||
const strictMatch = range.match(strictRange)
|
||||
if (strictMatch) {
|
||||
return `>= ${strictMatch[1]} and <= ${strictMatch[2]}`
|
||||
}
|
||||
const horMatch = range.match(halfopenRightRange)
|
||||
if (horMatch) {
|
||||
return `>= ${horMatch[1]} and < ${horMatch[2]}`
|
||||
}
|
||||
const holMatch = range.match(halfopenLeftRange)
|
||||
if (holMatch) {
|
||||
return `> ${holMatch[1]} and <= ${holMatch[2]}`
|
||||
}
|
||||
return `>= ${range}`
|
||||
}
|
||||
|
||||
export const getValidDependencies = (boot, dependencies) => {
|
||||
return dependencies
|
||||
.map(dep => {
|
||||
const compatibility = dep.versionRange
|
||||
? isInRange(boot, dep.versionRange)
|
||||
: true
|
||||
if (!compatibility) {
|
||||
return null
|
||||
}
|
||||
return dep
|
||||
})
|
||||
.filter(d => !!d)
|
||||
}
|
||||
19
initializer-page/src/components/utils/WindowsUtils.js
Normal file
19
initializer-page/src/components/utils/WindowsUtils.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import {useState} from 'react'
|
||||
|
||||
function getProperties() {
|
||||
return {
|
||||
symb:
|
||||
window.navigator.userAgent.toLowerCase().indexOf('mac') > -1
|
||||
? '⌘'
|
||||
: 'Ctrl',
|
||||
origin: window.location.origin,
|
||||
pathname: window.location.pathname,
|
||||
}
|
||||
}
|
||||
|
||||
export default function useWindowsUtils() {
|
||||
const [symb] = useState(getProperties().symb)
|
||||
const [origin] = useState(getProperties().origin)
|
||||
const [pathname] = useState(getProperties().pathname)
|
||||
return { symb, origin, pathname }
|
||||
}
|
||||
90
initializer-page/src/components/utils/Zip.js
Normal file
90
initializer-page/src/components/utils/Zip.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import get from 'lodash.get'
|
||||
|
||||
const FILE_EXTENSION = {
|
||||
js: 'javascript',
|
||||
md: 'markdown',
|
||||
kt: 'kotlin',
|
||||
kts: 'kotlin',
|
||||
gradle: 'groovy',
|
||||
gitignore: 'git',
|
||||
java: 'java',
|
||||
xml: 'xml',
|
||||
properties: 'properties',
|
||||
groovy: 'groovy',
|
||||
html: 'html',
|
||||
css: 'css',
|
||||
}
|
||||
|
||||
export const getLanguage = file => {
|
||||
if (!file.includes(`.`)) {
|
||||
return null
|
||||
}
|
||||
const extension = file.split(`.`).pop()
|
||||
return get(FILE_EXTENSION, extension, null)
|
||||
}
|
||||
|
||||
export const createTree = (files, path, fileName, zip) => {
|
||||
return new Promise(resolve => {
|
||||
const recursive = (pfiles, ppath, pfileName, pzip, pdepth) => {
|
||||
const type = pfiles[ppath].dir ? 'folder' : 'file'
|
||||
const item = {
|
||||
type,
|
||||
filename: pfileName,
|
||||
path: `/${ppath}`,
|
||||
hidden: pdepth === 1 && type === 'folder' ? true : null,
|
||||
}
|
||||
if (type === 'folder') {
|
||||
const children = []
|
||||
pzip.folder(ppath).forEach((relativePath, file) => {
|
||||
const pathArray = relativePath.split('/')
|
||||
if (pathArray.length === 1 || (file.dir && pathArray.length === 2)) {
|
||||
children.push(
|
||||
recursive(
|
||||
pfiles,
|
||||
ppath + relativePath,
|
||||
relativePath,
|
||||
pzip,
|
||||
pdepth + 1
|
||||
)
|
||||
)
|
||||
}
|
||||
})
|
||||
item.children = children.sort((a, b) => (a.path > b.path ? 1 : -1))
|
||||
item.filename = pfileName.substring(0, pfileName.length - 1)
|
||||
} else {
|
||||
item.language = getLanguage(item.filename)
|
||||
if (item.language) {
|
||||
pfiles[ppath].async('string').then(content => {
|
||||
item.content = content
|
||||
})
|
||||
}
|
||||
}
|
||||
return item
|
||||
}
|
||||
const tree = recursive(files, path, fileName, zip, 0)
|
||||
const selected = tree.children.find(
|
||||
item =>
|
||||
['pom.xml', 'build.gradle', 'build.gradle.kts','settings.gradle','settings.gradle.kts',].indexOf(item.filename) >
|
||||
-1
|
||||
)
|
||||
if (selected) {
|
||||
files[selected.path.substring(1)].async('string').then(content => {
|
||||
selected.content = content
|
||||
resolve({ tree, selected })
|
||||
})
|
||||
} else {
|
||||
resolve({ tree, selected: null })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const findRoot = zip => {
|
||||
const root = Object.keys(zip.files).filter(filename => {
|
||||
const pathArray = filename.split('/')
|
||||
if (zip.files[filename].dir && pathArray.length === 2) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})[0]
|
||||
return root.substring(0, root.length - 1)
|
||||
}
|
||||
423
initializer-page/src/components/utils/__tests__/ApiUtils.js
Normal file
423
initializer-page/src/components/utils/__tests__/ApiUtils.js
Normal file
@@ -0,0 +1,423 @@
|
||||
import get from 'lodash.get'
|
||||
|
||||
import MockClient from '../../../../dev/api.json'
|
||||
import {getDefaultValues, getLists, getProject, getShareUrl, parseParams,} from '../ApiUtils'
|
||||
|
||||
/**
|
||||
* Function getDefaultValues
|
||||
*/
|
||||
describe('getDefaultValues', () => {
|
||||
it('parse correctly', () => {
|
||||
const json = { ...MockClient }
|
||||
const defaultValues = getDefaultValues(json)
|
||||
expect(get(defaultValues, 'project')).toBe(get(MockClient, 'type.default'))
|
||||
expect(get(defaultValues, 'boot')).toBe(
|
||||
get(MockClient, 'bootVersion.default')
|
||||
)
|
||||
expect(get(defaultValues, 'language')).toBe(
|
||||
get(MockClient, 'language.default')
|
||||
)
|
||||
expect(get(defaultValues, 'architecture')).toBe(
|
||||
get(MockClient, 'architecture.default')
|
||||
)
|
||||
expect(get(defaultValues, 'meta.name')).toBe(
|
||||
get(MockClient, 'name.default')
|
||||
)
|
||||
expect(get(defaultValues, 'meta.group')).toBe(
|
||||
get(MockClient, 'groupId.default')
|
||||
)
|
||||
expect(get(defaultValues, 'meta.artifact')).toBe(
|
||||
get(MockClient, 'artifactId.default')
|
||||
)
|
||||
expect(get(defaultValues, 'meta.description')).toBe(
|
||||
get(MockClient, 'description.default')
|
||||
)
|
||||
expect(get(defaultValues, 'meta.packaging')).toBe(
|
||||
get(MockClient, 'packaging.default')
|
||||
)
|
||||
expect(get(defaultValues, 'meta.packageName')).toBe(
|
||||
get(MockClient, 'packageName.default')
|
||||
)
|
||||
expect(get(defaultValues, 'meta.java')).toBe(
|
||||
get(MockClient, 'javaVersion.default')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Function getListValues
|
||||
*/
|
||||
describe('getListValues', () => {
|
||||
it('parse correctly the list of projects', () => {
|
||||
const json = { ...MockClient }
|
||||
const listsValues = getLists(json)
|
||||
const listProjects = get(listsValues, 'project')
|
||||
const mockProjects = get(MockClient, 'type.values').filter(
|
||||
project => project.action === '/starter.zip'
|
||||
)
|
||||
expect(listProjects.length).toBe(mockProjects.length)
|
||||
for (let i = 0; i < mockProjects.length; i += 1) {
|
||||
expect(listProjects[i].key).toBe(mockProjects[i].id)
|
||||
expect(listProjects[i].text).toBe(mockProjects[i].name)
|
||||
}
|
||||
})
|
||||
|
||||
it('parse correctly the list of languages', () => {
|
||||
const json = { ...MockClient }
|
||||
const listsValues = getLists(json)
|
||||
const listLanguages = get(listsValues, 'language')
|
||||
const mockLanguages = get(MockClient, 'language.values')
|
||||
expect(listLanguages.length).toBe(mockLanguages.length)
|
||||
for (let i = 0; i < mockLanguages.length; i += 1) {
|
||||
expect(listLanguages[i].key).toBe(mockLanguages[i].id)
|
||||
expect(listLanguages[i].text).toBe(mockLanguages[i].name)
|
||||
}
|
||||
})
|
||||
|
||||
it('parse correctly the list of boot versions', () => {
|
||||
const json = { ...MockClient }
|
||||
const listsValues = getLists(json)
|
||||
const listBootVersions = get(listsValues, 'boot')
|
||||
const mockBootVersions = get(MockClient, 'bootVersion.values')
|
||||
expect(listBootVersions.length).toBe(mockBootVersions.length)
|
||||
for (let i = 0; i < mockBootVersions.length; i += 1) {
|
||||
expect(listBootVersions[i].key).toBe(mockBootVersions[i].id)
|
||||
expect(listBootVersions[i].text).toBe(mockBootVersions[i].name)
|
||||
}
|
||||
})
|
||||
|
||||
it('parse correctly the list of java versions', () => {
|
||||
const json = { ...MockClient }
|
||||
const listsValues = getLists(json)
|
||||
const listJavaVersions = get(listsValues, 'meta.java')
|
||||
const mockJavaVersions = get(MockClient, 'javaVersion.values')
|
||||
expect(listJavaVersions.length).toBe(mockJavaVersions.length)
|
||||
for (let i = 0; i < mockJavaVersions.length; i += 1) {
|
||||
expect(listJavaVersions[i].key).toBe(mockJavaVersions[i].id)
|
||||
expect(listJavaVersions[i].text).toBe(mockJavaVersions[i].name)
|
||||
}
|
||||
})
|
||||
|
||||
it('parse correctly the list of packagings', () => {
|
||||
const json = { ...MockClient }
|
||||
const listsValues = getLists(json)
|
||||
const listPackagings = get(listsValues, 'meta.packaging')
|
||||
const mockPackagings = get(MockClient, 'packaging.values')
|
||||
expect(listPackagings.length).toBe(mockPackagings.length)
|
||||
for (let i = 0; i < mockPackagings.length; i += 1) {
|
||||
expect(listPackagings[i].key).toBe(mockPackagings[i].id)
|
||||
expect(listPackagings[i].text).toBe(mockPackagings[i].name)
|
||||
}
|
||||
})
|
||||
|
||||
it('parse correctly the list of dependencies', () => {
|
||||
const json = { ...MockClient }
|
||||
const listsValues = getLists(json)
|
||||
const listDependencies = get(listsValues, 'dependencies')
|
||||
const mockDependencies = get(MockClient, 'dependencies.values').reduce(
|
||||
(p, c) => {
|
||||
p.push(...get(c, 'values'))
|
||||
return p
|
||||
},
|
||||
[]
|
||||
)
|
||||
expect(listDependencies.length).toBe(mockDependencies.length)
|
||||
for (let i = 0; i < mockDependencies.length; i += 1) {
|
||||
expect(listDependencies[i].id).toBe(mockDependencies[i].id)
|
||||
expect(listDependencies[i].name).toBe(mockDependencies[i].name)
|
||||
expect(listDependencies[i].description).toBe(
|
||||
mockDependencies[i].description
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Function parseParams
|
||||
*/
|
||||
describe('parseParams', () => {
|
||||
it('return parameters, no warning and no error', () => {
|
||||
const json = { ...MockClient }
|
||||
const defaultValues = getDefaultValues(json)
|
||||
const listsValues = getLists(json)
|
||||
|
||||
const result = parseParams(
|
||||
defaultValues,
|
||||
{
|
||||
type: 'gradle-project',
|
||||
language: 'kotlin',
|
||||
architecture: 'none',
|
||||
platformVersion: get(defaultValues, 'boot'),
|
||||
packaging: 'war',
|
||||
jvmVersion: '11',
|
||||
groupId: 'com.example2',
|
||||
artifactId: 'demo2',
|
||||
name: 'demo2',
|
||||
description: 'Demo project for Spring Boot2',
|
||||
packageName: 'com.example2.demo2',
|
||||
dependencies: ' devtools , lombok ',
|
||||
},
|
||||
listsValues
|
||||
)
|
||||
expect(Object.keys(result.warnings).length).toBe(0)
|
||||
expect(Object.keys(result.errors).length).toBe(0)
|
||||
|
||||
expect(get(result, 'values.project')).toBe('gradle-project')
|
||||
expect(get(result, 'values.language')).toBe('kotlin')
|
||||
expect(get(result, 'values.boot')).toBe(get(defaultValues, 'boot'))
|
||||
expect(get(result, 'values.meta.packaging')).toBe('war')
|
||||
expect(get(result, 'values.meta.java')).toBe('11')
|
||||
expect(get(result, 'values.meta.group')).toBe('com.example2')
|
||||
expect(get(result, 'values.meta.artifact')).toBe('demo2')
|
||||
expect(get(result, 'values.meta.name')).toBe('demo2')
|
||||
expect(get(result, 'values.meta.description')).toBe(
|
||||
'Demo project for Spring Boot2'
|
||||
)
|
||||
expect(get(result, 'values.meta.packageName')).toBe('com.example2.demo2')
|
||||
expect(get(result, 'values.dependencies').length).toBe(2)
|
||||
})
|
||||
|
||||
it('return parameters, warnings and no error', () => {
|
||||
const json = { ...MockClient }
|
||||
const defaultValues = getDefaultValues(json)
|
||||
const listsValues = getLists(json)
|
||||
|
||||
const result = parseParams(
|
||||
defaultValues,
|
||||
{
|
||||
type: 'ant-project',
|
||||
language: 'php',
|
||||
architecture: 'none',
|
||||
platformVersion: get(defaultValues, 'boot'),
|
||||
packaging: 'tar',
|
||||
jvmVersion: '1',
|
||||
groupId: 'com.example',
|
||||
artifactId: 'demo',
|
||||
name: 'demo',
|
||||
description: 'Demo project for Spring Boot',
|
||||
packageName: 'com.example.demo',
|
||||
dependencies: 'a,devtools,b,lombok',
|
||||
},
|
||||
listsValues
|
||||
)
|
||||
expect(get(result, 'warnings.project.value')).toBe('ant-project')
|
||||
expect(get(result, 'warnings.language.value')).toBe('php')
|
||||
expect(get(result, 'warnings.meta.packaging.value')).toBe('tar')
|
||||
expect(get(result, 'warnings.meta.java.value')).toBe('1')
|
||||
|
||||
expect(get(result, 'values.meta.group')).toBe('com.example')
|
||||
expect(get(result, 'values.meta.artifact')).toBe('demo')
|
||||
expect(get(result, 'values.meta.name')).toBe('demo')
|
||||
expect(get(result, 'values.meta.description')).toBe(
|
||||
'Demo project for Spring Boot'
|
||||
)
|
||||
|
||||
expect(get(result, 'values.dependencies').length).toBe(2)
|
||||
expect(get(result, 'warnings.dependencies.value')).toBe('a, b')
|
||||
|
||||
expect(get(result, 'values.project')).toBe('maven-project')
|
||||
expect(get(result, 'values.language')).toBe('java')
|
||||
expect(get(result, 'values.boot')).toBe(get(defaultValues, 'boot'))
|
||||
expect(get(result, 'values.meta.packaging')).toBe('jar')
|
||||
expect(get(result, 'values.meta.java')).toBe('1.8')
|
||||
})
|
||||
|
||||
it('return parameters, no warning and error', () => {
|
||||
const json = { ...MockClient }
|
||||
const defaultValues = getDefaultValues(json)
|
||||
const listsValues = getLists(json)
|
||||
|
||||
const result = parseParams(
|
||||
defaultValues,
|
||||
{
|
||||
type: 'gradle-project',
|
||||
language: 'kotlin',
|
||||
architecture: 'none',
|
||||
platformVersion: '1.1.1',
|
||||
packaging: 'war',
|
||||
jvmVersion: '11',
|
||||
groupId: 'com.example2',
|
||||
artifactId: 'demo2',
|
||||
name: 'demo2',
|
||||
description: 'Demo project for Spring Boot2',
|
||||
packageName: 'com.example2.demo2',
|
||||
dependencies: ' devtools , lombok ',
|
||||
},
|
||||
listsValues
|
||||
)
|
||||
expect(Object.keys(result.warnings).length).toBe(0)
|
||||
expect(Object.keys(result.errors).length).toBe(1)
|
||||
|
||||
expect(get(result, 'values.project')).toBe('gradle-project')
|
||||
expect(get(result, 'values.language')).toBe('kotlin')
|
||||
expect(get(result, 'values.boot')).toBe(get(defaultValues, 'boot'))
|
||||
expect(get(result, 'values.meta.packaging')).toBe('war')
|
||||
expect(get(result, 'values.meta.java')).toBe('11')
|
||||
expect(get(result, 'values.meta.group')).toBe('com.example2')
|
||||
expect(get(result, 'values.meta.artifact')).toBe('demo2')
|
||||
expect(get(result, 'values.meta.name')).toBe('demo2')
|
||||
expect(get(result, 'values.meta.description')).toBe(
|
||||
'Demo project for Spring Boot2'
|
||||
)
|
||||
expect(get(result, 'values.meta.packageName')).toBe('com.example2.demo2')
|
||||
expect(get(result, 'values.dependencies').length).toBe(2)
|
||||
expect(get(result, 'errors.boot.value')).toBe('1.1.1')
|
||||
})
|
||||
|
||||
it('return parameters, warnings and error', () => {
|
||||
const json = { ...MockClient }
|
||||
const defaultValues = getDefaultValues(json)
|
||||
const listsValues = getLists(json)
|
||||
|
||||
const result = parseParams(
|
||||
defaultValues,
|
||||
{
|
||||
type: 'ant-project',
|
||||
language: 'php',
|
||||
architecture: 'none',
|
||||
platformVersion: '1.1.1',
|
||||
packaging: 'tar',
|
||||
jvmVersion: '1',
|
||||
groupId: 'com.example',
|
||||
artifactId: 'demo',
|
||||
name: 'demo',
|
||||
description: 'Demo project for Spring Boot',
|
||||
packageName: 'com.example.demo',
|
||||
dependencies: 'a,devtools,b,lombok',
|
||||
},
|
||||
listsValues
|
||||
)
|
||||
expect(get(result, 'warnings.project.value')).toBe('ant-project')
|
||||
expect(get(result, 'warnings.language.value')).toBe('php')
|
||||
expect(get(result, 'warnings.meta.packaging.value')).toBe('tar')
|
||||
expect(get(result, 'warnings.meta.java.value')).toBe('1')
|
||||
|
||||
expect(get(result, 'errors.boot.value')).toBe('1.1.1')
|
||||
|
||||
expect(get(result, 'values.meta.group')).toBe('com.example')
|
||||
expect(get(result, 'values.meta.artifact')).toBe('demo')
|
||||
expect(get(result, 'values.meta.name')).toBe('demo')
|
||||
expect(get(result, 'values.meta.description')).toBe(
|
||||
'Demo project for Spring Boot'
|
||||
)
|
||||
|
||||
expect(get(result, 'values.dependencies').length).toBe(2)
|
||||
expect(get(result, 'warnings.dependencies.value')).toBe('a, b')
|
||||
|
||||
expect(get(result, 'values.project')).toBe('maven-project')
|
||||
expect(get(result, 'values.language')).toBe('java')
|
||||
expect(get(result, 'values.boot')).toBe(get(defaultValues, 'boot'))
|
||||
expect(get(result, 'values.meta.packaging')).toBe('jar')
|
||||
expect(get(result, 'values.meta.java')).toBe('1.8')
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Function getShareUrl
|
||||
*/
|
||||
describe('getShareUrl', () => {
|
||||
it('return the base URL with custom values', () => {
|
||||
const result = getShareUrl({
|
||||
project: 'foo1',
|
||||
language: 'foo2',
|
||||
architecture: 'none',
|
||||
boot: 'foo3',
|
||||
meta: {
|
||||
packaging: 'foo4',
|
||||
java: 'foo5',
|
||||
group: 'foo6',
|
||||
artifact: 'foo7',
|
||||
name: 'foo8',
|
||||
description: 'foo9',
|
||||
packageName: 'foo10',
|
||||
},
|
||||
dependencies: ['foo11', 'foo12'],
|
||||
})
|
||||
expect(result).toBe(
|
||||
'type=foo1&language=foo2&architecture=none&platformVersion=foo3&packaging=foo4&jvmVersion=foo5&groupId=foo6&artifactId=foo7&name=foo8&description=foo9&packageName=foo10&dependencies=foo11,foo12'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Get Project
|
||||
*/
|
||||
describe('getProject', () => {
|
||||
beforeEach(() => {
|
||||
fetch.resetMocks()
|
||||
fetch.mockResponseOnce(JSON.stringify({}))
|
||||
})
|
||||
it('should call the right service with the right parameters', () => {
|
||||
const values = {
|
||||
project: 'foo1',
|
||||
language: 'foo2',
|
||||
architecture: 'none',
|
||||
boot: 'foo3',
|
||||
meta: {
|
||||
packaging: 'foo4',
|
||||
java: 'foo5',
|
||||
group: 'foo6',
|
||||
artifact: 'foo7',
|
||||
name: 'foo8',
|
||||
description: 'foo9',
|
||||
packageName: 'foo10',
|
||||
},
|
||||
dependencies: ['foo11', 'foo12'],
|
||||
}
|
||||
getProject('http://demo/starter.zip', values, [
|
||||
{ id: 'foo11' },
|
||||
{ id: 'foo12' },
|
||||
])
|
||||
expect(fetch.mock.calls.length).toEqual(1)
|
||||
expect(fetch.mock.calls[0][0]).toEqual(
|
||||
'http://demo/starter.zip?type=foo1&language=foo2&architecture=none&bootVersion=foo3&baseDir=foo7&groupId=foo6&artifactId=foo7&name=foo8&description=foo9&packageName=foo10&packaging=foo4&javaVersion=foo5&dependencies=foo11,foo12'
|
||||
)
|
||||
})
|
||||
|
||||
it('should call the right service with the right parameters (clean invalid dependencies)', () => {
|
||||
const values = {
|
||||
project: 'foo1',
|
||||
language: 'foo2',
|
||||
architecture: 'none',
|
||||
boot: 'foo3',
|
||||
meta: {
|
||||
packaging: 'foo4',
|
||||
java: 'foo5',
|
||||
group: 'foo6',
|
||||
artifact: 'foo7',
|
||||
name: 'foo8',
|
||||
description: 'foo9',
|
||||
packageName: 'foo10',
|
||||
},
|
||||
dependencies: ['foo11', 'foo12'],
|
||||
}
|
||||
getProject('http://demo/starter.zip', values, [{ id: 'foo11' }])
|
||||
expect(fetch.mock.calls.length).toEqual(1)
|
||||
expect(fetch.mock.calls[0][0]).toEqual(
|
||||
'http://demo/starter.zip?type=foo1&language=foo2&architecture=none&bootVersion=foo3&baseDir=foo7&groupId=foo6&artifactId=foo7&name=foo8&description=foo9&packageName=foo10&packaging=foo4&javaVersion=foo5&dependencies=foo11'
|
||||
)
|
||||
})
|
||||
|
||||
it('should call the right service with the right parameters (no dependency)', () => {
|
||||
const values = {
|
||||
project: 'foo1',
|
||||
language: 'foo2',
|
||||
architecture: 'none',
|
||||
boot: 'foo3',
|
||||
meta: {
|
||||
packaging: 'foo4',
|
||||
java: 'foo5',
|
||||
group: 'foo6',
|
||||
artifact: 'foo7',
|
||||
name: 'foo8',
|
||||
description: 'foo9',
|
||||
packageName: 'foo10',
|
||||
},
|
||||
}
|
||||
getProject('http://demo/starter.zip', values, [])
|
||||
expect(fetch.mock.calls.length).toEqual(1)
|
||||
expect(fetch.mock.calls[0][0]).toEqual(
|
||||
'http://demo/starter.zip?type=foo1&language=foo2&architecture=none&bootVersion=foo3&baseDir=foo7&groupId=foo6&artifactId=foo7&name=foo8&description=foo9&packageName=foo10&packaging=foo4&javaVersion=foo5'
|
||||
)
|
||||
})
|
||||
})
|
||||
165
initializer-page/src/components/utils/__tests__/Version.js
Normal file
165
initializer-page/src/components/utils/__tests__/Version.js
Normal file
@@ -0,0 +1,165 @@
|
||||
import get from 'lodash.get'
|
||||
|
||||
import {
|
||||
compare,
|
||||
getValidDependencies,
|
||||
isInRange,
|
||||
parseQualifier,
|
||||
parseReleases,
|
||||
parseVersion,
|
||||
rangeToText,
|
||||
} from '../Version'
|
||||
|
||||
/**
|
||||
* Function parseQualifier
|
||||
*/
|
||||
describe('parseQualifier', () => {
|
||||
it('should return the default value (RELEASE)', () => {
|
||||
let release = parseQualifier('1.0.0')
|
||||
expect(release).toBe('RELEASE')
|
||||
release = parseQualifier('1.0.0.RELEASE')
|
||||
expect(release).toBe('RELEASE')
|
||||
release = parseQualifier('foo')
|
||||
expect(release).toBe('RELEASE')
|
||||
})
|
||||
it('should return a specific qualifier', () => {
|
||||
let release = parseQualifier('1.0.0.M2')
|
||||
expect(release).toBe('M')
|
||||
release = parseQualifier('1.0.0.RC1')
|
||||
expect(release).toBe('RC')
|
||||
release = parseQualifier('1.0.0.BUILD-SNAPSHOT')
|
||||
expect(release).toBe('BUILD-SNAPSHOT')
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Function parseVersion
|
||||
*/
|
||||
describe('parseVersion', () => {
|
||||
it('should parse correctly the version', () => {
|
||||
const version = parseVersion('1.0.1.RELEASE')
|
||||
expect(get(version, 'version')).toBe('1.0.1.RELEASE')
|
||||
expect(get(version, 'short')).toBe('1.0.1')
|
||||
expect(get(version, 'major')).toBe('1.0.x')
|
||||
expect(get(version, 'qualify')).toBe(3)
|
||||
expect(get(version, 'minor')).toBe(1)
|
||||
})
|
||||
|
||||
it('should not parse the version', () => {
|
||||
const version = parseVersion('foo')
|
||||
expect(get(version, 'version')).toBe('foo')
|
||||
expect(get(version, 'short', null)).toBeNull()
|
||||
expect(get(version, 'major', null)).toBeNull()
|
||||
expect(get(version, 'qualify', null)).toBeNull()
|
||||
expect(get(version, 'minor', null)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Function compare
|
||||
*/
|
||||
describe('compare', () => {
|
||||
it('should compare 2 versions', () => {
|
||||
let result = compare('1.0.0.RELEASE', '1.0.1.RELEASE')
|
||||
expect(result < 0).toBe(true)
|
||||
result = compare('1.0.2.RELEASE', '1.0.1.RELEASE')
|
||||
expect(result > 0).toBe(true)
|
||||
result = compare('1.0.1.RELEASE', '1.0.1.M1')
|
||||
expect(result > 0).toBe(true)
|
||||
result = compare('1.0.1.RELEASE', '1.0.1.RC1')
|
||||
expect(result > 0).toBe(true)
|
||||
result = compare('1.0.1.RELEASE', '1.0.1.BUILD-SNAPSHOT')
|
||||
expect(result > 0).toBe(true)
|
||||
result = compare('1.0.1', '1.0.1.BUILD-SNAPSHOT')
|
||||
expect(result > 0).toBe(true)
|
||||
result = compare('1.0.1.RELEASE', '1.0.1')
|
||||
expect(result > 0).toBe(true)
|
||||
result = compare('1.0.1', '1.0.1.RELEASE')
|
||||
expect(result < 0).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Function parseReleases
|
||||
*/
|
||||
describe('parseReleases', () => {
|
||||
it('should parse the versions', () => {
|
||||
const result = parseReleases([{ key: '1.0.1.RELEASE' }])
|
||||
expect(result.length).toBe(1)
|
||||
expect(get(result[0], 'version')).toBe('1.0.1.RELEASE')
|
||||
expect(get(result[0], 'short')).toBe('1.0.1')
|
||||
expect(get(result[0], 'major')).toBe('1.0.x')
|
||||
expect(get(result[0], 'qualify')).toBe(3)
|
||||
expect(get(result[0], 'minor')).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Function isInRange
|
||||
*/
|
||||
describe('isInRange', () => {
|
||||
it('should return true', () => {
|
||||
let result = isInRange('2.1.1.RELEASE', '[2.0.0.RELEASE,2.2.0.M1)')
|
||||
expect(result).toBe(true)
|
||||
result = isInRange('2.0.0.RELEASE', '[2.0.0.RELEASE,2.2.0.M1)')
|
||||
expect(result).toBe(true)
|
||||
result = isInRange('2.0.0.RELEASE', '')
|
||||
expect(result).toBe(true)
|
||||
})
|
||||
it('should return false', () => {
|
||||
let result = isInRange('1.9.8.RELEASE', '[2.0.0.RELEASE,2.2.0.M1)')
|
||||
expect(result).toBe(false)
|
||||
result = isInRange('2.0.0.M1', '[2.0.0.RELEASE,2.2.0.M1)')
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Function rangeToText
|
||||
*/
|
||||
describe('rangeToText', () => {
|
||||
it('should return the correct string', () => {
|
||||
let result = rangeToText('[2.0.0.RELEASE,2.2.0.M1)')
|
||||
expect(result).toBe('>= 2.0.0.RELEASE and < 2.2.0.M1')
|
||||
result = rangeToText('[2.0.0.RELEASE,2.2.0.M1]')
|
||||
expect(result).toBe('>= 2.0.0.RELEASE and <= 2.2.0.M1')
|
||||
result = rangeToText('(2.0.0.RELEASE,2.2.0.M1]')
|
||||
expect(result).toBe('> 2.0.0.RELEASE and <= 2.2.0.M1')
|
||||
result = rangeToText('2.0.0.RELEASE')
|
||||
expect(result).toBe('>= 2.0.0.RELEASE')
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Function getValidDependencies
|
||||
*/
|
||||
describe('getValidDependencies', () => {
|
||||
it('should return the valid dependencies', () => {
|
||||
const result = getValidDependencies('2.0.0.RELEASE', [
|
||||
{
|
||||
name: 'foo1',
|
||||
versionRange: '[2.0.0.RELEASE,2.2.0.M1)',
|
||||
},
|
||||
{
|
||||
name: 'foo2',
|
||||
versionRange: '(2.0.0.RELEASE,2.2.0.M1]',
|
||||
},
|
||||
{
|
||||
name: 'foo3',
|
||||
versionRange: '2.0.0.RELEASE',
|
||||
},
|
||||
{
|
||||
name: 'foo4',
|
||||
versionRange: '',
|
||||
},
|
||||
{
|
||||
name: 'foo5',
|
||||
},
|
||||
])
|
||||
expect(result.length).toBe(4)
|
||||
expect(get(result[0], 'name')).toBe('foo1')
|
||||
expect(get(result[1], 'name')).toBe('foo3')
|
||||
expect(get(result[2], 'name')).toBe('foo4')
|
||||
expect(get(result[3], 'name')).toBe('foo5')
|
||||
})
|
||||
})
|
||||
81
initializer-page/src/components/utils/__tests__/Zip.js
Normal file
81
initializer-page/src/components/utils/__tests__/Zip.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import JSZip from 'jszip'
|
||||
import fs from 'fs'
|
||||
import get from 'lodash.get'
|
||||
import path from 'path'
|
||||
|
||||
import {createTree, findRoot, getLanguage} from '../Zip'
|
||||
|
||||
/**
|
||||
* Function getLanguage
|
||||
*/
|
||||
describe('getLanguage', () => {
|
||||
it('should return the extention', () => {
|
||||
expect(getLanguage('index.md')).toBe('markdown')
|
||||
expect(getLanguage('index.spec.md')).toBe('markdown')
|
||||
expect(getLanguage('index.js')).toBe('javascript')
|
||||
expect(getLanguage('index.kt')).toBe('kotlin')
|
||||
expect(getLanguage('index.kts')).toBe('kotlin')
|
||||
expect(getLanguage('index.gradle')).toBe('groovy')
|
||||
expect(getLanguage('index.gitignore')).toBe('git')
|
||||
expect(getLanguage('index.java')).toBe('java')
|
||||
expect(getLanguage('index.xml')).toBe('xml')
|
||||
expect(getLanguage('index.properties')).toBe('properties')
|
||||
expect(getLanguage('index.groovy')).toBe('groovy')
|
||||
expect(getLanguage('index.html')).toBe('html')
|
||||
expect(getLanguage('index.css')).toBe('css')
|
||||
})
|
||||
it('should not return the extention', () => {
|
||||
expect(getLanguage('index.php')).toBe(null)
|
||||
expect(getLanguage('index.scss')).toBe(null)
|
||||
expect(getLanguage('mvnw')).toBe(null)
|
||||
expect(getLanguage('command.cmd')).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Function getLanguage
|
||||
*/
|
||||
describe('createTree', () => {
|
||||
// eslint-disable-next-line
|
||||
it('should create a tree', async done => {
|
||||
const zipJs = new JSZip()
|
||||
await fs.readFile(path.resolve('./dev/starter.zip'), async (err, blob) => {
|
||||
const { files } = await zipJs.loadAsync(blob).catch(() => {
|
||||
throw Error(`Could not load the ZIP project.`)
|
||||
})
|
||||
const pathZ = `${findRoot({ files })}/`
|
||||
const result = await createTree(files, pathZ, pathZ, zipJs).catch(() => {
|
||||
throw Error(`Could not read the ZIP project.`)
|
||||
})
|
||||
expect(get(result, 'tree', null) !== null).toBe(true)
|
||||
expect(get(result, 'tree.type', null)).toBe('folder')
|
||||
expect(get(result, 'tree.filename', null)).toBe('demo')
|
||||
expect(get(result, 'tree.path', null)).toBe('/demo/')
|
||||
expect(get(result, 'tree.hidden', null)).toBe(null)
|
||||
expect(get(result, 'tree.children').length).toBe(7)
|
||||
expect(get(result, 'selected.type')).toBe('file')
|
||||
expect(get(result, 'selected.filename')).toBe('pom.xml')
|
||||
expect(get(result, 'selected.path')).toBe('/demo/pom.xml')
|
||||
expect(get(result, 'selected.language')).toBe('xml')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Function getLanguage
|
||||
*/
|
||||
describe('findRoot', () => {
|
||||
// eslint-disable-next-line
|
||||
it('should return the right value', async done => {
|
||||
const zipJs = new JSZip()
|
||||
await fs.readFile(path.resolve('./dev/starter.zip'), async (err, blob) => {
|
||||
const { files } = await zipJs.loadAsync(blob).catch(() => {
|
||||
throw Error(`Could not load the ZIP project.`)
|
||||
})
|
||||
const pathZ = `${findRoot({ files })}/`
|
||||
expect(pathZ).toBe('demo/')
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user