forked from pkreissel/foryoufeed
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Feed.tsx
177 lines (149 loc) · 7.09 KB
/
Feed.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*
* Class for retrieving and sorting the user's feed based on their chosen weighting values.
*/
import React, { useState, useEffect, useRef } from "react";
import { Modal } from "react-bootstrap";
import Col from 'react-bootstrap/Col';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import { mastodon, createRestAPIClient as loginToMastodon } from "masto";
import { TheAlgorithm, Toot, Weights } from "fedialgo";
import FilterSetter from "../components/FilterSetter";
import FindFollowers from "../components/FindFollowers";
import FullPageIsLoading, { DEFAULT_LOADING_MESSAGE } from "../components/FullPageIsLoading";
import StatusComponent from "../components/Status";
import useOnScreen from "../hooks/useOnScreen";
import WeightSetter from "../components/WeightSetter";
import { useAuthContext } from "../hooks/useAuth";
const DEFAULT_NUM_TOOTS = 20;
const NUM_TOOTS_TO_LOAD_ON_SCROLL = 10;
const RELOAD_IF_OLDER_THAN_MS = 1000 * 60 * 15; // 15 minutes
const FOCUS = "focus";
const VISIBILITY_CHANGE = "visibilitychange";
export default function Feed() {
// Contruct Feed on Page Load
const { user, logout } = useAuthContext();
// State variables
const [algorithm, setAlgorithm] = useState<TheAlgorithm>(null);
const [error, setError] = useState<string>("");
const [feed, setFeed] = useState<Toot[]>([]); // timeline toots
const [isLoading, setIsLoading] = useState<boolean>(true);
const [numDisplayedToots, setNumDisplayedToots] = useState<number>(DEFAULT_NUM_TOOTS);
const api: mastodon.rest.Client = loginToMastodon({url: user.server, accessToken: user.access_token});
const bottomRef = useRef<HTMLDivElement>(null);
const isBottom = useOnScreen(bottomRef);
const handleFocus = () => {
console.log(`window is ${document.hasFocus() ? "focused" : "not focused"}`);
if (isLoading && feed.length == 0) {
console.log(`isLoading=True; not reloading feed...`);
return;
} else if (!algorithm) {
console.warn("Algorithm not set yet!");
return;
} else if (!shouldReloadFeed()) {
console.log(`shouldReloadFeed() returned false; not reloading feed...`);
return;
}
algorithm.getFeed();
};
// Load the posts in the feed either from mastodon server or from the cache
useEffect(() => {
if (!user) {
console.warn("User not set yet!");
return;
}
constructFeed();
const handleVisibility = () => console.log(`Tab is ${document.visibilityState}`);
window.addEventListener(VISIBILITY_CHANGE, handleVisibility);
window.addEventListener(FOCUS, handleFocus);
return () => {
window.removeEventListener(VISIBILITY_CHANGE, handleVisibility);
window.removeEventListener(FOCUS, handleFocus);
}
}, [user]);
// Show more toots when the user scrolls to bottom of the page
// TODO: This doesn't actually trigger any API calls, it just shows more of the preloaded toots
useEffect(() => {
if (isBottom) {
console.log("hit bottom of page; should load more toots...");
showMoreToots();
}
}, [isBottom]);
// Check that we have valid user credentials and load timeline toots, otherwise force a logout.
const constructFeed = async (): Promise<void> => {
console.log(`constructFeed() called with user ID ${user?.id} (feed already has ${feed.length} toots)`);
let currentUser: mastodon.v1.Account;
try {
if (!user) throw new Error(`User not set in constructFeed()!`);
currentUser = await api.v1.accounts.verifyCredentials();
} catch (error) {
console.warn(`Failed to verifyCredentials() with error:`, error);
logout();
return;
}
const algo = await TheAlgorithm.create({api: api, user: currentUser, setFeedInApp: setFeed});
setAlgorithm(algo);
// If there are toots in the cache set isLoading to false early so something is displayed
if (algo.feed.length > 0) setIsLoading(false);
await algo.getFeed();
console.log(`constructFeed() finished; feed has ${algo.feed.length} toots, setting isLoading=false`);
setIsLoading(false);
};
const shouldReloadFeed = (): boolean => {
if (!algorithm || !feed || feed.length == 0) return false;
const mostRecentAt = algorithm.mostRecentTootAt();
const should = ((Date.now() - mostRecentAt.getTime()) > RELOAD_IF_OLDER_THAN_MS);
console.log(`shouldReloadFeed() mostRecentAt: ${mostRecentAt}, should: ${should}`);
return should;
}
// Pull more toots to display from our local cached and sorted toot feed
// TODO: this should trigger the pulling of more toots from the server if we run out of local cache
const showMoreToots = () => {
console.log(`Showing ${numDisplayedToots} toots, ${NUM_TOOTS_TO_LOAD_ON_SCROLL} more (${feed.length} available to show)`);
setNumDisplayedToots(numDisplayedToots + NUM_TOOTS_TO_LOAD_ON_SCROLL);
};
// Learn weights based on user action // TODO: does learning weights really work?
const learnWeights = async (scores: Weights): Promise<void> => {
const newWeights = await algorithm.learnWeights(scores);
if (!newWeights) return;
};
return (
<Container fluid style={{height: 'auto'}}>
<Modal show={error !== ""} onHide={() => setError("")}>
<Modal.Header closeButton>
<Modal.Title>Error</Modal.Title>
</Modal.Header>
<Modal.Body>{error}</Modal.Body>
</Modal>
<Row>
<Col xs={6}>
<div className="sticky-top">
{algorithm && <WeightSetter algorithm={algorithm} />}
{algorithm && <FilterSetter algorithm={algorithm} />}
<FindFollowers api={api} user={user} />
</div>
</Col>
<Col style={{backgroundColor: '#15202b', height: 'auto'}} xs={6}>
{!isLoading && api && (feed.length >= 1) &&
feed.slice(0, Math.max(DEFAULT_NUM_TOOTS, numDisplayedToots)).map((toot) => (
<StatusComponent
api={api}
key={toot.uri}
learnWeights={learnWeights}
setError={setError}
status={toot}
user={user}
/>
))}
{(isLoading || feed.length == 0) &&
<FullPageIsLoading
message={isLoading ? DEFAULT_LOADING_MESSAGE : "No toots found! Maybe check your filter settings"}
/>}
<div ref={bottomRef} onClick={showMoreToots}>
<p>Load More</p>
</div>
</Col>
</Row>
</Container>
);
};