362 lines
16 KiB
TypeScript
362 lines
16 KiB
TypeScript
import React, {Component} from 'react';
|
|
import {
|
|
withStyles,
|
|
Typography,
|
|
Avatar,
|
|
Divider,
|
|
Button,
|
|
CircularProgress,
|
|
Paper,
|
|
Tooltip,
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogContentText,
|
|
DialogActions,
|
|
Toolbar,
|
|
IconButton
|
|
} from '@material-ui/core';
|
|
import {styles} from './PageLayout.styles';
|
|
import Mastodon from 'megalodon';
|
|
import { Account } from '../types/Account';
|
|
import { Status } from '../types/Status';
|
|
import { Relationship } from '../types/Relationship';
|
|
import Post from '../components/Post';
|
|
import {withSnackbar} from 'notistack';
|
|
import { LinkableIconButton } from '../interfaces/overrides';
|
|
import { emojifyString } from '../utilities/emojis';
|
|
|
|
import AccountEditIcon from 'mdi-material-ui/AccountEdit';
|
|
import PersonAddIcon from '@material-ui/icons/PersonAdd';
|
|
import PersonAddDisabledIcon from '@material-ui/icons/PersonAddDisabled';
|
|
import AccountMinusIcon from 'mdi-material-ui/AccountMinus';
|
|
import ChatIcon from '@material-ui/icons/Chat';
|
|
import AccountRemoveIcon from 'mdi-material-ui/AccountRemove';
|
|
import AccountHeartIcon from 'mdi-material-ui/AccountHeart';
|
|
import OpenInNewIcon from '@material-ui/icons/OpenInNew';
|
|
|
|
|
|
|
|
interface IProfilePageState {
|
|
account?: Account;
|
|
relationship?: Relationship;
|
|
posts?: [Status];
|
|
viewIsLoading: boolean;
|
|
viewDidLoad?: boolean;
|
|
viewDidError?: boolean;
|
|
viewDidErrorCode?: string;
|
|
blockDialogOpen: boolean;
|
|
}
|
|
|
|
class ProfilePage extends Component<any, IProfilePageState> {
|
|
|
|
client: Mastodon;
|
|
|
|
constructor(props: any) {
|
|
super(props);
|
|
|
|
|
|
this.client = new Mastodon(localStorage.getItem('access_token') as string, localStorage.getItem('baseurl') + "/api/v1");
|
|
|
|
this.state = {
|
|
viewIsLoading: true,
|
|
blockDialogOpen: false
|
|
}
|
|
}
|
|
|
|
toggleBlockDialog() {
|
|
if (this.state.relationship && !this.state.relationship.blocking)
|
|
this.setState({ blockDialogOpen: !this.state.blockDialogOpen })
|
|
else
|
|
this.toggleBlock()
|
|
}
|
|
|
|
getAccountData(id: string) {
|
|
this.client.get(`/accounts/${id}`).then((resp: any) => {
|
|
let profile: Account = resp.data;
|
|
|
|
const div = document.createElement('div');
|
|
div.innerHTML = profile.note;
|
|
profile.note = div.textContent || div.innerText || "";
|
|
|
|
this.setState({
|
|
account: profile
|
|
})
|
|
}).catch((error: Error) => {
|
|
this.setState({
|
|
viewIsLoading: false,
|
|
viewDidError: true,
|
|
viewDidErrorCode: error.message
|
|
})
|
|
});
|
|
this.getRelationships();
|
|
this.client.get(`/accounts/${id}/statuses`).then((resp: any) => {
|
|
this.setState({
|
|
posts: resp.data,
|
|
viewIsLoading: false,
|
|
viewDidLoad: true,
|
|
viewDidError: false
|
|
})
|
|
}).catch( (err: Error) => {
|
|
this.setState({
|
|
viewIsLoading: false,
|
|
viewDidError: true,
|
|
viewDidErrorCode: err.message
|
|
})
|
|
});
|
|
}
|
|
|
|
componentWillReceiveProps(props: any) {
|
|
this.getAccountData(props.match.params.profileId);
|
|
window.scrollTo(0, 0);
|
|
}
|
|
|
|
componentWillMount() {
|
|
const { match: { params }} = this.props;
|
|
this.getAccountData(params.profileId);
|
|
}
|
|
|
|
isItMe(): boolean {
|
|
if (this.state.account) {
|
|
return this.state.account.id === JSON.parse(localStorage.getItem('account') as string).id;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
getRelationships() {
|
|
this.client.get("/accounts/relationships", {id: this.props.match.params.profileId }).then((resp: any) => {
|
|
let relationship: Relationship = resp.data[0];
|
|
this.setState({ relationship });
|
|
}).catch((error: Error) => {
|
|
this.setState({
|
|
viewIsLoading: false,
|
|
viewDidError: true,
|
|
viewDidErrorCode: error.message
|
|
})
|
|
});
|
|
}
|
|
|
|
loadMoreTimelinePieces() {
|
|
const { match: {params}} = this.props;
|
|
this.setState({ viewDidLoad: false, viewIsLoading: true})
|
|
if (this.state.posts && this.state.posts.length > 0) {
|
|
this.client.get(`/accounts/${params.profileId}/statuses`, { max_id: this.state.posts[this.state.posts.length - 1].id, limit: 20 }).then((resp: any) => {
|
|
let newPosts: [Status] = resp.data;
|
|
let posts = this.state.posts as [Status];
|
|
if (newPosts.length <= 0) {
|
|
this.props.enqueueSnackbar("Reached end of posts", {
|
|
variant: 'error'
|
|
});
|
|
} else {
|
|
newPosts.forEach((post: Status) => {
|
|
posts.push(post);
|
|
});
|
|
}
|
|
this.setState({
|
|
viewIsLoading: false,
|
|
viewDidLoad: true,
|
|
posts
|
|
})
|
|
}).catch((err: Error) => {
|
|
this.setState({
|
|
viewIsLoading: false,
|
|
viewDidError: true,
|
|
viewDidErrorCode: err.message
|
|
})
|
|
this.props.enqueueSnackbar("Failed to get posts", {
|
|
variant: 'error',
|
|
});
|
|
})
|
|
} else {
|
|
this.props.enqueueSnackbar("Reached end of posts", { variant: 'error'} );
|
|
this.setState({
|
|
viewIsLoading: false,
|
|
viewDidLoad: true
|
|
})
|
|
}
|
|
}
|
|
|
|
toggleFollow() {
|
|
if (this.state.relationship) {
|
|
if (this.state.relationship.following) {
|
|
this.client.post(`/accounts/${this.state.account? this.state.account.id: this.props.match.params.profileId}/unfollow`).then((resp: any) => {
|
|
let relationship: Relationship = resp.data;
|
|
this.setState({ relationship });
|
|
this.props.enqueueSnackbar('You are no longer following this account.');
|
|
}).catch((err: Error) => {
|
|
this.props.enqueueSnackbar("Couldn't unfollow account: " + err.name, { variant: 'error' });
|
|
console.error(err.message);
|
|
})
|
|
} else {
|
|
this.client.post(`/accounts/${this.state.account? this.state.account.id: this.props.match.params.profileId}/follow`).then((resp: any) => {
|
|
let relationship: Relationship = resp.data;
|
|
this.setState({ relationship });
|
|
this.props.enqueueSnackbar('You are now following this account.');
|
|
}).catch((err: Error) => {
|
|
this.props.enqueueSnackbar("Couldn't follow account: " + err.name, { variant: 'error' });
|
|
console.error(err.message);
|
|
})
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
toggleBlock() {
|
|
if (this.state.relationship) {
|
|
if (this.state.relationship.blocking) {
|
|
this.client.post(`/accounts/${this.state.account? this.state.account.id: this.props.match.params.profileId}/unblock`).then((resp: any) => {
|
|
let relationship: Relationship = resp.data;
|
|
this.setState({ relationship });
|
|
this.props.enqueueSnackbar('You are no longer blocking this account.');
|
|
}).catch((err: Error) => {
|
|
this.props.enqueueSnackbar("Couldn't unblock account: " + err.name, { variant: 'error' });
|
|
console.error(err.message);
|
|
})
|
|
} else {
|
|
this.client.post(`/accounts/${this.state.account? this.state.account.id: this.props.match.params.profileId}/block`).then((resp: any) => {
|
|
let relationship: Relationship = resp.data;
|
|
this.setState({ relationship });
|
|
this.props.enqueueSnackbar('You are now blocking this account.');
|
|
}).catch((err: Error) => {
|
|
this.props.enqueueSnackbar("Couldn't block account: " + err.name, { variant: 'error' });
|
|
console.error(err.message);
|
|
})
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
render() {
|
|
const { classes } = this.props;
|
|
return(
|
|
<div className={classes.pageLayoutMinimalConstraints}>
|
|
<div className={classes.pageHeroBackground}>
|
|
<div className={classes.pageHeroBackgroundImage} style={{ backgroundImage: this.state.account? `url("${this.state.account.header}")`: `url("")`}}/>
|
|
<Toolbar className={classes.profileToolbar}>
|
|
<div className={classes.pageGrow}/>
|
|
<Tooltip title={
|
|
this.isItMe()?
|
|
"You can't follow yourself.":
|
|
this.state.relationship && this.state.relationship.following?
|
|
"Unfollow":
|
|
"Follow"
|
|
}>
|
|
<IconButton color={"inherit"} disabled={this.isItMe()} onClick={() => this.toggleFollow()}>
|
|
{
|
|
this.isItMe()?
|
|
<PersonAddDisabledIcon/>:
|
|
this.state.relationship && this.state.relationship.following?
|
|
<AccountMinusIcon/>:
|
|
<PersonAddIcon/>
|
|
}
|
|
</IconButton>
|
|
</Tooltip>
|
|
<Tooltip title={"Send a message or post"}>
|
|
<LinkableIconButton to={`/compose?acct=${this.state.account? this.state.account.acct: ""}`} color={"inherit"}>
|
|
<ChatIcon/>
|
|
</LinkableIconButton>
|
|
</Tooltip>
|
|
<Tooltip title={this.state.relationship && this.state.relationship.blocking? "Unblock this account": "Block this account"}>
|
|
<IconButton color={"inherit"} disabled={this.isItMe()} onClick={() => this.toggleBlockDialog()}>
|
|
{
|
|
this.state.relationship && this.state.relationship.blocking? <AccountHeartIcon/>: <AccountRemoveIcon/>
|
|
}
|
|
</IconButton>
|
|
</Tooltip>
|
|
<Tooltip title="Open in web">
|
|
<IconButton href={this.state.account? this.state.account.url: ""} target="_blank" rel={"nofollower noreferrer noopener"} color={"inherit"}>
|
|
<OpenInNewIcon/>
|
|
</IconButton>
|
|
</Tooltip>
|
|
{
|
|
this.isItMe()?
|
|
<Tooltip title="Edit profile">
|
|
<LinkableIconButton to="/you" color="inherit">
|
|
<AccountEditIcon/>
|
|
</LinkableIconButton>
|
|
</Tooltip>: null
|
|
}
|
|
</Toolbar>
|
|
<div className={classes.profileContent}>
|
|
<Avatar className={classes.profileAvatar} src={this.state.account ? this.state.account.avatar: ""}/>
|
|
<div className={classes.profileUserBox}>
|
|
<Typography variant="h4" color="inherit" dangerouslySetInnerHTML={
|
|
{__html: this.state.account?
|
|
this.state.account.display_name?
|
|
emojifyString(this.state.account.display_name, this.state.account.emojis, classes.pageProfileNameEmoji)
|
|
: this.state.account.username
|
|
: ""}}
|
|
className={classes.pageProfileNameEmoji}/>
|
|
<Typography variant="caption" color="inherit">{this.state.account ? '@' + this.state.account.acct: ""}</Typography>
|
|
<Typography paragraph color="inherit">{
|
|
this.state.account ?
|
|
this.state.account.note?
|
|
this.state.account.note
|
|
: "No bio provided by user."
|
|
: "No bio available."
|
|
}</Typography>
|
|
<Typography color={"inherit"}>
|
|
{this.state.account? this.state.account.followers_count: 0} followers | {this.state.account? this.state.account.following_count: 0} following | {this.state.account? this.state.account.statuses_count: 0} posts
|
|
</Typography>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className={classes.pageContentLayoutConstraints}>
|
|
{
|
|
this.state.viewDidError?
|
|
<Paper className={classes.errorCard}>
|
|
<Typography variant="h4">Bummer.</Typography>
|
|
<Typography variant="h6">Something went wrong when loading this profile.</Typography>
|
|
<Typography>{this.state.viewDidErrorCode? this.state.viewDidErrorCode: ""}</Typography>
|
|
</Paper>:
|
|
<span/>
|
|
}
|
|
{
|
|
this.state.posts?
|
|
<div>
|
|
{
|
|
this.state.posts.map((post: Status) => {
|
|
return <Post key={post.id} post={post} client={this.client}/>;
|
|
})
|
|
}
|
|
<br/>
|
|
{
|
|
this.state.viewDidLoad && !this.state.viewDidError? <div style={{textAlign: "center"}} onClick={() => this.loadMoreTimelinePieces()}><Button variant="contained">Load more</Button></div>: null
|
|
}
|
|
</div>: <span/>
|
|
}
|
|
{
|
|
this.state.viewIsLoading?
|
|
<div style={{ textAlign: 'center' }}><CircularProgress className={classes.progress} color="primary" /></div>:
|
|
<span/>
|
|
}
|
|
<Dialog
|
|
open={this.state.blockDialogOpen}
|
|
onClose={() => this.toggleBlockDialog()}
|
|
>
|
|
<DialogTitle id="alert-dialog-title">Block this person?</DialogTitle>
|
|
<DialogContent>
|
|
<DialogContentText id="alert-dialog-description">
|
|
Are you sure you want to block this person? You won't see their posts on your home feed, local timeline, or public timeline.
|
|
</DialogContentText>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button onClick={() => this.toggleBlockDialog()} color="primary" autoFocus>
|
|
Cancel
|
|
</Button>
|
|
<Button onClick={() => {
|
|
this.toggleBlock();
|
|
this.toggleBlockDialog();
|
|
}} color="primary">
|
|
Block
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default withStyles(styles)(withSnackbar(ProfilePage)); |