Социальная подкаст-платформа
Рассмотрение сервисов, позволяющих прослушивать подкасты. Разработка платформы, сочетающей в себе функции для удобного поиска подкастов и возможность их обсуждения с ведущими и другими слушателями. Оценка удобства работы с пользовательским интерфейсом.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | дипломная работа |
Язык | русский |
Дата добавления | 14.12.2019 |
Размер файла | 2,7 M |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
{tss && <button type="button" className="btn btn-lg btn-info btn-block"
disabled>Register</button>}
<hr className="hr10"/>
{tss &&
<div className="alert alert-success text-center">You successfully registered. Go to Sign
in...</div>}
{tse === errorEnum.USERNAME_TAKEN &&
<div className="alert alert-danger text-center">{errorEnum.USERNAME_TAKEN}</div>}
{tse === errorEnum.UNKNOWN &&
<div className="alert alert-danger text-center">{errorEnum.UNKNOWN}</div>}
{tse === errorEnum.LONG_USERNAME &&
<div className="alert alert-danger text-center">{errorEnum.LONG_USERNAME}</div>}
{tse === errorEnum.WRONG_USERNAME &&
<div className="alert alert-danger text-center">{errorEnum.WRONG_USERNAME}</div>}
{tse === errorEnum.LONG_NAME &&
<div className="alert alert-danger text-center">{errorEnum.LONG_NAME}</div>}
{tse === errorEnum.WRONG_NAME &&
<div className="alert alert-danger text-center">{errorEnum.WRONG_NAME}</div>}
{tse === errorEnum.WRONG_EMAIL &&
<div className="alert alert-danger text-center">{errorEnum.WRONG_EMAIL}</div>}
{tse === errorEnum.WRONG_PASS &&
<div className="alert alert-danger text-center">{errorEnum.WRONG_PASS}</div>}
{tse === errorEnum.WRONG_PASS_REPEAT &&
<div className="alert alert-danger text-center">{errorEnum.WRONG_PASS_REPEAT}</div>}
{tse === errorEnum.WRONG_PASS_LENGTH &&
<div className="alert alert-danger text-center">{errorEnum.WRONG_PASS_LENGTH}</div>}
</form>
</div>
</div>
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\login\SigninPage.js
const React = require('react');
import {getFullCSRFParam, saveAllCookie} from "../Helper";
export default class SigninPage extends React.Component {
constructor(props) {
super(props);
this.state = {
password: '',
username: '',
logout: false,
error: false
};
}
refreshPageParams(url) {
this.setState({
logout: false,
error: false
});
if (url.searchParams.get("error") !== null) this.setState({error: true});
if (url.searchParams.get("logout") !== null) this.setState({logout: true});
if (!this.state.error && !this.state.logout) return false;
}
componentDidMount() {
let url = new URL(window.location.href);
this.refreshPageParams(url);
$('form').submit(false);
}
signin() {
fetch(`/perform_login?` +
`username=${this.state.username}` + `&` +
`password=${this.state.password}` + `&` +
getFullCSRFParam(), {
method: 'POST'
}).then((response) => {
if (!this.refreshPageParams(new URL(response.url))) {
if (!this.state.error)
this.loadUserInfoFromServerAndRedirect();
}
});
}
onChangePass(event) {
this.setState({password: event.target.value});
}
onChangeUsername(event) {
this.setState({username: event.target.value});
}
loadUserInfoFromServerAndRedirect() {
$.getJSON(`/getUserInfo?username=${this.state.username}`, (data) => {
saveAllCookie(data.username, data.id, data.email, data.creationDate, data.name, data.bio)
}).then(() => {
this.props.history.push('/');
});
}
toRegiser = () => {
this.props.history.push('/register');
};
render() {
return (
<div className="center-block main-center signin-css">
<form onSubmit={this.signin.bind(this)} className="form-signin center-block" style={{maxWidth: 320}}>
<hr className="hr100"/>
<hr className="hr60"/>
<img src="/images/discasst.png" type="image" alt="" width="260" className="center-block"></img>
<hr className="hr15"/>
<input type="username" className="form-control form-control-signin-css" placeholder="Username"
required id='signinUser'
autoFocus onChange={this.onChangeUsername.bind(this)}></input>
<input type="password" className="form-control form-control-signin-css" placeholder="Password"
required id='signinPass'
onChange={this.onChangePass.bind(this)}></input>
<br/>
<button type="submit" className="btn btn-lg btn-info btn-block">Sign In</button>
<br/>
<button type="button" className="btn btn-lg btn-info btn-block" onClick={() => {
this.toRegiser()
}}>Sign Up
</button>
<br/>
{this.state.logout &&
<div className="alert alert-info text-center">You signed out successfully.</div>}
{this.state.error &&
<div className="alert alert-danger text-center">Invalid Username or Password.</div>}
</form>
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\podcastPage\ EpisodePanel.js
import React from 'react';
import ReactDisqusComments from 'react-disqus-comments';
export default class EpisodePanel extends React.Component {
constructor() {
super();
this.state = {
isCommentsOn: false
}
}
closeComments = () => {
this.setState({isCommentsOn: false});
};
render() {
return (
<div className="container-fluid">
<div className="panel panel-info" style={{marginBottom: 10}}>
<div className="panel-body" style={{maxWidth: '100%'}}>
<h4 style={{marginTop: 0}}>{this.props.episode.name}</h4>
<h5 style={{color: '#acacac', marginBottom: 0}}>{this.props.episode.date}</h5>
<hr style={{marginTop: 10}}/>
<iframe width="100%" height="63" src={`${this.props.episode.podsterLink}/embed/15?link=0&ap=0`}
frameborder="0" allowtransparency="true"></iframe>
<p className="text-justify">{this.props.episode.description}
</p>
<button
onClick={() => {
if (!this.state.isCommentsOn) this.props.closeAllComments();
this.setState({isCommentsOn: !this.state.isCommentsOn});
}}
type="button"
className="btn btn-info btn-md pull-right "
>
{this.state.isCommentsOn ? 'Свернуть комментарии': 'Обсудить'}
</button>
{this.state.isCommentsOn &&
<ReactDisqusComments
shortname={'discasst'}
identifier={'http://discasst.com/#!/episode/' + this.props.episode.id}
title={this.props.episode.name}
url={'http://discasst.com/#!/episode/' + this.props.episode.id}
onNewComment={() => {
}}
id={this.props.episode.id}
/>
}
</div>
</div>
</div>
);
}
}
Файл: Discasst\src\main\resources\static\app\components\podcastPage\ PodcastList.js
import React from 'react';
import EpisodePanel from './EpisodePanel';
import WelcomePanel from './tweets/WelcomePanel';
import {getCookie} from '../Helper';
export default class PodcastList extends React.Component {
constructor() {
super();
this.state = {
episodes: [],
pageSize: 5,
loading: false,
currPage: 0,
allPages: false,
};
}
componentDidMount() {
this.getEpisodes(0);
const that = this;
window.onscroll = () => {
if (!that.state.allPages && !that.state.loading) {
//(document.body.scrollHeight - window.scrollY - document.body.offsetHeight) <= 100
if ((window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 100)) {
let nextPage = that.state.currPage + 1;
console.log("End of page, load next page: " + nextPage);
that.setState({currPage: nextPage, loading: true});
that.getEpisodes(nextPage);
}
}
};
}
getEpisodes(page) {
$.ajax({
url: `${this.props.feed ? "/getEpisodesInFeedByUserIdInPage": "/getEpisodesByPodcastIdInPage"}?id=${this.props.feed ? getCookie('userId'): this.props.podcastId}&page=${page}&size=${this.state.pageSize}`,
type: 'get',
contentType: 'application/json',
success: (response) => {
this.setState(
{
allPages: response.last,
loading: false,
episodes: this.state.episodes.concat(response.content)
}
);
},
error: (jqXhr, textStatus, errorThrown) => {
console.log(errorThrown + " " + textStatus);
}
});
}
closeAllComments = () => {
this.state.episodes.map((ep) => {
this.refs['episode' + ep.id].closeComments();
});
};
render() {
this.episodes = [];
let episodeList = this.state.episodes.map(ep =>
<EpisodePanel
episode={ep}
key={ep.id}
history={this.props.history}
closeAllComments={this.closeAllComments}
ref={'episode' + ep.id}
/>
);
return (
<div>
{this.state.episodes.length === 0 && <WelcomePanel/>}
{episodeList}
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\podcastPage\ PodcastPage.js
import Navbar from '../Navbar';
import PodcastProfile from "./PodcastProfile";
import PodcastPageContent from './PodcastPageContent';
import {getCookie} from '../Helper';
const React = require('react');
export default class PodcastPage extends React.Component {
constructor(props) {
super(props);
this.state = {
podcast: null,
coauthors: [],
coauthorsInfo: [],
myPodcast: false
};
}
componentDidMount() {
this.loadPodcastInfo();
}
loadPodcastInfo() {
fetch(`/getPodcastById?id=${this.props.match.params.podcastId}`)
.then((response) => {
return response.json()
})
.then((json) => {
console.log(json);
this.setState({podcast: json});
this.loadCoauthors();
});
}
loadCoauthors() {
fetch(`/getCoauthors?podcastId=${this.props.match.params.podcastId}`)
.then((response) => {
return response.json()
})
.then((json) => {
let coauthorsIds = [this.state.podcast.authorId.toString(),...json.map(x => x.userId.toString())];
console.log(coauthorsIds)
this.setState({coauthors: coauthorsIds});
this.loadCoauthorsInfo(coauthorsIds);
this.myPodcastCheck();
});
}
myPodcastCheck() {
if (this.state.coauthors.indexOf(getCookie("userId")) !== -1) this.setState({myPodcast: true});
}
loadCoauthorsInfo(coauthorsIds) {
let data = {
"ids": coauthorsIds
};
$.ajax({
url: `/getCoauthorsByIdList`,
type: 'post',
contentType: 'application/json',
data: JSON.stringify(data),
success: (response) => {
console.log(response);
this.setState({coauthorsInfo: response});
},
error: (jqXhr, textStatus, errorThrown) => {
console.log(errorThrown + " " + textStatus);
}
});
}
render() {
return (
<div>
<Navbar history={this.props.history}/>
<div className="container-fluid">
{this.state.podcast !== null &&
<div style={{display: "flex",}}>
<div style={{width: "25%"}}>
<PodcastProfile podcast={this.state.podcast} coauthors={this.state.coauthorsInfo}
history={this.props.history}/>
</div>
<div style={{width: "50%"}}>
<PodcastPageContent
feed={false}
podcastId={this.state.podcast.id}
myPodcast={this.state.myPodcast}
history={this.props.history}
/>
</div>
<div style={{width: "25%"}}>
</div>
</div>}
</div>
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\podcastPage\ PodcastPageContent.js
import React, {Component} from 'react';
import {Button, ButtonGroup} from 'react-bootstrap';
import PodcastList from './PodcastList';
import TweetPage from './tweets/TweetPage';
export default class PodcastPageContent extends Component {
constructor() {
super();
this.state = {
tweetPage: false
}
}
onTweetPageClick = (e) => {
this.setState({tweetPage: true});
e.target.blur();
};
onPodcastPageClick = (e) => {
this.setState({tweetPage: false});
e.target.blur();
};
render() {
return (
<div style={{textAlign: "center"}}>
<ButtonGroup style={{marginBottom: 20}}>
<Button
onClick={this.onPodcastPageClick}
style={{width: 100}}
bsStyle={!this.state.tweetPage ? "info": "default"}>
Выпуски
</Button>
<Button
onClick={this.onTweetPageClick}
style={{width: 100}}
bsStyle={this.state.tweetPage ? "info": "default"}>
Микроблог
</Button>
</ButtonGroup>
{this.state.tweetPage ? <TweetPage
feed={this.props.feed}
podcastId={this.props.podcastId}
myPodcast={this.props.myPodcast}
history={this.props.history}
/>:
<PodcastList
feed={this.props.feed}
podcastId={this.props.podcastId}
history={this.props.history}
/>
}
</div>
);
}
}
Файл: Discasst\src\main\resources\static\app\components\podcastPage\ PodcastProfile.js
import {getCookie, getFullCSRFParam} from '../Helper';
const React = require('react');
export default class PodcastProfile extends React.Component {
constructor(props) {
super(props);
this.state = {
genres: null,
podcast: null,
myDecision: 0,
mySubscribe: false,
isUserSignedIn: false,
extraSub: 0,
emptyLinks: false
};
}
componentDidMount() {
const podcast = this.state.podcast === null ? this.props.podcast: this.state.podcast;
if (podcast.itunesLink === "" && podcast.vkLink === "" && podcast.twitterLink === "" && podcast.facebookLink === "") this.setState({emptyLinks: true})
this.isUserLogIn();
this.loadAllGenres();
}
loadAllGenres() {
fetch(`/getAllGenres`)
.then((response) => {
return response.json()
})
.then((json) => {
console.log(json)
this.setState({genres: json, loadData: this.state.loadData + 1});
});
}
ratingChange = (rating) => {
if (this.state.isUserSignedIn === false) {
this.props.history.push('/signin');
} else {
let obj = {
"podcastId": this.props.podcast.id,
"userId": getCookie('userId'),
"decision": rating,
};
let rate = JSON.stringify(obj);
const that = this;
$.ajax({
url: `/vote?${getFullCSRFParam()}`,
type: 'post',
contentType: 'application/json',
data: rate,
success: () => {
this.setState({myDecision: rating ? 1: -1});
this.loadNewPodcastInfo();
},
error: (jqXhr, textStatus, errorThrown) => {
console.log(errorThrown + " " + textStatus);
}
});
}
};
isUserLogIn() {
let username = getCookie("username");
if (username === "" || username === undefined) {
this.setState({isUserSignedIn: false});
return false;
}
fetch(`/loginCheck?${getFullCSRFParam()}`, {
method: 'POST'
}).then((response) => {
if (response.status === 200 && !response.redirected) {
this.setState({isUserSignedIn: true});
this.loadUserRatingAndSubscribeInfo();
return true;
} else {
this.setState({isUserSignedIn: false});
return false;
}
});
}
loadNewPodcastInfo() {
fetch(`/getPodcastById?id=${this.props.podcast.id}`)
.then((response) => {
return response.json()
})
.then((json) => {
this.setState({podcast: json});
});
}
loadUserRatingAndSubscribeInfo() {
fetch(`/getMyVote?userId=${getCookie("userId")}&podcastId=${this.props.podcast.id}`)
.then((response) => {
return response.json()
})
.then((json) => {
console.log(json);
this.setState({myDecision: json});
this.loadUserSubscribeInfo();
});
}
loadUserSubscribeInfo() {
fetch(`/getMySubscribe?userId=${getCookie("userId")}&podcastId=${this.props.podcast.id}`)
.then((response) => {
return response.json()
})
.then((json) => {
console.log(json);
this.setState({mySubscribe: json});
});
}
subscribeStateChange = () => {
if (this.state.isUserSignedIn === false) {
this.props.history.push('/signin');
} else {
fetch(`/subscribe?userId=${getCookie("userId")}&podcastId=${this.props.podcast.id}`)
.then((response) => {
if (response.status === 200) {
this.setState({mySubscribe: !this.state.mySubscribe})
this.loadNewPodcastInfo();
}
});
}
};
render() {
const podcast = this.state.podcast === null ? this.props.podcast: this.state.podcast;
return (
<div className="container-fluid">
<div className="panel panel-info">
<div className="panel-body" style={{maxWidth: "100%"}}>
<div className="row">
<div className="col-xs-4" style={{width: "180px", marginBottom: -20}}>
<div className="panel panel-info"
style={{width: "150px", textAlign: "center", paddingBottom: 10}}>
<br></br>
<img src="/images/podcastDefaultLogo.png"
className="img-rounded" style={{width: "120px"}}></img><br></br>
<div className="panel-body">
<button onClick={() => {
this.ratingChange(false)
}} type="button"
className={this.state.myDecision === -1 ? "btn btn-danger btn-xs": "btn btn-info btn-xs"}>
<span
className="glyphicon glyphicon-minus">
</span>
</button>
 <span className="badge">{podcast.rating}</span> 
<button onClick={() => {
this.ratingChange(true)
}} type="button"
className={this.state.myDecision === 1 ? "btn btn-success btn-xs": "btn btn-info btn-xs"}>
<span
className="glyphicon glyphicon-plus">
</span>
</button>
</div>
</div>
<div>
<button
type="button"
style={{width: "150px"}}
className={this.state.mySubscribe ? "btn btn-success btn-md": "btn btn-info btn-md"}
onClick={() => {
this.subscribeStateChange()
}}
>
{this.state.mySubscribe ? "Отписаться": "Подписаться"}
</button>
</div>
<br></br>
</div>
<div className="container-fluid">
<h4>{podcast.name}</h4>
<h4>
{this.state.genres !== null &&
<small>{this.state.genres[podcast.genreId].name}</small>}
</h4>
<hr></hr>
<p className="text-justify" style={{minHeight: 160}}>{podcast.description}</p>
<hr></hr>
<div className="row">
<div className="col-xs-6">
<h5 className="text-center">Подписчиков: {podcast.subscribersCount}</h5>
</div>
<div className="col-xs-6">
<h5 className="text-center">Выпусков: {podcast.episodeCount}</h5>
</div>
</div>
{!this.state.emptyLinks && <div>
<hr></hr>
</div>}
{!this.state.emptyLinks && <div className="container-fluid text-center">
{podcast.itunesLink !== "" && <a href={podcast.itunesLink}><img
src="/images/itunesLogo.png"
className="img-rounded" style={{width: "30px", margin: "0 10px 0 10px"}}></img></a>}
{podcast.vkLink !== "" && <a href={podcast.vkLink}><img
src="/images/vkLogo.png"
className="img-rounded" style={{width: "30px", margin: "0 10px 0 10px"}}></img></a>}
{podcast.twitterLink !== "" && <a href={podcast.twitterLink}><img
src="/images/twitterLogo.png"
className="img-rounded" style={{width: "30px", margin: "0 10px 0 10px"}}></img></a>}
{podcast.facebookLink !== "" && <a href={podcast.facebookLink}><img
src="/images/facebookLogo.png"
className="img-rounded" style={{width: "30px", margin: "0 10px 0 10px"}}></img>
</a>}
</div>}
<hr></hr>
<div>
<h5>Ведущие:</h5>
<div className="container-fluid text-center">
{this.props.coauthors.map(item =>
<a key={item.id} onClick={() => {
this.props.history.push(`/user/${item.username}`)
}}>
<h4>{item.name} (@{item.username})</h4>
</a>
)}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\podcastPage\tweets\ DeleteTweetButton.js
const React = require('react');
export default class Navbar extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<button type="button"
className="btn btn-info btn-xs pull-right"
onClick={this.props.onDelete}
style={{marginLeft: -24}}
>
<span className="glyphicon glyphicon-remove"></span>
</button>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\podcastPage\tweets\ LoadingPanel.js
const React = require('react');
export default class LoadingPanel extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div hidden={this.props.hidden}>
<img className="center-block" src="/images/loading.svg" width="70" height="70"/>
<br/>
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\podcastPage\tweets\ Tweet.js
const React = require('react');
const url = '/deleteTweet?tweetId=';
import DeleteTweetButton from './DeleteTweetButton';
import {getCookie, getFullCSRFParam} from "../../Helper";
export default class Tweet extends React.Component {
constructor(props) {
super(props);
}
delete() {
const that = this;
const tweetId = this.props.tweet.id;
$.ajax({
url: url + tweetId + '&' + getFullCSRFParam(),
type: 'post',
success: () => {
that.props.callbackFromChild(tweetId);
},
error: (jqXhr, textStatus, errorThrown) => {
console.log(errorThrown + " " + textStatus);
}
});
}
render() {
const {text, creationDate, authorUsername} = this.props.tweet;
return (
<div className="container-fluid">
<div className="panel panel-info" style={{marginBottom: 10}}>
<div className="panel-body" style={{backgroundColor: '#ffffff'}}>
<div>
{this.props.myPodcast && getCookie("username") === authorUsername &&
<DeleteTweetButton onDelete={this.delete.bind(this)}/>}
<font size="3"><a onClick={() => {
this.props.history.push(`/user/${authorUsername}`)
}}>@{authorUsername}</a></font>
<h5 style={{color: '#acacac', marginBottom: 0}}>{creationDate}</h5>
</div>
<hr style={{margin: "10px 0 15px 0"}}/>
<font size="3" style={{whiteSpace: "pre-wrap"}}>{text}</font>
</div>
</div>
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\podcastPage\tweets\ TweetInput.js
import {getCookie, getFullCSRFParam} from "../../Helper";
const React = require('react');
const addTweetUrl = '/addTweet';
export default class TweetInput extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
}
handleSubmit = () => {
if (this.state.value !== '') {
this.addNewTweet();
}
};
addNewTweet() {
let obj = {
"text": this.state.value,
"authorUsername": getCookie("username"),
"authorId": getCookie("userId"),
"podcastId": this.props.podcastId
};
let tweetJson = JSON.stringify(obj);
const that = this;
$.ajax({
url: addTweetUrl + '?' + getFullCSRFParam(),
type: 'post',
contentType: 'application/json',
data: tweetJson,
success: () => {
that.afterPostNewTwit();
},
error: (jqXhr, textStatus, errorThrown) => {
console.log(errorThrown + " " + textStatus);
}
});
}
afterPostNewTwit() {
console.log("Tweet: " + this.state.value);
this.setState({value: ''});
this.refs.tweetTextarea.value = '';
this.refs.tweetTextarea.focus();
this.props.callbackFromChild();
}
handleChange = (event) => {
this.setState({value: event.target.value});
};
render() {
return (
<div className="container-fluid">
<div className="panel panel-info">
<div className="panel-body">
<div className="form-group shadow-textarea">
<textarea style={{resize: 'none'}} className="form-control z-depth-1"
rows="4" maxLength="200" autoFocus ref="tweetTextarea"
placeholder="What's happening?" onChange={this.handleChange}>
</textarea>
</div>
<div className="btn-group-md">
<button onClick={this.handleSubmit} type="button" className="btn btn-info pull-right"
style={{width: 100}}>Tweet
</button>
</div>
</div>
</div>
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\podcastPage\tweets\ TweetList.js
const React = require('react');
import Tweet from './Tweet';
export default class TweetList extends React.Component {
constructor(props) {
super(props);
}
deleteTwitCallback(id) {
this.props.callbackFromChild(id);
}
render() {
let tweets = this.props.tweets.map(tweet =>
<Tweet id={"tweet" + tweet.id}
key={"tweet" + tweet.id}
tweet={tweet}
callbackFromChild={this.deleteTwitCallback.bind(this)}
myPodcast={this.props.myPodcast}
history={this.props.history}
/>
);
const date = new Date();
console.log("Rerender TweetList at time: " + date.getMinutes() + "." + date.getSeconds() + "." + date.getMilliseconds());
return (
<div>
{tweets}
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\podcastPage\tweets\ TweetPage.js
const React = require('react');
import TweetList from './TweetList';
import TweetInput from './TweetInput';
import WelcomePanel from './WelcomePanel';
import LoadingPanel from './LoadingPanel';
import {getCookie} from '../../Helper';
export default class TweetPage extends React.Component {
constructor(props) {
super(props);
this.state = {
tweets: [],
currPage: 0,
pageSize: 10,
allPages: false,
numberOfTweets: 0,
loading: false,
loadImg: null
};
}
componentDidMount() {
this.getNewPageOfTweetsFromServer(0, this.state.pageSize * (this.state.currPage + 1));
const that = this;
/**
* Проверяет конец страницы, если так и страница не последняя, то грузит новую страницу твитов.
*/
window.onscroll = () => {
if (!that.state.allPages && !that.state.loading) {
if ((window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 100)) {
let nextPage = that.state.currPage + 1;
console.log("End of page, load next page: " + nextPage);
that.setState({currPage: nextPage, loading: true});
that.getNewPageOfTweetsFromServer(nextPage, that.state.pageSize);
}
}
};
}
/**
* Грузит с сервера последний твит, помещает его в массив твитов, удаляет последний элемент массива
* если не последняя страница и ререндерит компонент.
*/
refreshTweetArrayAfterAdd() {
fetch(`${this.props.feed ? "/getAllTwitsByUserIdFeed": "/getAllTwitsByPodcastId"}?page=0&size=1&id=${this.props.feed ? getCookie('userId'): this.props.podcastId}`)
.then((response) => {
return response.json()
})
.then((json) => {
let array = this.state.tweets;
if (!this.state.allPages) array.splice(array.length - 1, 1);
this.setState({tweets: json.content.concat(array)});
});
}
/**
* Загружает заново с сервера все страницы и ререндерит компонент.
* @param id айдишник удаляемого твита.
*/
refreshTweetArrayAfterDelete(id) {
fetch(`${this.props.feed ? "/getAllTwitsByUserIdFeed": "/getAllTwitsByPodcastId"}?page=0&size=${(this.state.currPage + 1) * this.state.pageSize}&id=${this.props.feed ? getCookie('userId'): this.props.podcastId}`)
.then((response) => {
return response.json()
})
.then((json) => {
this.setState({tweets: json.content});
const date = new Date();
console.log("Deleting tweet at time: " + date.getMinutes() + "." + date.getSeconds() + "." + date.getMilliseconds());
this.lastPageCheck(json);
});
}
/**
* Получает новую страницу твитов, добавляет её к уже существующему массиву твитов и ререндерит компонент.
*/
getNewPageOfTweetsFromServer(page, pagesize) {
fetch(`${this.props.feed ? "/getAllTwitsByUserIdFeed": "/getAllTwitsByPodcastId"}?page=${page}&size=${pagesize}&id=${this.props.feed ? getCookie('userId'): this.props.podcastId}`)
.then((response) => {
return response.json()
})
.then((json) => {
this.setState({tweets: this.state.tweets.concat(json.content)});
this.setState({numberOfTweets: json.totalElements});
this.lastPageCheck(json);
this.setState({loading: false});
});
}
lastPageCheck(json) {
this.setState({allPages: json.last});
}
addTweetCallback() {
this.refreshTweetArrayAfterAdd();
this.setState({numberOfTweets: (this.state.numberOfTweets + 1)});
}
deleteTwitCallback(id) {
this.refreshTweetArrayAfterDelete(id);
this.setState({numberOfTweets: (this.state.numberOfTweets - 1)});
}
render() {
return (
<div>
{this.props.myPodcast &&
<TweetInput podcastId={this.props.podcastId} callbackFromChild={this.addTweetCallback.bind(this)}/>}
<TweetList
tweets={this.state.tweets}
callbackFromChild={this.deleteTwitCallback.bind(this)}
myPodcast={this.props.myPodcast}
history={this.props.history}
/>
{!this.state.loading && this.state.numberOfTweets === 0 && <WelcomePanel/>}
<LoadingPanel hidden={!this.state.loading}/>
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\podcastPage\tweets\ WelcomePanel.js
const React = require('react');
export default class WelcomePanel extends React.Component {
render() {
return (
<div className="container-fluid">
<div className="panel panel-info" style={{height: '240px'}}>
<div className="panel-body text-center">
<h3 style={{color: '#a0a0a0'}}>Здесь еще ничего нет,</h3>
<h3 style={{color: '#a0a0a0'}}>но скоро обязательно что-то появится!</h3>
<br/>
<img src="/images/smile.png" width="60"/>
</div>
</div>
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\searchPage\PodcastLine.js
const React = require('react');
export default class PodcastLine extends React.Component {
constructor(props) {
super(props);
this.state = {
podcast: null
};
}
render() {
const podcast = this.state.podcast === null ? this.props.podcast: this.state.podcast;
return (
<div className="container-fluid">
<div className="panel panel-info" style={{marginBottom: 10}}>
<div className="panel-body">
<div className="row">
<div className="col-xs-4" style={{width: "180px"}}>
<div className="panel panel-info"
style={{width: "150px", textAlign: "center", marginBottom: 0}}>
<br></br>
<img src="images/podcastDefaultLogo.png"
className="img-rounded" style={{width: "120px"}}></img><br></br>
<div className="panel-body">
<span className="badge">{podcast.rating}</span>
</div>
</div>
</div>
<div className="container-fluid">
<h4>{podcast.name}
<small> {this.props.genres[podcast.genreId].name}</small>
</h4>
<hr/>
<p className="text-justify">{podcast.description}
</p>
<a>
<button type="button"
className="btn btn-info btn-md pull-right "
onClick={() => {
this.props.history.push(`/podcast/${podcast.id}`);
}}
>Подробнее
</button>
</a>
</div>
</div>
</div>
</div>
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\searchPage\PodcastList.js
const React = require('react');
import PodcastLine from "./PodcastLine";
export default class PodcastList extends React.Component {
constructor(props) {
super(props);
}
render() {
let podcastList = this.props.podcasts.map(podcast =>
<PodcastLine
history={this.props.history}
key={podcast.id}
podcast={podcast}
genres={this.props.genres}/>
);
return (
<div>
{podcastList}
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\searchPage\SearchPage.js
import PodcastList from "./PodcastList";
import Navbar from '../Navbar';
import SearchPanel from './SearchPanel';
import {getGenres} from "../Helper";
const React = require('react');
export default class SearchPage extends React.Component {
constructor(props) {
super(props);
this.state = {
podcasts: [],
genres: [],
loadData: 0,
pageSize: 5,
loading: false,
currPage: 0,
allPages: false,
filter: "",
notInGenres: [],
byRating: false
};
}
componentDidMount() {
const {state} = this.props.location;
if (state) {
this.loadPodcasts(state.filterValue, [], false, 0);
} else {
this.loadPodcasts("", [], false, 0);
}
this.loadAllGenres();
const that = this;
window.onscroll = () => {
if (!that.state.allPages && !that.state.loading) {
if ((window.innerHeight + window.scrollY) >= (document.body.offsetHeight - 100)) {
let nextPage = that.state.currPage + 1;
console.log("End of page, load next page: " + nextPage);
that.setState({currPage: nextPage, loading: true});
that.loadPodcasts(this.state.filter, this.state.notInGenres, this.state.byRating, nextPage);
}
}
};
}
loadAllGenres() {
getGenres().then(genres => {
this.setState({genres: genres, loadData: this.state.loadData + 1});
})
}
loadPodcasts(filter, notInGenres, byRating, page) {
let data = {
"filter": filter,
"genres": notInGenres.length === 0 ? [-1]: notInGenres.map(i => i - 1),
"byRating": byRating
};
$.ajax({
url: `/getPodcastsInPage?page=${page}&size=${this.state.pageSize}`,
type: 'post',
contentType: 'application/json',
data: JSON.stringify(data),
success: (response) => {
console.log(response);
this.setState(
page !== 0 ? {
podcasts: this.state.podcasts.concat(response.content),
loading: false,
allPages: response.last,
filter: filter,
notInGenres: notInGenres,
byRating: byRating
}:
{
podcasts: response.content,
loading: false,
allPages: response.last,
filter: filter,
notInGenres: notInGenres,
byRating: byRating,
currPage: 0
}
);
},
error: (jqXhr, textStatus, errorThrown) => {
console.log(errorThrown + " " + textStatus);
}
});
}
submitFilter = (filter, notInGenres, byRating) => {
this.loadPodcasts(filter, notInGenres, byRating, 0);
};
render() {
return (
<div>
<Navbar isSearchOff={true} history={this.props.history}/>
<div className="container-fluid">
<div style={{display: "flex",}}>
<div style={{width: "25%"}}>
<SearchPanel genres={this.state.genres}
onSubmit={(filter, notInGenres, byRating) => this.submitFilter(filter, notInGenres, byRating)}
filterValue={this.props.location.state ? this.props.location.state.filterValue: ""}
/>
</div>
<div style={{width: "50%"}}>
{this.state.loadData === 1 &&
<PodcastList history={this.props.history} podcasts={this.state.podcasts}
genres={this.state.genres}/>}
</div>
<div style={{width: "25%"}}>
</div>
</div>
</div>
</div>
)
}
}
Файл: Discasst\src\main\resources\static\app\components\searchPage\SearchPanel.js
import React, {Component} from 'react';
import {
Button,
ButtonGroup,
FormControl,
FormGroup,
Glyphicon,
InputGroup,
ListGroup,
ListGroupItem
} from 'react-bootstrap';
export default class SearchPanel extends Component {
constructor() {
super();
this.state = {
filterValue: "",
unselectedGenres: [],
byRating: false
}
}
componentDidMount() {
this.setState({filterValue: this.props.filterValue});
}
clearFilter = (e) => {
this.setState({filterValue: ""});
e.target.blur();
this.props.onSubmit("", this.state.unselectedGenres, this.state.byRating);
};
applyFilter = (e) => {
e.preventDefault();
this.props.onSubmit(this.state.filterValue, this.state.unselectedGenres, this.state.byRating);
};
handleChange = (e) => {
this.setState({filterValue: e.target.value});
};
unselectGenre = (id) => {
let tmp = this.state.unselectedGenres;
let index = tmp.indexOf(id);
index === -1 ? tmp = [...tmp, id]: tmp.splice(index, 1);
this.setState({unselectedGenres: tmp});
};
searchHeight = 40;
changeByRatingVal = (val) => {
this.setState({byRating: val});
this.props.onSubmit(this.state.filterValue, this.state.unselectedGenres, val);
};
render() {
return (
<div className="container-fluid">
<div className="panel panel-info">
<div className="panel-body">
<form
onSubmit={(event) => {
this.applyFilter(event)
}}
>
<FormGroup>
<InputGroup>
<FormControl
type="text"
placeholder="Search"
value={this.state.filterValue}
id="searchPanelFilter"
onChange={this.handleChange}
style={{height: this.searchHeight}}
/>
<InputGroup.Button>
<Button onClick={this.clearFilter}
style={{height: this.searchHeight, minWidth: this.searchHeight}}>
<Glyphicon glyph="remove" style={{fontSize: "1.2em"}}/>
</Button>
</InputGroup.Button>
</InputGroup>
</FormGroup>
</form>
<ButtonGroup style={{width: '100%', marginBottom: 15}}>
<Button
style={{width: '50%'}}
className="btn btn-sm"
bsStyle={!this.state.byRating ? "info": "default"}
onClick={() => this.changeByRatingVal(false)}>
По новизне
</Button>
<Button
style={{width: '50%'}}
className="btn btn-sm"
bsStyle={this.state.byRating ? "info": "default"}
onClick={() => this.changeByRatingVal(true)}>
По рейтингу
</Button>
</ButtonGroup>
<ListGroup className='search-list' style={{marginBottom: 0}}>
{this.props.genres && this.props.genres.map((item, pos) =>
<ListGroupItem
style={{height: 50, backgroundColor: pos % 2 === 0 ? "#fcfcfc": "#ffffff"}}
key={item.id} onClick={(e) => {
this.unselectGenre(item.id);
e.target.blur();
}}
>
<div style={{display: "flex"}}>
<div style={{minWidth: "50px"}}>
<Glyphicon style={{
color: this.state.unselectedGenres.includes(item.id) ? "#d5d5d5": "#5bc0de",
fontSize: "1.8em"
}} glyph="ok"/>
</div>
<h5>{item.name}</h5>
</div>
</ListGroupItem>
)}
</ListGroup>
</div>
</div>
</div>
);
}
}
Файл: Discasst\src\main\resources\static\app\components\statistics\StatisticsPage.js
import React, {Component} from 'react';
import StatisticsPanel from './StatisticsPanel';
import Navbar from "../Navbar";
export default class StatisticsPage extends Component {
constructor() {
super();
this.state = {
usersPerDay: '...',
usersPerMonth: '...',
podcastPerDay: '...',
podcastPerMonth: '...',
episodePerDay: '...',
episodePerMonth: '...',
}
}
componentDidMount() {
this.loadData();
}
loadData() {
fetch(`/getEpisodesCountToday`)
.then((response) => {
return response.json();
})
.then((json) => {
this.setState({episodePerDay: json});
});
fetch(`/getEpisodesCountThisMounth`)
.then((response) => {
return response.json();
})
.then((json) => {
this.setState({episodePerMonth: json});
});
fetch(`/getPodcastsCountToday`)
.then((response) => {
return response.json();
})
.then((json) => {
this.setState({podcastPerDay: json});
});
fetch(`/getPodcastsCountThisMounth`)
.then((response) => {
return response.json();
})
.then((json) => {
this.setState({podcastPerMonth: json});
});
fetch(`/getUsersCountToday`)
.then((response) => {
return response.json();
})
.then((json) => {
this.setState({usersPerDay: json});
});
fetch(`/getUsersCountThisMounth`)
.then((response) => {
return response.json();
})
.then((json) => {
this.setState({usersPerMonth: json});
});
}
render() {
const ts = this.state;
return (
<div>
<Navbar history={this.props.history}/>
<div>
<div style={{display: "flex", justifyContent: "center", flexWrap: "wrap"}}>
<StatisticsPanel time={statTime.MONTH} stat={stats.USER} number={ts.usersPerMonth}/>
<StatisticsPanel time={statTime.MONTH} stat={stats.EPISODE} number={ts.episodePerMonth}/>
<StatisticsPanel time={statTime.MONTH} stat={stats.PODCAST} number={ts.podcastPerMonth}/>
</div>
<div style={{display: "flex", justifyContent: "center", flexWrap: "wrap"}}>
<StatisticsPanel time={statTime.DAY} stat={stats.USER} number={ts.usersPerDay}/>
<StatisticsPanel time={statTime.DAY} stat={stats.EPISODE} number={ts.episodePerDay}/>
<StatisticsPanel time={statTime.DAY} stat={stats.PODCAST} number={ts.podcastPerDay}/>
</div>
</div>
</div>
);
}
}
const statTime = {
MONTH: 0,
DAY: 1
};
const stats = {
USER: 0,
PODCAST: 1,
EPISODE: 2,
};
export {stats, statTime};
Файл: Discasst\src\main\resources\static\app\components\statistics\StatisticsPanel.js
import React, {Component} from 'react';
import {Panel} from 'react-bootstrap';
import {stats, statTime} from './StatisticsPage';
export default class StatisticsPanel extends Component {
constructor() {
super();
this.state = {}
}
getStat() {
if (this.props.stat === stats.USER) return "Новых пользователей:";
if (this.props.stat === stats.PODCAST) return "Подкастов создано:";
if (this.props.stat === stats.EPISODE) return "Выпусков загружено:";
}
getTime() {
if (this.props.time === statTime.MONTH) return "ЗА МЕСЯЦ";
if (this.props.time === statTime.DAY) return "СЕГОДНЯ";
}
getLabelClass() {
if (this.props.time === statTime.MONTH) return "label label-info";
if (this.props.time === statTime.DAY) return "label label-success";
}
render() {
return (
<Panel style={{width: 320, margin: 10}}>
<Panel.Body>
<div className="text-center" style={{display: "flex", justifyContent: "center", flexWrap: "wrap"}}>
<h3>
<div className={this.getLabelClass()}>{this.getTime()}</div>
</h3>
<h3>{this.getStat()}</h3>
<Panel style={{width: "50%", backgroundColor: "#f1f1f1"}}>
<Panel.Body>
<h1 style={{margin: 0}}><strong><u>{this.props.number}</u></strong></h1>
</Panel.Body>
</Panel>
</div>
</Panel.Body>
</Panel>
);
}
}
Файл: Discasst\src\main\resources\static\app\components\userPage\AddCoauthorsPage.js
import React, {Component} from 'react';
import Navbar from "../Navbar";
import CoauthorsList from "./CoauthorsList";
import {getCookie, getFullCSRFParam} from "../Helper";
export default class AddCoauthorsPage extends Component {
constructor() {
super();
this.state = {
coauthors: []
}
}
componentDidMount() {
this.loadCoauthors();
}
loadCoauthors() {
fetch(`/getCoauthors?podcastId=${this.props.match.params.podcastId}`)
.then((response) => {
return response.json()
})
.then((json) => {
console.log(json)
let coauthorsIds = json.map(x => x.userId.toString());
this.setState({coauthors: coauthorsIds});
});
}
isId(x) {
return (x >= 0 && x % 1 === 0 && x.indexOf('.') === -1 && x.length <= 10 && x !== "")
}
addCoauthor = () => {
let tmp = this.state.coauthors;
let cid = this.refs.coauthorInput.value;
if (cid !== getCookie('userId'))
if (tmp.length <= 3 && this.isId(cid)) {
let newCoauthor = cid;
console.log(tmp.indexOf(newCoauthor));
if (tmp.indexOf(newCoauthor) < 0) {
this.refs.coauthorInput.value = "";
tmp = [...tmp, newCoauthor];
this.setState({coauthors: tmp});
}
}
};
deleteCoauthor = (item) => {
let tmp = this.state.coauthors;
let index = tmp.indexOf(item);
tmp.splice(index, 1);
this.setState({coauthors: tmp});
};
saveChanges = () => {
let authorsIds = JSON.stringify(this.state.coauthors);
const that = this;
$.ajax({
url: `/addCoauthors?podcastId=${this.props.match.params.podcastId}&${getFullCSRFParam()}`,
type: 'post',
contentType: 'application/json',
data: authorsIds,
success: () => {
that.changesIsApplyed();
},
error: (jqXhr, textStatus, errorThrown) => {
console.log(errorThrown + " " + textStatus);
}
});
};
changesIsApplyed = () => {
this.props.history.push(`/user/${getCookie("username")}`);
};
render() {
return (
<div>
<Navbar history={this.props.history}/>
<div style={{display: "flex", justifyContent: "center"}}>
<div className="container-fluid" style={{width: "50%"}}>
<br/><br/><br/>
<div className="panel panel-default">
<div className="panel-body" style={{maxWidth: '100%'}}>
{this.state.coauthors.length <= 3 ?
<div style={{display: "flex", justifyContent: "center"}}>
<input ref="coauthorInput" maxLength="10" className="form-control"
placeholder="Id соведущего" type="number" min="0" step="1"
max="999999999"/>  
<button type="button" className="btn btn-info"
onClick={this.addCoauthor}>Добавить ведущего
</button>
</div>: <h4>Соведущие:</h4>}
{this.state.coauthors.length > 0 && <br/>}
<CoauthorsList coauthors={this.state.coauthors} onClick={this.deleteCoauthor}/>
<br/>
<button
type="button"
className="btn btn-info"
onClick={this.saveChanges}>Сохранить
</button>
<br/>
</div>
</div>
</div>
</div>
</div>
);
}
}
Файл: Discasst\src\main\resources\static\app\components\userPage\CoauthorsList.js
import React, {Component} from 'react';
export default class CoauthorsList extends Component {
constructor() {
super();
this.state = {}
}
render() {
return (<div className="container-fluid">
<div style={{display: "flex", justifyContent: "flex-start", flexWrap: "wrap"}}>
{this.props.coauthors.map((item) =>
<button key={item} onClick={() => this.props.onClick(item)} style={{margin: "2px"}}
className="btn btn-info">id: {item} <span className="glyphicon glyphicon-remove"></span>
</button>
)}
</div>
</div>
);
}
}
Файл: Discasst\src\main\resources\static\app\components\userPage\ CreateNewEpisodePage.js
import React, {Component} from 'react';
import Navbar from "../Navbar";
import {getCookie, getFullCSRFParam} from '../Helper'
export default class CreateNewEpisodePage extends Component {
constructor() {
super();
this.state = {}
}
createNewEpisode = () => {
if (this.validationIsOk()) {
console.log("Create new episode")
let obj = {
"name": this.refs.episodeName.value,
"podcastId": this.props.match.params.podcastId,
"description": this.refs.episodeDesc.value,
"podsterLink": this.refs.podsterLink.value,
};
let episode = JSON.stringify(obj);
const that = this;
$.ajax({
url: `/createEpisode?${getFullCSRFParam()}`,
type: 'post',
contentType: 'application/json',
data: episode,
success: () => {
that.episodeIsCreated();
},
error: (jqXhr, textStatus, errorThrown) => {
console.log(errorThrown + " " + textStatus);
}
});
}
};
episodeIsCreated() {
console.log("Episode is created");
this.props.history.push(`/user/${getCookie("username")}`);
}
validationIsOk() {
let en = this.refs.episodeName.value;
let ed = this.refs.episodeDesc.value;
let pl = this.refs.podsterLink.value;
if (en !== "" && ed !== "" && pl !== "") return true;
else return false;
}
render() {
return (
<div>
<Navbar history={this.props.history}/>
<div style={{display: "flex", justifyContent: "center"}}>
<div className="container-fluid" style={{width: "50%"}}>
<br/><br/><br/>
<div className="panel panel-default">
<div className="panel-body" style={{maxWidth: '100%'}}>
<input ref="episodeName" type="text" maxLength="50" className="form-control"
placeholder="* Название"/><br/>
<textarea ref="episodeDesc" className="form-control" maxLength="350"
style={{resize: "vertical"}} rows="5"
placeholder="* Описание"></textarea><br/>
<input ref="podsterLink" type="text" maxLength="50" className="form-control"
placeholder="* Ссылка на выпуск в Podster.fm (формата https://podcast-name.podster.fm/42)"/><br/>
<br/>
<div>
<button type="button" className="btn btn-info pull-right"
onClick={this.createNewEpisode}><span
className="glyphicon glyphicon-plus"></span> Добавить новый выпуск
</button>
</div>
<br/>
</div>
</div>
</div>
</div>
</div>
);
}
}
Файл: Discasst\src\main\resources\static\app\components\userPage\ CreateNewPodcastPage.js
import React, {Component} from 'react';
import Navbar from "../Navbar";
import {getCookie, getFullCSRFParam} from "../Helper";
export default class CreateNewPodcastPage extends Component {
constructor() {
super();
this.state = {
genres: null,
selectedGenre: {
id: null,
name: "* Выберите"
},
};
}
componentDidMount() {
this.getGenres();
}
getGenres = () => {
let genres;
$.getJSON(`/getGenres`, (data) => {
console.log(data);
this.setState({genres: data});
return data;
});
};
selectGenre = (genre) => {
this.setState({selectedGenre: genre});
};
validationIsOk() {
let pn = this.refs.podcastName.value;
let pd = this.refs.podcastDesc.value;
let g = this.state.selectedGenre.name;
if (pn !== "" && pd !== "" && g !== "* Выберите") return true;
return false;
}
createNewPodcast = () => {
if (this.validationIsOk()) {
console.log("Create new podcast");
let obj = {
"name": this.refs.podcastName.value,
"authorId": getCookie("userId"),
"description": this.refs.podcastDesc.value,
"genreId": (this.state.selectedGenre.id - 1),
"coauthors": this.state.coauthors,
"vkLink": this.refs.vkLink.value,
"twitterLink": this.refs.twitterLink.value,
"itunesLink": this.refs.itunesLink.value,
"facebookLink": this.refs.facebookLink.value,
"rating": 0
};
let podcast = JSON.stringify(obj);
const that = this;
$.ajax({
url: `/createPodcast?${getFullCSRFParam()}`,
type: 'post',
contentType: 'application/json',
data: podcast,
success: () => {
that.podcastIsCreated();
},
error: (jqXhr, textStatus, errorThrown) => {
console.log(errorThrown + " " + textStatus);
}
});
}
};
podcastIsCreated() {
console.log("Подкаст создан!")
this.props.history.push(`/user/${getCookie("username")}`);
}
render() {
return (
<div>
<Navbar history={this.props.history}/>
<div style={{display: "flex", justifyContent: "center"}}>
<div className="container-fluid" style={{width: "50%"}}>
<br/><br/><br/>
<div className="panel panel-default">
<div className="panel-body" style={{maxWidth: '100%'}}>
<input ref="podcastName" type="text" maxLength="50" className="form-control"
placeholder="* Название"/><br/>
<textarea ref="podcastDesc" className="form-control" maxLength="350"
style={{resize: "vertical"}} rows="5"
placeholder="* Описание"></textarea><br/>
<div className="btn-group">
<button className="btn btn-info dropdown-toggle" data-toggle="dropdown"
style={{minWidth: "160px"}}>{"Жанр: " + this.state.selectedGenre.name}  
<span className="glyphicon glyphicon-chevron-down"></span></button>
{this.state.genres !== null && <ul className="dropdown-menu">
{this.state.genres.map((item) =>
<li key={item.id}><a role="button"
onClick={() => this.selectGenre(item)}>{item.name}</a>
</li>
)}
</ul>}
</div>
<br/><br/>
<input ref="twitterLink" type="text" maxLength="50" className="form-control"
placeholder="Ссылка на Twitter"/><br/>
<input ref="vkLink" type="text" maxLength="50" className="form-control"
placeholder="Ссылка на Vk"/><br/>
<input ref="facebookLink" type="text" maxLength="50" className="form-control"
placeholder="Ссылка на Facebook"/><br/>
<input ref="itunesLink" type="text" maxLength="50" className="form-control"
placeholder="Ссылка на iTunes"/><br/>
<div>
<button type="button" className="btn btn-info pull-right"
onClick={this.createNewPodcast}><span
className="glyphicon glyphicon-plus"></span> Создать новый подкаст
</but...
Подобные документы
Обзор существующих технологий разработки программного обеспечения. Описание платформы NET Framework. Принцип работы платформы: компиляция исходного кода; процесс загрузки и исполнения кода; IL-код и верификация. Новые возможности платформы NET Framework.
реферат [30,7 K], добавлен 01.03.2011Последовательность разработки информационного обеспечения очного и дистанционного обучения через просмотры и прослушивание подкастов. Создание веб-сайта или модуля существующей системы. Описание интерфейсов системы. Настройка прав доступа к подкастам.
дипломная работа [2,3 M], добавлен 19.11.2010Знакомство с особенностями и этапами разработки приложения для платформы Android. Рассмотрение функций персонажа: бег, прыжок, взаимодействие с объектами. Анализ блок-схемы алгоритма генерации платформ. Способы настройки функционала рабочей области.
дипломная работа [3,4 M], добавлен 19.01.2017Основы работы с многооконным графическим пользовательским интерфейсом операционной системы Windows95/NT. Основы работы с прикладными программами Windows и DOS. Разработка простого приложения для Windows при помощи средства разработки приложений DELPHI.
контрольная работа [281,0 K], добавлен 15.01.2009Облачные технологии - использование приложения, расположенного на удаленных серверах, посредством удобного пользовательского интерфейса. История и перспективы их развития. Платформы обслуживания "облаков". Положительные и отрицательные стороны сервисов.
презентация [770,2 K], добавлен 14.03.2017Отличительные черты смартфонов и коммуникаторов от обычных мобильных телефонов, их дополнительные возможности. Назначение и конфигурация платформы J2ME, ее функции. Порядок проектирования приложения для мобильного телефона на основе платформы J2ME.
дипломная работа [3,6 M], добавлен 05.09.2009Анализ хозяйственной деятельности организации и ее состояния. Особенности работы мобильной платформы. Реквизитный состав документов. Программная реализация и оценка эффективности приложения. Безопасность работы с приложением и безопасность данных.
дипломная работа [1,0 M], добавлен 13.06.2014Рассмотрение поисковых систем интернета как программно-аппаратного комплекса с веб-интерфейсом, предоставляющего возможность поиска информации. Виды поисковых систем: Archie, Wandex, Aliweb, WebCrawler, AltaVista, Yahoo!, Google, Яндекс, Bing и Rambler.
реферат [24,3 K], добавлен 10.05.2013База знаний интеллектуальной справочной системы по алгебре дробей со стандартными набором информационно-поисковых операций, пользовательским интерфейсом. Тестирование на стандартных вопросах и шаблонах поиска. Интеграция со смежными предметными областями.
курсовая работа [12,3 M], добавлен 06.05.2011Описание языков программирования Java и JavaFX. Среда разработки NetBeans и класс численных методов. Архитектура и принцип работы апплета с понятным пользовательским интерфейсом. Разработка алгоритма программы на примере модели межвидовой конкуренции.
курсовая работа [1023,2 K], добавлен 19.09.2012Понятие и функциональные особенности Java Card как версии Java-платформы для устройств с крайне ограниченными вычислительными ресурсами, оценка ее возможностей и необходимых ресурсов. Анализ степени безопасности платформы, взаимодействие компонентов.
презентация [1,0 M], добавлен 19.05.2014Описание платформы Deductor, ее назначение. Организационная структура аналитической платформы Deductor, состав модулей. Принципы работы программы, импорт и экспорт данных. Визуализация информации, сценарная последовательность и мастер обработки.
курсовая работа [3,7 M], добавлен 19.04.2014Рассмотрение и анализ моделей и алгоритмов семантического поиска в мультиагентной системе поддержки пользователей. Ознакомление с интерфейсом чата с ботом. Изучение и характеристика экспериментальных оценок релевантности и пертинентности запросов.
дипломная работа [3,0 M], добавлен 13.10.2017Изучение информационной базы клиента "Управление торговлей". Выбор и изучение платформы для построения сайта. Выбор технологии и среды разработки. Разработка основных алгоритмов решения задач и хранения данных. Проектирование интерфейса пользователя.
дипломная работа [1,1 M], добавлен 20.05.2017Разработка базы данных средней сложности с типовым пользовательским интерфейсом, а в частности, разработка базы данных СНАБЖЕНИЕ МАГАЗИНОВ на основе реляционной системы управления базами данных Microsoft Access, входящей в комплект Microsoft Office.
курсовая работа [2,1 M], добавлен 02.12.2012Разработка компьютерного приложения "Кипящая жидкость" с применением навыков программирования на языке Java. Проектирование алгоритма для решения поставленной задачи, его предметная область. Создание приложения с графическим пользовательским интерфейсом.
отчет по практике [3,0 M], добавлен 29.10.2015Возможности среды программирования delphi при разработке приложения с визуальным интерфейсом. Разработка спецификации программного обеспечения и на ее основе кода программного продукта. Отладка программы "трассировкой", ее тестирование и оптимизация.
курсовая работа [501,4 K], добавлен 07.12.2016Возможность поиска информации в режиме продвинутого диалога на естественном языке. Системы с интеллектуальным интерфейсом. Экспертные, самообучающиеся и адаптивные системы. Интеллектуальные базы данных. Системы контекстной и когнитивной помощи.
презентация [224,2 K], добавлен 16.10.2013Средства поиска информации в сети Интернет. Основные требования и методика поиска информации. Структура и характеристика поисковых сервисов. Глобальные поисковые машины WWW (World Wide Web). Планирование поиска и сбора информации в сети Интернет.
реферат [32,2 K], добавлен 02.11.2010Создание информационной системы работы такси с целью обеспечения диспетчерам более быстрого и удобного поиска необходимой информации. Создание таблиц и связей для работы с базами данных в среде Microsoft Access 2007. разработка запросов и отчетов.
курсовая работа [3,1 M], добавлен 06.05.2013