import React from "react"; import './Novel.sass' import NotFoundError from "../NotFoundError"; import {connect} from "react-redux"; import { fetchNovel, fetchNovelCharacters, fetchNovelComments, fetchUserNovel, updateUserNovel, deleteUserNovel, fetchNovelGenres, postNovelComment, createUserNovel } from "../../api/novels"; import {Link} from "react-router-dom"; import Loading from "../../ui/Loading"; import {TranslateExitStatus, TranslateStatus} from "../../api/api"; import Comments from "../Comments/Comments"; import LoadingModal from "../Modals/LoadingModal"; import {hideModal, setModal} from "../../store/ducks/modal"; type Props = { currentUser: UserType setModal: SetModal hideModal: HideModal match: { params: { novelId: number } } }; type State = { novel: NovelType | null characters: CharacterType[] genres: GenreType[] errorCode: number | null userData: UserNovelType | null statusExpanded: boolean comments: CommentType[] commentsOffset: number hasMoreComments: boolean } class Novel extends React.Component { state: State = { novel: null, characters: [], genres: [], errorCode: null, userData: null, statusExpanded: false, comments: [], commentsOffset: 5, hasMoreComments: true }; updateNovelStatus = (status: string) => { const { match: { params } , currentUser, setModal, hideModal } = this.props; const { userData } = this.state; setModal(); if(userData) { updateUserNovel(currentUser.id, params.novelId, { status }).then(r => { this.setState({ userData: r, statusExpanded: false }); hideModal(); }).catch(console.error); } else { createUserNovel(currentUser.id, params.novelId, status).then(r => { this.setState({ userData: r, statusExpanded: false }); hideModal(); }).catch(console.error); } }; deleteNovelStatus = () => { const { match: { params } , currentUser, setModal, hideModal } = this.props; setModal(); deleteUserNovel(currentUser.id, params.novelId).then(() => { this.setState({ userData: null, statusExpanded: false }); hideModal(); }); }; sendComment = (text: string) => { const { match: { params: { novelId } } } = this.props; postNovelComment(novelId, text).then((comment: CommentType) => { const { comments } = this.state; this.setState({ comments: [comment, ...comments] }); }).catch(console.error); }; loadMoreComments = () => { const { match: { params } } = this.props; let { comments, commentsOffset } = this.state; fetchNovelComments(params.novelId, commentsOffset, 10).then(r => { const hasMoreComments = r.count <= commentsOffset; commentsOffset += 5; this.setState({ comments: [...r.comments.reverse(), ...comments], hasMoreComments, commentsOffset }) }); }; componentDidUpdate(prevProps: any) { const { match: { params }, currentUser } = this.props; const { currentUser: prevUser } = prevProps; if(!prevUser.authorized && currentUser.authorized) fetchUserNovel(currentUser.id, params.novelId) .then(data => this.setState({ userData: data })) .catch(err => console.error(err)); else if(prevUser.authorized && !currentUser.authorized) this.setState({ userData: null }) } componentDidMount() { const { match: { params }, currentUser } = this.props; fetchNovel(params.novelId).then((novel: NovelType) => { novel.release_date = new Date(novel.release_date); this.setState({ novel }); document.title = novel.localized_name ? `${novel.original_name} / ${novel.localized_name}` : novel.original_name; fetchNovelComments(params.novelId).then(r => { const hasMoreComments = r.count > 5; this.setState({ comments: r.comments.reverse(), hasMoreComments }) }); fetchNovelCharacters(params.novelId).then(characters => this.setState({ characters })); fetchNovelGenres(params.novelId).then(genres => this.setState({ genres })); }).catch((err: ApiError) => { this.setState({ errorCode: err.code }); }); if(currentUser.authorized) fetchUserNovel(currentUser.id, params.novelId).then((userData: UserNovelType) => { this.setState({ userData }); }).catch((err: ApiError) => { if(err.code !== 100) console.error(err.text); }); } updateNovelMark = (mark: number) => { const { match: { params }, currentUser, hideModal, setModal } = this.props; const { userData } = this.state; if(userData?.mark === mark) return; setModal(); updateUserNovel(currentUser.id, params.novelId, { mark }).then(res => { userData!!.mark = mark; this.setState({ userData: res }); hideModal(); }); } countNovelDuration = (t: number) => { const h = Math.round(t / 60); const m = t - (t*60); if(h === 0) return `${m} мин`; else return `${h} ч`; } render() { const { errorCode, novel, comments, characters, genres, userData, statusExpanded, hasMoreComments } = this.state; const { currentUser } = this.props; if(errorCode === 100) return ; if(!novel) return ; return (
{novel.image ? {novel.original_name}/ : } {currentUser.authorized &&
{!userData ?
this.updateNovelStatus('planned')}>Добавить в список
:
{TranslateStatus[userData.status]}
} {userData && (
this.setState({ statusExpanded: !statusExpanded })} style={{ paddingLeft: 5, borderLeft: '1px solid white' }} > +
)}
{statusExpanded &&
{userData && Object.entries(TranslateStatus).filter(([eng]) => eng !== userData.status).map(([eng, rus]) => (
this.updateNovelStatus(eng)}>{rus}
))}
Удалить из списка
}
{userData && [0,1,2,3,4,5,6,7,8,9,10].map(mark => (
this.updateNovelMark(mark)} > {mark}
))}
}
{novel.localized_name || novel.original_name}
{novel.localized_name &&
Оригинальное название: {novel.original_name}
} {genres && genres.length > 0 &&
Жанры: {genres.map(genre =>
{genre.localized_name}
)}
}
Год выхода: {novel.release_date.toLocaleDateString()}
Статус: {TranslateExitStatus[novel.exit_status]}
Продолжительность: {this.countNovelDuration(novel.duration)}
Ср. оценка: {novel.rating.toFixed(2)}
{novel.description}
{characters && characters.length > 0 &&
Главные герои:
{this.state.characters.filter(char => char.main).map(char =>
{char.localized_name}
)}
}
); } } export default connect( (state: StoreState) => ({ currentUser: state.currentUser }), dispatch => ({ setModal: (modal: React.ReactNode | null) => dispatch(setModal(modal)), hideModal: () => dispatch(hideModal()) }) )(Novel);