2019-09-18 19:52:39 +02:00
|
|
|
import React, { Component } from "react";
|
|
|
|
import {
|
|
|
|
withStyles,
|
|
|
|
CircularProgress,
|
|
|
|
Typography,
|
|
|
|
Paper,
|
|
|
|
Button,
|
|
|
|
Chip,
|
|
|
|
Avatar,
|
|
|
|
Slide
|
|
|
|
} from "@material-ui/core";
|
|
|
|
import { styles } from "./PageLayout.styles";
|
|
|
|
import Post from "../components/Post";
|
|
|
|
import { Status } from "../types/Status";
|
|
|
|
import Mastodon, { StreamListener } from "megalodon";
|
|
|
|
import { withSnackbar } from "notistack";
|
2019-12-22 00:10:24 +01:00
|
|
|
import Masonry from 'react-masonry-css';
|
|
|
|
import { getUserDefaultBool } from "../utilities/settings";
|
2019-09-18 19:52:39 +02:00
|
|
|
import ArrowUpwardIcon from "@material-ui/icons/ArrowUpward";
|
2019-03-31 03:20:55 +02:00
|
|
|
|
|
|
|
interface ILocalPageState {
|
|
|
|
posts?: [Status];
|
|
|
|
backlogPosts?: [Status] | null;
|
|
|
|
viewIsLoading: boolean;
|
|
|
|
viewDidLoad?: boolean;
|
|
|
|
viewDidError?: boolean;
|
|
|
|
viewDidErrorCode?: any;
|
2019-12-22 00:10:24 +01:00
|
|
|
isMasonryLayout?: boolean;
|
2019-03-31 03:20:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class LocalPage extends Component<any, ILocalPageState> {
|
|
|
|
client: Mastodon;
|
2019-04-06 19:41:43 +02:00
|
|
|
streamListener: StreamListener;
|
2019-03-31 03:20:55 +02:00
|
|
|
|
|
|
|
constructor(props: any) {
|
|
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
|
|
|
viewIsLoading: true,
|
2019-12-22 00:10:24 +01:00
|
|
|
backlogPosts: null,
|
|
|
|
isMasonryLayout: getUserDefaultBool('isMasonryLayout'),
|
2019-09-18 19:52:39 +02:00
|
|
|
};
|
2019-03-31 03:20:55 +02:00
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
this.client = new Mastodon(
|
|
|
|
localStorage.getItem("access_token") as string,
|
|
|
|
(localStorage.getItem("baseurl") as string) + "/api/v1"
|
|
|
|
);
|
|
|
|
this.streamListener = this.client.stream("/streaming/public/local");
|
2019-03-31 03:20:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
componentWillMount() {
|
2019-09-18 19:52:39 +02:00
|
|
|
this.streamListener.on("connect", () => {
|
|
|
|
this.client
|
|
|
|
.get("/timelines/public", { limit: 40, local: true })
|
|
|
|
.then((resp: any) => {
|
|
|
|
let statuses: [Status] = resp.data;
|
|
|
|
this.setState({
|
|
|
|
posts: statuses,
|
|
|
|
viewIsLoading: false,
|
|
|
|
viewDidLoad: true,
|
|
|
|
viewDidError: false
|
|
|
|
});
|
2019-03-31 03:20:55 +02:00
|
|
|
})
|
2019-09-18 19:52:39 +02:00
|
|
|
.catch((resp: any) => {
|
|
|
|
this.setState({
|
|
|
|
viewIsLoading: false,
|
|
|
|
viewDidLoad: true,
|
|
|
|
viewDidError: true,
|
|
|
|
viewDidErrorCode: String(resp)
|
|
|
|
});
|
|
|
|
this.props.enqueueSnackbar("Failed to get posts.", {
|
|
|
|
variant: "error"
|
|
|
|
});
|
2019-03-31 03:20:55 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
this.streamListener.on("update", (status: Status) => {
|
2019-03-31 03:20:55 +02:00
|
|
|
let queue = this.state.backlogPosts;
|
2019-09-18 19:52:39 +02:00
|
|
|
if (queue !== null && queue !== undefined) {
|
|
|
|
queue.unshift(status);
|
|
|
|
} else {
|
|
|
|
queue = [status];
|
|
|
|
}
|
2019-03-31 03:20:55 +02:00
|
|
|
this.setState({ backlogPosts: queue });
|
2019-09-18 19:52:39 +02:00
|
|
|
});
|
2019-03-31 03:20:55 +02:00
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
this.streamListener.on("delete", (id: number) => {
|
2019-03-31 03:20:55 +02:00
|
|
|
let posts = this.state.posts;
|
|
|
|
if (posts) {
|
|
|
|
posts.forEach((post: Status) => {
|
|
|
|
if (posts && parseInt(post.id) === id) {
|
|
|
|
posts.splice(posts.indexOf(post), 1);
|
|
|
|
}
|
2019-09-18 19:52:39 +02:00
|
|
|
});
|
2019-03-31 03:20:55 +02:00
|
|
|
this.setState({ posts });
|
|
|
|
}
|
2019-09-18 19:52:39 +02:00
|
|
|
});
|
2019-03-31 03:20:55 +02:00
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
this.streamListener.on("error", (err: Error) => {
|
2019-03-31 03:20:55 +02:00
|
|
|
this.setState({
|
|
|
|
viewDidError: true,
|
|
|
|
viewDidErrorCode: err.message
|
|
|
|
});
|
2019-09-18 19:52:39 +02:00
|
|
|
this.props.enqueueSnackbar("An error occured.", {
|
|
|
|
variant: "error"
|
|
|
|
});
|
|
|
|
});
|
2019-03-31 03:20:55 +02:00
|
|
|
|
2019-09-18 19:52:39 +02:00
|
|
|
this.streamListener.on("heartbeat", () => {});
|
2019-03-31 03:20:55 +02:00
|
|
|
}
|
|
|
|
|
2019-04-06 19:41:43 +02:00
|
|
|
componentWillUnmount() {
|
|
|
|
this.streamListener.stop();
|
|
|
|
}
|
|
|
|
|
2019-03-31 03:20:55 +02:00
|
|
|
insertBacklog() {
|
|
|
|
window.scrollTo(0, 0);
|
|
|
|
let posts = this.state.posts;
|
|
|
|
let backlog = this.state.backlogPosts;
|
|
|
|
if (posts && backlog && backlog.length > 0) {
|
|
|
|
let push = backlog.concat(posts);
|
2019-09-18 19:52:39 +02:00
|
|
|
this.setState({ posts: push as [Status], backlogPosts: null });
|
2019-03-31 03:20:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
loadMoreTimelinePieces() {
|
2019-09-18 19:52:39 +02:00
|
|
|
this.setState({ viewDidLoad: false, viewIsLoading: true });
|
2019-03-31 03:20:55 +02:00
|
|
|
if (this.state.posts) {
|
2019-09-18 19:52:39 +02:00
|
|
|
this.client
|
|
|
|
.get("/timelines/public", {
|
|
|
|
max_id: this.state.posts[this.state.posts.length - 1].id,
|
|
|
|
limit: 20,
|
|
|
|
local: true
|
2019-03-31 03:20:55 +02:00
|
|
|
})
|
2019-09-18 19:52:39 +02:00
|
|
|
.then((resp: any) => {
|
|
|
|
let newPosts: [Status] = resp.data;
|
|
|
|
let posts = this.state.posts as [Status];
|
|
|
|
newPosts.forEach((post: Status) => {
|
|
|
|
posts.push(post);
|
|
|
|
});
|
|
|
|
this.setState({
|
|
|
|
viewIsLoading: false,
|
|
|
|
viewDidLoad: true,
|
|
|
|
posts
|
|
|
|
});
|
2019-03-31 03:20:55 +02:00
|
|
|
})
|
2019-09-18 19:52:39 +02:00
|
|
|
.catch((err: Error) => {
|
|
|
|
this.setState({
|
|
|
|
viewIsLoading: false,
|
|
|
|
viewDidError: true,
|
|
|
|
viewDidErrorCode: err.message
|
|
|
|
});
|
|
|
|
this.props.enqueueSnackbar("Failed to get posts", {
|
|
|
|
variant: "error"
|
|
|
|
});
|
2019-03-31 03:20:55 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2019-09-18 19:52:39 +02:00
|
|
|
const { classes } = this.props;
|
2019-03-31 03:20:55 +02:00
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={classes.pageLayoutMaxConstraints}>
|
2019-09-18 19:52:39 +02:00
|
|
|
{this.state.backlogPosts ? (
|
2019-03-31 03:20:55 +02:00
|
|
|
<div className={classes.pageTopChipContainer}>
|
|
|
|
<div className={classes.pageTopChips}>
|
|
|
|
<Slide direction="down" in={true}>
|
|
|
|
<Chip
|
|
|
|
avatar={
|
|
|
|
<Avatar>
|
2019-09-18 19:52:39 +02:00
|
|
|
<ArrowUpwardIcon />
|
2019-03-31 03:20:55 +02:00
|
|
|
</Avatar>
|
|
|
|
}
|
2019-09-18 19:52:39 +02:00
|
|
|
label={`View ${
|
|
|
|
this.state.backlogPosts.length
|
|
|
|
} new post${
|
|
|
|
this.state.backlogPosts.length > 1
|
|
|
|
? "s"
|
|
|
|
: ""
|
|
|
|
}`}
|
2019-03-31 03:20:55 +02:00
|
|
|
color="primary"
|
|
|
|
className={classes.pageTopChip}
|
|
|
|
onClick={() => this.insertBacklog()}
|
|
|
|
clickable
|
2019-09-18 19:52:39 +02:00
|
|
|
/>
|
2019-03-31 03:20:55 +02:00
|
|
|
</Slide>
|
|
|
|
</div>
|
2019-09-18 19:52:39 +02:00
|
|
|
</div>
|
|
|
|
) : null}
|
|
|
|
{this.state.posts ? (
|
2019-03-31 03:20:55 +02:00
|
|
|
<div>
|
2019-12-22 00:10:24 +01:00
|
|
|
{this.state.isMasonryLayout ? (
|
|
|
|
<Masonry
|
|
|
|
breakpointCols={{
|
|
|
|
default: 4,
|
|
|
|
2000: 3,
|
|
|
|
1400: 2,
|
|
|
|
1050: 1,
|
|
|
|
}}
|
|
|
|
className={classes.masonryGrid}
|
|
|
|
columnClassName={classes['my-masonry-grid_column']}
|
|
|
|
>
|
|
|
|
{this.state.posts.map((post: Status) => {
|
|
|
|
return (
|
|
|
|
<div className={classes.masonryGrid_item}>
|
|
|
|
<Post
|
|
|
|
key={post.id}
|
|
|
|
post={post}
|
|
|
|
client={this.client}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</Masonry>
|
|
|
|
) : (
|
|
|
|
<div>
|
|
|
|
{this.state.posts.map((post: Status) => {
|
|
|
|
return (
|
|
|
|
<div className={classes.masonryGrid_item}>
|
|
|
|
<Post
|
|
|
|
key={post.id}
|
|
|
|
post={post}
|
|
|
|
client={this.client}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
)}
|
2019-09-18 19:52:39 +02:00
|
|
|
<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.viewDidError ? (
|
|
|
|
<Paper className={classes.errorCard}>
|
|
|
|
<Typography variant="h4">Bummer.</Typography>
|
|
|
|
<Typography variant="h6">
|
|
|
|
Something went wrong when loading this timeline.
|
|
|
|
</Typography>
|
|
|
|
<Typography>
|
|
|
|
{this.state.viewDidErrorCode
|
|
|
|
? this.state.viewDidErrorCode
|
|
|
|
: ""}
|
|
|
|
</Typography>
|
|
|
|
</Paper>
|
|
|
|
) : (
|
|
|
|
<span />
|
|
|
|
)}
|
|
|
|
{this.state.viewIsLoading ? (
|
|
|
|
<div style={{ textAlign: "center" }}>
|
|
|
|
<CircularProgress
|
|
|
|
className={classes.progress}
|
|
|
|
color="primary"
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<span />
|
|
|
|
)}
|
2019-03-31 03:20:55 +02:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default withStyles(styles)(withSnackbar(LocalPage));
|