forked from community/device-mgt-core
feature/appm-store/pbac
commit
dd707b98f2
@ -1,17 +0,0 @@
|
|||||||
const express = require('express');
|
|
||||||
const bodyParser = require('body-parser');
|
|
||||||
const pino = require('express-pino-logger')();
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
app.use(bodyParser.urlencoded({ extended: false }));
|
|
||||||
app.use(pino);
|
|
||||||
|
|
||||||
app.get('/api/greeting', (req, res) => {
|
|
||||||
const name = req.query.name || 'World';
|
|
||||||
res.setHeader('Content-Type', 'application/json');
|
|
||||||
res.send(JSON.stringify({ greeting: `Hello ${name}!` }));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(3001, () =>
|
|
||||||
console.log('Express server is running on localhost:3001')
|
|
||||||
);
|
|
@ -1,33 +1,45 @@
|
|||||||
.App {
|
.ant-upload.ant-upload-drag {
|
||||||
text-align: center;
|
height: 170px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-logo {
|
.release-icon img{
|
||||||
animation: App-logo-spin infinite 20s linear;
|
width: 100%;
|
||||||
height: 40vmin;
|
border-radius: 28%;
|
||||||
pointer-events: none;
|
}
|
||||||
|
.release .release-icon{
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
.appCard .release-icon{
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.release .release-title{
|
||||||
|
margin-left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-header {
|
.release .release-screenshot img{
|
||||||
background-color: #282c34;
|
width: 100%;
|
||||||
min-height: 100vh;
|
border-radius: 15px;
|
||||||
display: flex;
|
padding: 5px;
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: calc(10px + 2vmin);
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-link {
|
.logo {
|
||||||
color: #61dafb;
|
width: 120px;
|
||||||
|
height: 31px;
|
||||||
|
margin: 0 0 16px 20px;
|
||||||
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
.logo img{
|
||||||
from {
|
height: 35px;
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
}
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
.main-container{
|
||||||
|
background: #f0f2f5;
|
||||||
|
min-height: 780px
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 768px) {
|
||||||
|
.main-container{
|
||||||
|
padding: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {Route} from 'react-router-dom';
|
||||||
|
class RouteWithSubRoutes extends React.Component{
|
||||||
|
props;
|
||||||
|
constructor(props){
|
||||||
|
super(props);
|
||||||
|
this.props = props;
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return(
|
||||||
|
<Route path={this.props.path} exact={this.props.exact} render={(props) => (
|
||||||
|
<this.props.component {...props} routes={this.props.routes}/>
|
||||||
|
)}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RouteWithSubRoutes;
|
@ -0,0 +1,71 @@
|
|||||||
|
import {
|
||||||
|
Skeleton, Switch, Card, Icon, Avatar, Typography, Col, Row, Rate
|
||||||
|
} from 'antd';
|
||||||
|
import React from "react";
|
||||||
|
import {openReleasesModal} from "../../js/actions";
|
||||||
|
import {connect} from "react-redux";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import "../../App.css";
|
||||||
|
import StarRatings from 'react-star-ratings';
|
||||||
|
|
||||||
|
const {Meta} = Card;
|
||||||
|
const {Text, Title} = Typography;
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
openReleasesModal: (app) => dispatch(openReleasesModal(app))
|
||||||
|
});
|
||||||
|
|
||||||
|
class ConnectedAppCard extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.handleReleasesClick = this.handleReleasesClick.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleReleasesClick() {
|
||||||
|
this.props.openReleasesModal(this.props.app);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const app = this.props.app;
|
||||||
|
const release = this.props.app.applicationReleases[0];
|
||||||
|
console.log(this.props);
|
||||||
|
|
||||||
|
const description = (
|
||||||
|
<div className="appCard">
|
||||||
|
<Link to={"/store/apps/" + release.uuid}>
|
||||||
|
<Row className="release">
|
||||||
|
<Col span={24} className="release-icon">
|
||||||
|
<img src={release.iconPath} alt="icon"/>
|
||||||
|
{/*<Avatar shape="square" size={128} src={release.iconPath} />*/}
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Text strong level={4}>{app.name}</Text><br/>
|
||||||
|
<Text type="secondary" level={4}>{app.deviceType}</Text><br/>
|
||||||
|
<StarRatings
|
||||||
|
rating={app.rating}
|
||||||
|
starRatedColor="#777"
|
||||||
|
starDimension = "12px"
|
||||||
|
starSpacing = "0"
|
||||||
|
numberOfStars={5}
|
||||||
|
name='rating'
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card style={{marginTop: 16}}>
|
||||||
|
<Meta
|
||||||
|
description={description}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppCard = connect(null, mapDispatchToProps)(ConnectedAppCard);
|
||||||
|
|
||||||
|
export default AppCard;
|
@ -0,0 +1,38 @@
|
|||||||
|
import React from "react";
|
||||||
|
import AppCard from "./AppCard";
|
||||||
|
import {Col, Row} from "antd";
|
||||||
|
import {connect} from "react-redux";
|
||||||
|
import {getApps} from "../../js/actions";
|
||||||
|
|
||||||
|
// connecting state.apps with the component
|
||||||
|
const mapStateToProps= state => {
|
||||||
|
return {apps : state.apps}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectedAppList extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.getApps();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Row gutter={16}>
|
||||||
|
{this.props.apps.map(app => (
|
||||||
|
<Col key={app.id} xs={12} sm={6} md={6} lg={4} xl={3}>
|
||||||
|
<AppCard key={app.id}
|
||||||
|
app = {app}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppList = connect(mapStateToProps,{getApps})(ConnectedAppList);
|
||||||
|
|
||||||
|
export default AppList;
|
@ -0,0 +1,85 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {Modal, Typography,List, Avatar} from 'antd';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
|
||||||
|
// connecting state.releaseView with the component
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
return {releaseView: state.releaseView}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Text = Typography;
|
||||||
|
|
||||||
|
class ConnectedReleaseModal extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
visible: false,
|
||||||
|
app: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (nextProps !== this.props) {
|
||||||
|
this.setState({
|
||||||
|
visible: nextProps.releaseView.visible,
|
||||||
|
app: nextProps.releaseView.app
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showModal = () => {
|
||||||
|
this.setState({
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOk = (e) => {
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCancel = (e) => {
|
||||||
|
this.setState({
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.props.releaseView.app != null) {
|
||||||
|
const app = this.props.releaseView.app;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal
|
||||||
|
title={app.name}
|
||||||
|
visible={this.state.visible}
|
||||||
|
onOk={this.handleOk}
|
||||||
|
onCancel={this.handleCancel}
|
||||||
|
>
|
||||||
|
<p>Some contents...</p>
|
||||||
|
<List
|
||||||
|
itemLayout="horizontal"
|
||||||
|
dataSource={app.applicationReleases}
|
||||||
|
renderItem={release => (
|
||||||
|
<List.Item>
|
||||||
|
<List.Item.Meta
|
||||||
|
avatar={<Avatar src={release.iconPath} />}
|
||||||
|
title={<Link to={"/store/apps/"+release.uuid}>{release.version}</Link>}
|
||||||
|
description={release.description}
|
||||||
|
/>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReleaseModal = connect(mapStateToProps, null)(ConnectedReleaseModal);
|
||||||
|
|
||||||
|
export default ReleaseModal;
|
@ -0,0 +1,72 @@
|
|||||||
|
.d-rating .numeric-data{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 20px 0 20px 0;
|
||||||
|
vertical-align: top;
|
||||||
|
text-align: center;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-rating .bar-containers{
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 20px 20px 20px 30px;
|
||||||
|
vertical-align: top;
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-rating .bar-containers .bar-container{
|
||||||
|
color: #737373;
|
||||||
|
font-weight: 400;
|
||||||
|
height: 20px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-rating .bar-containers .bar-container .number{
|
||||||
|
font-size: 11px;
|
||||||
|
left: -16px;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-rating .bar-containers .bar-container .bar{
|
||||||
|
transition: width .25s ease;
|
||||||
|
display: inline-block;
|
||||||
|
height: 100%;
|
||||||
|
opacity: .8;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-container .rate-5{
|
||||||
|
background: #57bb8a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-container .rate-4{
|
||||||
|
background: #9ace6a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-container .rate-3{
|
||||||
|
background: #ffcf02;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-container .rate-2{
|
||||||
|
background: #ff9f02;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-container .rate-1{
|
||||||
|
background: #ff6f31;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-rating .numeric-data .rate{
|
||||||
|
color: #333;
|
||||||
|
font-size: 64px;
|
||||||
|
font-weight: 100;
|
||||||
|
line-height: 64px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-rating .numeric-data .people-count{
|
||||||
|
padding-top: 6px;
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {Row, Typography, Icon} from "antd";
|
||||||
|
import StarRatings from "react-star-ratings";
|
||||||
|
import "./DetailedRating.css";
|
||||||
|
import {connect} from "react-redux";
|
||||||
|
import {getDetailedRating} from "../../../js/actions";
|
||||||
|
|
||||||
|
const { Text } = Typography;
|
||||||
|
|
||||||
|
// connecting state. with the component
|
||||||
|
const mapStateToProps= state => {
|
||||||
|
return {detailedRating : state.detailedRating}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
getDetailedRating: (uuid) => dispatch(getDetailedRating(uuid))
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectedDetailedRating extends React.Component{
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.getDetailedRating(this.props.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const detailedRating = this.props.detailedRating;
|
||||||
|
|
||||||
|
if(detailedRating ==null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalCount = detailedRating.noOfUsers;
|
||||||
|
const ratingVariety = detailedRating.ratingVariety;
|
||||||
|
|
||||||
|
const ratingArray = [];
|
||||||
|
|
||||||
|
for (let [key, value] of Object.entries(ratingVariety)) {
|
||||||
|
ratingArray.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const maximumRating = Math.max(...ratingArray);
|
||||||
|
|
||||||
|
console.log(maximumRating);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row className="d-rating">
|
||||||
|
<div className="numeric-data">
|
||||||
|
<div className="rate">{detailedRating.ratingValue.toFixed(1)}</div>
|
||||||
|
<StarRatings
|
||||||
|
rating={detailedRating.ratingValue}
|
||||||
|
starRatedColor="#777"
|
||||||
|
starDimension = "16px"
|
||||||
|
starSpacing = "2px"
|
||||||
|
numberOfStars={5}
|
||||||
|
name='rating'
|
||||||
|
/>
|
||||||
|
<br/>
|
||||||
|
<Text type="secondary" className="people-count"><Icon type="team" /> {totalCount} total</Text>
|
||||||
|
</div>
|
||||||
|
<div className="bar-containers">
|
||||||
|
<div className="bar-container">
|
||||||
|
<span className="number">5</span>
|
||||||
|
<span className="bar rate-5" style={{width: (ratingVariety["5"]/maximumRating*100)+"%"}}> </span>
|
||||||
|
</div>
|
||||||
|
<div className="bar-container">
|
||||||
|
<span className="number">4</span>
|
||||||
|
<span className="bar rate-4" style={{width: (ratingVariety["4"]/maximumRating*100)+"%"}}> </span>
|
||||||
|
</div>
|
||||||
|
<div className="bar-container">
|
||||||
|
<span className="number">3</span>
|
||||||
|
<span className="bar rate-3" style={{width: (ratingVariety["3"]/maximumRating*100)+"%"}}> </span>
|
||||||
|
</div>
|
||||||
|
<div className="bar-container">
|
||||||
|
<span className="number">2</span>
|
||||||
|
<span className="bar rate-2" style={{width: (ratingVariety["2"]/maximumRating*100)+"%"}}> </span>
|
||||||
|
</div>
|
||||||
|
<div className="bar-container">
|
||||||
|
<span className="number">1</span>
|
||||||
|
<span className="bar rate-1" style={{width: (ratingVariety["1"]/maximumRating*100)+"%"}}> </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DetailedRating = connect(mapStateToProps,mapDispatchToProps)(ConnectedDetailedRating);
|
||||||
|
|
||||||
|
export default DetailedRating;
|
@ -0,0 +1,61 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {Divider, Row, Col, Typography, Button, Rate} from "antd";
|
||||||
|
import "../../../App.css";
|
||||||
|
import ImgViewer from "../../apps/release/images/ImgViewer";
|
||||||
|
import StarRatings from "react-star-ratings";
|
||||||
|
import DetailedRating from "./DetailedRating";
|
||||||
|
const {Title, Text, Paragraph} = Typography;
|
||||||
|
|
||||||
|
class ReleaseView extends React.Component {
|
||||||
|
render() {
|
||||||
|
const release = this.props.release;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="release">
|
||||||
|
<Row>
|
||||||
|
<Col xl={4} sm={6} xs={8} className="release-icon">
|
||||||
|
<img src={release.iconPath} alt="icon"/>
|
||||||
|
</Col>
|
||||||
|
<Col xl={10} sm={11} className="release-title">
|
||||||
|
<Title level={2}>App Name</Title>
|
||||||
|
<Text>Version : {release.version}</Text><br/><br/>
|
||||||
|
<StarRatings
|
||||||
|
rating={release.rating}
|
||||||
|
starRatedColor="#777"
|
||||||
|
starDimension = "20px"
|
||||||
|
starSpacing = "2px"
|
||||||
|
numberOfStars={5}
|
||||||
|
name='rating'
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col xl={8} md={10} sm={24} xs={24} style={{float: "right"}}>
|
||||||
|
<div>
|
||||||
|
<Button.Group style={{float: "right"}}>
|
||||||
|
<Button htmlType="button" icon="usergroup-add">Enterprise Install</Button>
|
||||||
|
<Button htmlType="button" type="primary" icon="download">Install</Button>
|
||||||
|
</Button.Group>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Divider/>
|
||||||
|
<Row>
|
||||||
|
<ImgViewer images={release.screenshots}/>
|
||||||
|
</Row>
|
||||||
|
<Divider/>
|
||||||
|
<Paragraph type="secondary" ellipsis={{rows: 3, expandable: true}}>
|
||||||
|
{release.description}
|
||||||
|
</Paragraph>
|
||||||
|
<Divider/>
|
||||||
|
<Text>REVIEWS</Text>
|
||||||
|
<Row>
|
||||||
|
<Col lg={18}>
|
||||||
|
<DetailedRating uuid={release.uuid}/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ReleaseView;
|
@ -0,0 +1,45 @@
|
|||||||
|
import React, {Component} from 'react';
|
||||||
|
import RcViewer from 'rc-viewer';
|
||||||
|
import {Col} from "antd";
|
||||||
|
|
||||||
|
class ImgViewer extends Component {
|
||||||
|
render() {
|
||||||
|
const options = {
|
||||||
|
title: false,
|
||||||
|
toolbar: {
|
||||||
|
zoomIn: 0,
|
||||||
|
zoomOut: 0,
|
||||||
|
oneToOne: 0,
|
||||||
|
reset: 0,
|
||||||
|
prev: 1,
|
||||||
|
play: {
|
||||||
|
show: 0
|
||||||
|
},
|
||||||
|
next: 1,
|
||||||
|
rotateLeft: 0,
|
||||||
|
rotateRight: 0,
|
||||||
|
flipHorizontal: 0,
|
||||||
|
flipVertical: 0
|
||||||
|
},
|
||||||
|
rotatable: false,
|
||||||
|
transition: false,
|
||||||
|
movable : false
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<RcViewer options={options} ref='viewer'>
|
||||||
|
{this.props.images.map((screenshotUrl) => {
|
||||||
|
return (
|
||||||
|
<Col key={"col-" + screenshotUrl} lg={6} md={8} xs={8} className="release-screenshot">
|
||||||
|
<img key={screenshotUrl} src={screenshotUrl}/>
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</RcViewer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImgViewer;
|
@ -1,3 +1,27 @@
|
|||||||
.App {
|
.App {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-layout-header{
|
||||||
|
padding: 0;
|
||||||
|
height: auto;
|
||||||
|
box-shadow: 0 2px 8px #f0f1f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-content {
|
||||||
|
margin-top: 16px;
|
||||||
|
border: 1px dashed #e9e9e9;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #fafafa;
|
||||||
|
min-height: 200px;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-action {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-input-affix-wrapper .ant-input{
|
||||||
|
min-height: 0;
|
||||||
|
}
|
@ -1 +1,8 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/>
|
||||||
|
<title>Entgra App Store</title>
|
||||||
|
</head>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
</html>
|
@ -0,0 +1,168 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import ActionTypes from "../constants/ActionTypes";
|
||||||
|
import config from "../../../public/conf/config.json";
|
||||||
|
|
||||||
|
export const getApps = () => dispatch => {
|
||||||
|
|
||||||
|
const request = "method=post&content-type=application/json&payload={}&api-endpoint=/application-mgt-store/v1.0/applications";
|
||||||
|
|
||||||
|
return axios.post('https://' + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invokerUri, request
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
let apps = [];
|
||||||
|
|
||||||
|
if (res.data.data.hasOwnProperty("applications")) {
|
||||||
|
apps = res.data.data.applications;
|
||||||
|
}
|
||||||
|
dispatch({type: ActionTypes.GET_APPS, payload: apps});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch(function (error) {
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
window.location.href = 'https://localhost:9443/store/login';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRelease = (uuid) => dispatch => {
|
||||||
|
|
||||||
|
const request = "method=get&content-type=application/json&payload={}&api-endpoint=/application-mgt-store/v1.0/applications/" + uuid;
|
||||||
|
|
||||||
|
return axios.post('https://' + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invokerUri, request
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
let release = res.data.data.applicationReleases[0];
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.GET_RELEASE,
|
||||||
|
payload: {
|
||||||
|
release: release,
|
||||||
|
releaseLoading: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(function (error) {
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
window.location.href = 'https://localhost:9443/store/login';
|
||||||
|
}else if(error.response.status===404){
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.GET_RELEASE,
|
||||||
|
payload: {
|
||||||
|
release: null,
|
||||||
|
releaseLoading: false
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export const openReleasesModal = (app) => dispatch => {
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.OPEN_RELEASES_MODAL,
|
||||||
|
payload: {
|
||||||
|
app: app
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const openLifecycleModal = (nextState) => dispatch => {
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.OPEN_LIFECYCLE_MODAL,
|
||||||
|
payload: {
|
||||||
|
nextState: nextState
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const closeLifecycleModal = () => dispatch => {
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.CLOSE_LIFECYCLE_MODAL
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setLoading = (stateToLoad) => dispatch => {
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.SET_LOADING_STATE,
|
||||||
|
payload: {
|
||||||
|
stateToLoad: stateToLoad
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLifecycle = () => dispatch => {
|
||||||
|
const request = "method=get&content-type=application/json&payload={}&api-endpoint=/application-mgt-store/v1.0/applications/lifecycle-config";
|
||||||
|
|
||||||
|
return axios.post('https://' + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invokerUri, request
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
let lifecycle = res.data.data;
|
||||||
|
dispatch({type: ActionTypes.GET_LIFECYCLE, payload: lifecycle});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch(function (error) {
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
window.location.href = 'https://localhost:9443/store/login';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateLifecycleState = (uuid, nextState, reason) => dispatch => {
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
action: nextState,
|
||||||
|
reason: reason
|
||||||
|
};
|
||||||
|
|
||||||
|
const request = "method=post&content-type=application/json&payload=" + JSON.stringify(payload) + "&api-endpoint=/application-mgt-store/v1.0/applications/life-cycle/" + uuid;
|
||||||
|
|
||||||
|
|
||||||
|
return axios.post('https://' + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invokerUri, request
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 201) {
|
||||||
|
let release = res.data.data;
|
||||||
|
dispatch({type: ActionTypes.UPDATE_LIFECYCLE_STATE, payload: release});
|
||||||
|
}else {
|
||||||
|
alert("error");
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.CLOSE_LIFECYCLE_MODAL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch(function (error) {
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
window.location.href = 'https://localhost:9443/store/login';
|
||||||
|
} else if (error.response.status === 500) {
|
||||||
|
alert("error");
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.CLOSE_LIFECYCLE_MODAL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDetailedRating = (uuid) => dispatch => {
|
||||||
|
const request = "method=get&content-type=application/json&payload={}&api-endpoint=/application-mgt-store/v1.0/reviews/"+uuid+"/rating";
|
||||||
|
|
||||||
|
return axios.post('https://' + config.serverConfig.hostname + ':' + config.serverConfig.httpsPort + config.serverConfig.invokerUri, request
|
||||||
|
).then(res => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
let detailedRating = res.data.data;
|
||||||
|
dispatch({type: ActionTypes.GET_DETAILED_RATING, payload: detailedRating});
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch(function (error) {
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
window.location.href = 'https://localhost:9443/store/login';
|
||||||
|
} else{
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.GET_DETAILED_RATING, payload: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
};
|
@ -0,0 +1,17 @@
|
|||||||
|
import keyMirror from 'keymirror';
|
||||||
|
|
||||||
|
const ActionTypes = keyMirror({
|
||||||
|
LOGIN: null,
|
||||||
|
GET_APPS: null,
|
||||||
|
OPEN_RELEASES_MODAL: null,
|
||||||
|
CLOSE_RELEASES_MODAL: null,
|
||||||
|
GET_RELEASE: null,
|
||||||
|
GET_LIFECYCLE: null,
|
||||||
|
OPEN_LIFECYCLE_MODAL: null,
|
||||||
|
CLOSE_LIFECYCLE_MODAL: null,
|
||||||
|
UPDATE_LIFECYCLE_STATE: null,
|
||||||
|
SET_LOADING_STATE: null,
|
||||||
|
GET_DETAILED_RATING: null
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ActionTypes;
|
@ -0,0 +1,83 @@
|
|||||||
|
import ActionTypes from "../constants/ActionTypes";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
apps: [],
|
||||||
|
releaseView: {
|
||||||
|
visible: false,
|
||||||
|
app: null
|
||||||
|
},
|
||||||
|
release: null,
|
||||||
|
lifecycle: null,
|
||||||
|
lifecycleModal: {
|
||||||
|
visible: false,
|
||||||
|
nextState: null
|
||||||
|
},
|
||||||
|
loadingState: {
|
||||||
|
release: true
|
||||||
|
},
|
||||||
|
detailedRating: null
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function rootReducer(state = initialState, action) {
|
||||||
|
if (action.type === ActionTypes.GET_APPS) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
apps: action.payload
|
||||||
|
});
|
||||||
|
} else if (action.type === ActionTypes.OPEN_RELEASES_MODAL) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
releaseView: {
|
||||||
|
visible: true,
|
||||||
|
app: action.payload.app
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (action.type === ActionTypes.GET_RELEASE) {
|
||||||
|
let loadingState = {...state.loadingState};
|
||||||
|
loadingState.release = action.payload.releaseLoading;
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
release: action.payload.release,
|
||||||
|
loadingState: loadingState
|
||||||
|
});
|
||||||
|
} else if (action.type === ActionTypes.GET_LIFECYCLE) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
lifecycle: action.payload
|
||||||
|
});
|
||||||
|
} else if (action.type === ActionTypes.OPEN_LIFECYCLE_MODAL) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
lifecycleModal: {
|
||||||
|
visible: true,
|
||||||
|
nextState: action.payload.nextState
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (action.type === ActionTypes.CLOSE_LIFECYCLE_MODAL) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
lifecycleModal: {
|
||||||
|
visible: false,
|
||||||
|
nextState: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (action.type === ActionTypes.UPDATE_LIFECYCLE_STATE) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
lifecycleModal: {
|
||||||
|
visible: false,
|
||||||
|
nextState: null,
|
||||||
|
},
|
||||||
|
release: action.payload
|
||||||
|
});
|
||||||
|
} else if (action.type === ActionTypes.SET_LOADING_STATE) {
|
||||||
|
let loadingState = {...state.loadingState};
|
||||||
|
loadingState[action.payload.stateToLoad] = true;
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
loadingState: loadingState
|
||||||
|
});
|
||||||
|
} else if (action.type === ActionTypes.GET_DETAILED_RATING) {
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
detailedRating: action.payload
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default rootReducer;
|
@ -0,0 +1,5 @@
|
|||||||
|
import { createStore, applyMiddleware } from "redux";
|
||||||
|
import rootReducer from "../reducers/index";
|
||||||
|
import thunk from "redux-thunk";
|
||||||
|
const store = createStore(rootReducer, applyMiddleware(thunk));
|
||||||
|
export default store;
|
@ -0,0 +1,56 @@
|
|||||||
|
import * as dagre from "dagre";
|
||||||
|
import * as _ from "lodash";
|
||||||
|
|
||||||
|
const size = {
|
||||||
|
width: 60,
|
||||||
|
height: 60
|
||||||
|
};
|
||||||
|
|
||||||
|
export function distributeElements(model) {
|
||||||
|
let clonedModel = _.cloneDeep(model);
|
||||||
|
let nodes = distributeGraph(clonedModel);
|
||||||
|
nodes.forEach(node => {
|
||||||
|
let modelNode = clonedModel.nodes.find(item => item.id === node.id);
|
||||||
|
modelNode.x = node.x - node.width / 2;
|
||||||
|
modelNode.y = node.y - node.height / 2;
|
||||||
|
});
|
||||||
|
return clonedModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function distributeGraph(model) {
|
||||||
|
let nodes = mapElements(model);
|
||||||
|
let edges = mapEdges(model);
|
||||||
|
let graph = new dagre.graphlib.Graph();
|
||||||
|
graph.setGraph({});
|
||||||
|
graph.setDefaultEdgeLabel(() => ({}));
|
||||||
|
//add elements to dagre graph
|
||||||
|
nodes.forEach(node => {
|
||||||
|
graph.setNode(node.id, node.metadata);
|
||||||
|
});
|
||||||
|
edges.forEach(edge => {
|
||||||
|
if (edge.from && edge.to) {
|
||||||
|
graph.setEdge(edge.from, edge.to);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//auto-distribute
|
||||||
|
dagre.layout(graph);
|
||||||
|
return graph.nodes().map(node => graph.node(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapElements(model) {
|
||||||
|
// dagre compatible format
|
||||||
|
return model.nodes.map(node => ({ id: node.id, metadata: { ...size, id: node.id } }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapEdges(model) {
|
||||||
|
// returns links which connects nodes
|
||||||
|
// we check are there both from and to nodes in the model. Sometimes links can be detached
|
||||||
|
return model.links
|
||||||
|
.map(link => ({
|
||||||
|
from: link.source,
|
||||||
|
to: link.target
|
||||||
|
}))
|
||||||
|
.filter(
|
||||||
|
item => model.nodes.find(node => node.id === item.from) && model.nodes.find(node => node.id === item.to)
|
||||||
|
);
|
||||||
|
}
|
@ -1,7 +1,15 @@
|
|||||||
.logo {
|
.logo {
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 31px;
|
height: 31px;
|
||||||
background: rgba(0,0,0,.2);
|
margin: 16px 0 16px 20px;
|
||||||
margin: 16px 24px 16px 0;
|
|
||||||
float: left;
|
float: left;
|
||||||
|
|
||||||
|
img{
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
input{
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
@ -0,0 +1,336 @@
|
|||||||
|
import React from "react";
|
||||||
|
import "antd/dist/antd.css";
|
||||||
|
import {
|
||||||
|
PageHeader,
|
||||||
|
Typography,
|
||||||
|
Card,
|
||||||
|
Steps,
|
||||||
|
Button,
|
||||||
|
message,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Tag,
|
||||||
|
Tooltip,
|
||||||
|
Input,
|
||||||
|
Icon,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
Form,
|
||||||
|
Upload,
|
||||||
|
Divider
|
||||||
|
} from "antd";
|
||||||
|
import Step1 from "./Step1";
|
||||||
|
import Step2 from "./Step2";
|
||||||
|
import Step3 from "./Step3";
|
||||||
|
import styles from "./Style.less";
|
||||||
|
import IconImage from "./IconImg";
|
||||||
|
import UploadScreenshots from "./UploadScreenshots";
|
||||||
|
|
||||||
|
const Paragraph = Typography;
|
||||||
|
const Dragger = Upload.Dragger;
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: 'index',
|
||||||
|
breadcrumbName: 'store',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'first',
|
||||||
|
breadcrumbName: 'dashboard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'second',
|
||||||
|
breadcrumbName: 'add new app',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
name: 'file',
|
||||||
|
multiple: false,
|
||||||
|
action: '//jsonplaceholder.typicode.com/posts/',
|
||||||
|
onChange(info) {
|
||||||
|
const status = info.file.status;
|
||||||
|
if (status !== 'uploading') {
|
||||||
|
console.log(info.file, info.fileList);
|
||||||
|
}
|
||||||
|
if (status === 'done') {
|
||||||
|
message.success(`${info.file.name} file uploaded successfully.`);
|
||||||
|
} else if (status === 'error') {
|
||||||
|
message.error(`${info.file.name} file upload failed.`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Step = Steps.Step;
|
||||||
|
|
||||||
|
const steps = [{
|
||||||
|
title: 'First',
|
||||||
|
content: Step1
|
||||||
|
}, {
|
||||||
|
title: 'Second',
|
||||||
|
content: Step2,
|
||||||
|
}, {
|
||||||
|
title: 'Last',
|
||||||
|
content: Step3,
|
||||||
|
}];
|
||||||
|
|
||||||
|
|
||||||
|
const {Option} = Select;
|
||||||
|
const {TextArea} = Input;
|
||||||
|
const InputGroup = Input.Group;
|
||||||
|
|
||||||
|
const formItemLayout = {
|
||||||
|
labelCol: {
|
||||||
|
span: 4,
|
||||||
|
},
|
||||||
|
wrapperCol: {
|
||||||
|
span: 20,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class EditableTagGroup extends React.Component {
|
||||||
|
state = {
|
||||||
|
tags: [],
|
||||||
|
inputVisible: false,
|
||||||
|
inputValue: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClose = (removedTag) => {
|
||||||
|
const tags = this.state.tags.filter(tag => tag !== removedTag);
|
||||||
|
console.log(tags);
|
||||||
|
this.setState({tags});
|
||||||
|
}
|
||||||
|
|
||||||
|
showInput = () => {
|
||||||
|
this.setState({inputVisible: true}, () => this.input.focus());
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange = (e) => {
|
||||||
|
this.setState({inputValue: e.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputConfirm = () => {
|
||||||
|
const {inputValue} = this.state;
|
||||||
|
let {tags} = this.state;
|
||||||
|
if (inputValue && tags.indexOf(inputValue) === -1) {
|
||||||
|
tags = [...tags, inputValue];
|
||||||
|
}
|
||||||
|
console.log(tags);
|
||||||
|
this.setState({
|
||||||
|
tags,
|
||||||
|
inputVisible: false,
|
||||||
|
inputValue: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveInputRef = input => this.input = input
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {tags, inputVisible, inputValue} = this.state;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{tags.map((tag, index) => {
|
||||||
|
const isLongTag = tag.length > 20;
|
||||||
|
const tagElem = (
|
||||||
|
<Tag key={tag} closable={index !== 0} onClose={() => this.handleClose(tag)}>
|
||||||
|
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
return isLongTag ? <Tooltip title={tag} key={tag}>{tagElem}</Tooltip> : tagElem;
|
||||||
|
})}
|
||||||
|
{inputVisible && (
|
||||||
|
<Input
|
||||||
|
ref={this.saveInputRef}
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
style={{width: 78}}
|
||||||
|
value={inputValue}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
onBlur={this.handleInputConfirm}
|
||||||
|
onPressEnter={this.handleInputConfirm}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!inputVisible && (
|
||||||
|
<Tag
|
||||||
|
onClick={this.showInput}
|
||||||
|
style={{background: '#fff', borderStyle: 'dashed'}}
|
||||||
|
>
|
||||||
|
<Icon type="plus"/> New Tag
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddNewApp extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
current: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tags = [];
|
||||||
|
|
||||||
|
addTag(key, value){
|
||||||
|
this.tags.push(<Option key={key}>{value}</Option>);
|
||||||
|
}
|
||||||
|
|
||||||
|
next() {
|
||||||
|
const current = this.state.current + 1;
|
||||||
|
this.setState({current});
|
||||||
|
}
|
||||||
|
|
||||||
|
prev() {
|
||||||
|
const current = this.state.current - 1;
|
||||||
|
this.setState({current});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {current} = this.state;
|
||||||
|
const Content = steps[current].content;
|
||||||
|
this.addTag('1','Lorem');
|
||||||
|
this.addTag('2','Ipsum');
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
title="Add New App"
|
||||||
|
breadcrumb={{routes}}
|
||||||
|
>
|
||||||
|
<div className="wrap">
|
||||||
|
<div className="content">
|
||||||
|
<Paragraph>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempo.
|
||||||
|
</Paragraph>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PageHeader>
|
||||||
|
<div style={{background: '#f0f2f5', padding: 24, minHeight: 720}}>
|
||||||
|
<Row>
|
||||||
|
<Col span={20} offset={2}>
|
||||||
|
<Card>
|
||||||
|
<Row>
|
||||||
|
<Col span={12}>
|
||||||
|
<div>
|
||||||
|
<Form labelAlign="left" layout="horizontal" className={styles.stepForm}
|
||||||
|
hideRequiredMark>
|
||||||
|
<Form.Item {...formItemLayout} label="Platform">
|
||||||
|
<Select placeholder="ex: android">
|
||||||
|
<Option value="Android">Android</Option>
|
||||||
|
<Option value="iOS">iOS</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Type">
|
||||||
|
<Select value="Enterprise">
|
||||||
|
<Option value="Enterprise" selected>Enterprise</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="App Name">
|
||||||
|
<Input placeholder="ex: Lorem App"/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Description">
|
||||||
|
<TextArea placeholder="Enter the description..." rows={7}/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Category">
|
||||||
|
<Select placeholder="Select a category">
|
||||||
|
<Option value="travel">Travel</Option>
|
||||||
|
<Option value="entertainment">Entertainment</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Price">
|
||||||
|
<Input prefix="$" placeholder="00.00"/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Is Sahred?">
|
||||||
|
<Switch checkedChildren={<Icon type="check" />} unCheckedChildren={<Icon type="close" />} defaultChecked />
|
||||||
|
</Form.Item>
|
||||||
|
<Divider/>
|
||||||
|
<Form.Item {...formItemLayout} label="Tags">
|
||||||
|
|
||||||
|
<InputGroup>
|
||||||
|
<Row gutter={8}>
|
||||||
|
<Col span={22}>
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder="Tags Mode"
|
||||||
|
>
|
||||||
|
{this.tags}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
<Col span={2}>
|
||||||
|
<Button type="dashed" shape="circle" icon="plus"/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</InputGroup>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Meta Daa">
|
||||||
|
<InputGroup>
|
||||||
|
<Row gutter={8}>
|
||||||
|
<Col span={10}>
|
||||||
|
<Input placeholder="Key"/>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Input placeholder="value"/>
|
||||||
|
</Col>
|
||||||
|
<Col span={2}>
|
||||||
|
<Button type="dashed" shape="circle" icon="plus"/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</InputGroup>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={12} style={{paddingTop: 40, paddingLeft: 20}}>
|
||||||
|
<p>Application</p>
|
||||||
|
<div style={{height: 170}}>
|
||||||
|
<Dragger {...props}>
|
||||||
|
<p className="ant-upload-drag-icon">
|
||||||
|
<Icon type="inbox"/>
|
||||||
|
</p>
|
||||||
|
<p className="ant-upload-text">Click or drag file to this area to
|
||||||
|
upload</p>
|
||||||
|
<p className="ant-upload-hint">Support for a single or bulk upload.
|
||||||
|
Strictly prohibit from uploading company data or other band
|
||||||
|
files</p>
|
||||||
|
</Dragger>
|
||||||
|
</div>
|
||||||
|
<Row style={{marginTop: 40}}>
|
||||||
|
<Col span={12}>
|
||||||
|
<p>Icon</p>
|
||||||
|
<IconImage/>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<p>Banner</p>
|
||||||
|
<IconImage/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
|
||||||
|
<Row style={{marginTop: 40}}>
|
||||||
|
<Col span={24}>
|
||||||
|
<p>Screenshots</p>
|
||||||
|
<UploadScreenshots/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddNewApp;
|
@ -0,0 +1,66 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Upload, Icon, message } from 'antd';
|
||||||
|
|
||||||
|
function getBase64(img, callback) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.addEventListener('load', () => callback(reader.result));
|
||||||
|
reader.readAsDataURL(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
function beforeUpload(file) {
|
||||||
|
const isJPG = file.type === 'image/jpeg';
|
||||||
|
if (!isJPG) {
|
||||||
|
message.error('You can only upload JPG file!');
|
||||||
|
}
|
||||||
|
const isLt2M = file.size / 1024 / 1024 < 2;
|
||||||
|
if (!isLt2M) {
|
||||||
|
message.error('Image must smaller than 2MB!');
|
||||||
|
}
|
||||||
|
return isJPG && isLt2M;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IconImage extends React.Component {
|
||||||
|
state = {
|
||||||
|
loading: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange = (info) => {
|
||||||
|
if (info.file.status === 'uploading') {
|
||||||
|
this.setState({ loading: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (info.file.status === 'done') {
|
||||||
|
// Get this url from response in real world.
|
||||||
|
getBase64(info.file.originFileObj, imageUrl => this.setState({
|
||||||
|
imageUrl,
|
||||||
|
loading: false,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const uploadButton = (
|
||||||
|
<div>
|
||||||
|
<Icon type={this.state.loading ? 'loading' : 'plus'} />
|
||||||
|
<div className="ant-upload-text">Upload</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
const imageUrl = this.state.imageUrl;
|
||||||
|
return (
|
||||||
|
<Upload
|
||||||
|
name="avatar"
|
||||||
|
listType="picture-card"
|
||||||
|
className="avatar-uploader"
|
||||||
|
showUploadList={false}
|
||||||
|
action="//jsonplaceholder.typicode.com/posts/"
|
||||||
|
beforeUpload={beforeUpload}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
>
|
||||||
|
{imageUrl ? <img src={imageUrl} alt="avatar" /> : uploadButton}
|
||||||
|
</Upload>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IconImage;
|
@ -0,0 +1,153 @@
|
|||||||
|
import React from "react";
|
||||||
|
import {Form, Input, Button, Select, Divider, Tag, Tooltip, Icon, Checkbox, Row, Col} from "antd";
|
||||||
|
import styles from './Style.less';
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
const { TextArea } = Input;
|
||||||
|
const InputGroup = Input.Group;
|
||||||
|
|
||||||
|
const formItemLayout = {
|
||||||
|
labelCol: {
|
||||||
|
span: 8,
|
||||||
|
},
|
||||||
|
wrapperCol: {
|
||||||
|
span: 16,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class EditableTagGroup extends React.Component {
|
||||||
|
state = {
|
||||||
|
tags: [],
|
||||||
|
inputVisible: false,
|
||||||
|
inputValue: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClose = (removedTag) => {
|
||||||
|
const tags = this.state.tags.filter(tag => tag !== removedTag);
|
||||||
|
console.log(tags);
|
||||||
|
this.setState({ tags });
|
||||||
|
}
|
||||||
|
|
||||||
|
showInput = () => {
|
||||||
|
this.setState({ inputVisible: true }, () => this.input.focus());
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange = (e) => {
|
||||||
|
this.setState({ inputValue: e.target.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputConfirm = () => {
|
||||||
|
const { inputValue } = this.state;
|
||||||
|
let { tags } = this.state;
|
||||||
|
if (inputValue && tags.indexOf(inputValue) === -1) {
|
||||||
|
tags = [...tags, inputValue];
|
||||||
|
}
|
||||||
|
console.log(tags);
|
||||||
|
this.setState({
|
||||||
|
tags,
|
||||||
|
inputVisible: false,
|
||||||
|
inputValue: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
saveInputRef = input => this.input = input
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { tags, inputVisible, inputValue } = this.state;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{tags.map((tag, index) => {
|
||||||
|
const isLongTag = tag.length > 20;
|
||||||
|
const tagElem = (
|
||||||
|
<Tag key={tag} closable={index !== 0} onClose={() => this.handleClose(tag)}>
|
||||||
|
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
return isLongTag ? <Tooltip title={tag} key={tag}>{tagElem}</Tooltip> : tagElem;
|
||||||
|
})}
|
||||||
|
{inputVisible && (
|
||||||
|
<Input
|
||||||
|
ref={this.saveInputRef}
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
style={{ width: 78 }}
|
||||||
|
value={inputValue}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
onBlur={this.handleInputConfirm}
|
||||||
|
onPressEnter={this.handleInputConfirm}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!inputVisible && (
|
||||||
|
<Tag
|
||||||
|
onClick={this.showInput}
|
||||||
|
style={{ background: '#fff', borderStyle: 'dashed' }}
|
||||||
|
>
|
||||||
|
<Icon type="plus" /> New Tag
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Step1 extends React.Component {
|
||||||
|
render() {
|
||||||
|
console.log("hhhoohh");
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Form layout="horizontal" className={styles.stepForm} hideRequiredMark>
|
||||||
|
|
||||||
|
<Form.Item {...formItemLayout} label="Platform">
|
||||||
|
<Select placeholder="ex: android">
|
||||||
|
<Option value="Android">Android</Option>
|
||||||
|
<Option value="iOS">iOS</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Type">
|
||||||
|
<Select value="Enterprise">
|
||||||
|
<Option value="Enterprise" selected>Enterprise</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Name">
|
||||||
|
<Input placeholder="App Name" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Description">
|
||||||
|
<TextArea placeholder="Enter the description" rows={4} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Category">
|
||||||
|
<Select placeholder="Select a category">
|
||||||
|
<Option value="travel">Travel</Option>
|
||||||
|
<Option value="entertainment">Entertainment</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Tags">
|
||||||
|
<EditableTagGroup/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Price">
|
||||||
|
<Input prefix="$" placeholder="00.00" />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Share with all tenents?">
|
||||||
|
<Checkbox > </Checkbox>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item {...formItemLayout} label="Meta Daa">
|
||||||
|
<InputGroup>
|
||||||
|
<Row gutter={8}>
|
||||||
|
<Col span={5}>
|
||||||
|
<Input placeholder="Key" />
|
||||||
|
</Col>
|
||||||
|
<Col span={10}>
|
||||||
|
<Input placeholder="value" />
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<Button type="dashed" shape="circle" icon="plus" />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</InputGroup>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Step1;
|
@ -0,0 +1,12 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
class Step2 extends React.Component {
|
||||||
|
render() {
|
||||||
|
console.log("hhhoohh");
|
||||||
|
return (
|
||||||
|
<p>tttoooeeee</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Step2;
|
@ -0,0 +1,12 @@
|
|||||||
|
import React from "react"
|
||||||
|
|
||||||
|
class Step3 extends React.Component {
|
||||||
|
render() {
|
||||||
|
console.log("hhhoohh");
|
||||||
|
return (
|
||||||
|
<p>tttoooeeee</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Step3;
|
@ -0,0 +1,4 @@
|
|||||||
|
.stepForm {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 40px auto 0;
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Upload, Icon, Modal} from 'antd';
|
||||||
|
|
||||||
|
|
||||||
|
class UploadScreenshots extends React.Component {
|
||||||
|
state = {
|
||||||
|
previewVisible: false,
|
||||||
|
previewImage: '',
|
||||||
|
fileList: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCancel = () => this.setState({ previewVisible: false });
|
||||||
|
|
||||||
|
handlePreview = (file) => {
|
||||||
|
this.setState({
|
||||||
|
previewImage: file.url || file.thumbUrl,
|
||||||
|
previewVisible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange = ({ fileList }) => this.setState({ fileList });
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { previewVisible, previewImage, fileList } = this.state;
|
||||||
|
const uploadButton = (
|
||||||
|
<div>
|
||||||
|
<Icon type="plus" />
|
||||||
|
<div className="ant-upload-text">Upload</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="clearfix">
|
||||||
|
<Upload
|
||||||
|
action="//jsonplaceholder.typicode.com/posts/"
|
||||||
|
listType="picture-card"
|
||||||
|
fileList={fileList}
|
||||||
|
onPreview={this.handlePreview}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
>
|
||||||
|
{fileList.length >= 3 ? null : uploadButton}
|
||||||
|
</Upload>
|
||||||
|
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
|
||||||
|
<img alt="example" style={{ width: '100%' }} src={previewImage} />
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default UploadScreenshots;
|
@ -0,0 +1,49 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Upload, Icon, Modal} from 'antd';
|
||||||
|
|
||||||
|
|
||||||
|
class AddTagModal extends React.Component {
|
||||||
|
state = {
|
||||||
|
previewVisible: false,
|
||||||
|
previewImage: '',
|
||||||
|
fileList: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCancel = () => this.setState({ previewVisible: false });
|
||||||
|
|
||||||
|
handlePreview = (file) => {
|
||||||
|
this.setState({
|
||||||
|
previewImage: file.url || file.thumbUrl,
|
||||||
|
previewVisible: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange = ({ fileList }) => this.setState({ fileList });
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { previewVisible, previewImage, fileList } = this.state;
|
||||||
|
const uploadButton = (
|
||||||
|
<div>
|
||||||
|
<Icon type="plus" />
|
||||||
|
<div className="ant-upload-text">Upload</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="clearfix">
|
||||||
|
<Upload
|
||||||
|
action="//jsonplaceholder.typicode.com/posts/"
|
||||||
|
listType="picture-card"
|
||||||
|
fileList={fileList}
|
||||||
|
onPreview={this.handlePreview}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
>
|
||||||
|
{fileList.length >= 3 ? null : uploadButton}
|
||||||
|
</Upload>
|
||||||
|
<Modal visible={previewVisible} footer={null} onCancel={this.handleCancel}>
|
||||||
|
<img alt="example" style={{ width: '100%' }} src={previewImage} />
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default AddTagModal;
|
@ -0,0 +1,60 @@
|
|||||||
|
import React from "react";
|
||||||
|
import "antd/dist/antd.css";
|
||||||
|
import {PageHeader, Typography,Input, Button, Row, Col} from "antd";
|
||||||
|
import AppList from "../../../components/apps/AppList";
|
||||||
|
import ReleaseModal from "../../../components/apps/ReleaseModal";
|
||||||
|
|
||||||
|
const Search = Input.Search;
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: 'index',
|
||||||
|
breadcrumbName: 'store',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'first',
|
||||||
|
breadcrumbName: 'Dashboard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'second',
|
||||||
|
breadcrumbName: 'Apps',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
class Apps extends React.Component {
|
||||||
|
routes;
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.routes = props.routes;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<PageHeader
|
||||||
|
breadcrumb={{routes}}
|
||||||
|
/>
|
||||||
|
<div style={{background: '#f0f2f5', padding: 24, minHeight: 780}}>
|
||||||
|
<Row style={{padding:10}}>
|
||||||
|
<Col span={6} offset={18}>
|
||||||
|
<Search
|
||||||
|
placeholder="search"
|
||||||
|
onSearch={value => console.log(value)}
|
||||||
|
style={{ width: 200}}
|
||||||
|
/>
|
||||||
|
<Button style={{margin:5}}>Advanced Search</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<ReleaseModal/>
|
||||||
|
<AppList/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Apps;
|
@ -0,0 +1,104 @@
|
|||||||
|
import React from "react";
|
||||||
|
import '../../../../App.css';
|
||||||
|
import {Skeleton, Typography, Row, Col, Card} from "antd";
|
||||||
|
import {connect} from "react-redux";
|
||||||
|
import ReleaseView from "../../../../components/apps/release/ReleaseView";
|
||||||
|
import {getRelease, setLoading} from "../../../../js/actions";
|
||||||
|
|
||||||
|
const {Title} = Typography;
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: 'index',
|
||||||
|
breadcrumbName: 'store',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'first',
|
||||||
|
breadcrumbName: 'Dashboard',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'second',
|
||||||
|
breadcrumbName: 'Apps',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
return {
|
||||||
|
release: state.release,
|
||||||
|
releaseLoading: state.loadingState.release
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
getRelease: (uuid) => dispatch(getRelease(uuid)),
|
||||||
|
setLoading: (stateToLoad) => dispatch(setLoading(stateToLoad))
|
||||||
|
});
|
||||||
|
|
||||||
|
class ConnectedRelease extends React.Component {
|
||||||
|
routes;
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.routes = props.routes;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const {uuid} = this.props.match.params;
|
||||||
|
this.props.setLoading("release");
|
||||||
|
this.props.getRelease(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
const release = this.props.release;
|
||||||
|
let content = <Title level={3}>No Releases Found</Title>;
|
||||||
|
|
||||||
|
if (release != null) {
|
||||||
|
content = <ReleaseView release={release}/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{background: '#f0f2f5', padding: 24, minHeight: 780}}>
|
||||||
|
<Row style={{padding: 10}}>
|
||||||
|
<Col lg={4}>
|
||||||
|
|
||||||
|
</Col>
|
||||||
|
<Col lg={16} md={24} style={{padding: 3}}>
|
||||||
|
<Card>
|
||||||
|
<Skeleton loading={this.props.releaseLoading} avatar={{size: 'large'}} active paragraph={{rows: 8}}>
|
||||||
|
{content}
|
||||||
|
</Skeleton>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// //todo remove uppercase
|
||||||
|
// return (
|
||||||
|
// <div>
|
||||||
|
// <div className="main-container">
|
||||||
|
// <Row style={{padding: 10}}>
|
||||||
|
// <Col lg={4}>
|
||||||
|
//
|
||||||
|
// </Col>
|
||||||
|
// <Col lg={16} md={24} style={{padding: 3}}>
|
||||||
|
// <Card>
|
||||||
|
// <ReleaseView release={release}/>
|
||||||
|
// </Card>
|
||||||
|
// </Col>
|
||||||
|
// </Row>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
//
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Release = connect(mapStateToProps, mapDispatchToProps)(ConnectedRelease);
|
||||||
|
|
||||||
|
export default Release;
|
Loading…
Reference in new issue