mirror of
https://github.com/cheeaun/phanpy.git
synced 2025-03-23 06:09:21 +01:00
More refactoring work
This commit is contained in:
parent
016aea711b
commit
e0bab6c70a
11 changed files with 281 additions and 102 deletions
|
@ -26,6 +26,7 @@ import NotFound from './pages/404';
|
||||||
import AccountStatuses from './pages/account-statuses';
|
import AccountStatuses from './pages/account-statuses';
|
||||||
import Bookmarks from './pages/bookmarks';
|
import Bookmarks from './pages/bookmarks';
|
||||||
import Favourites from './pages/favourites';
|
import Favourites from './pages/favourites';
|
||||||
|
import Following from './pages/following';
|
||||||
import Hashtags from './pages/hashtags';
|
import Hashtags from './pages/hashtags';
|
||||||
import Home from './pages/home';
|
import Home from './pages/home';
|
||||||
import Lists from './pages/lists';
|
import Lists from './pages/lists';
|
||||||
|
@ -205,6 +206,7 @@ function App() {
|
||||||
{isLoggedIn && (
|
{isLoggedIn && (
|
||||||
<Route path="/notifications" element={<Notifications />} />
|
<Route path="/notifications" element={<Notifications />} />
|
||||||
)}
|
)}
|
||||||
|
{isLoggedIn && <Route path="/l/f" element={<Following />} />}
|
||||||
{isLoggedIn && <Route path="/b" element={<Bookmarks />} />}
|
{isLoggedIn && <Route path="/b" element={<Bookmarks />} />}
|
||||||
{isLoggedIn && <Route path="/f" element={<Favourites />} />}
|
{isLoggedIn && <Route path="/f" element={<Favourites />} />}
|
||||||
{isLoggedIn && <Route path="/l/:id" element={<Lists />} />}
|
{isLoggedIn && <Route path="/l/:id" element={<Lists />} />}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
|
||||||
import useScroll from '../utils/useScroll';
|
import useScroll from '../utils/useScroll';
|
||||||
import useTitle from '../utils/useTitle';
|
|
||||||
|
|
||||||
import Icon from './icon';
|
import Icon from './icon';
|
||||||
import Link from './link';
|
import Link from './link';
|
||||||
|
@ -11,29 +11,33 @@ import Status from './status';
|
||||||
function Timeline({
|
function Timeline({
|
||||||
title,
|
title,
|
||||||
titleComponent,
|
titleComponent,
|
||||||
path,
|
|
||||||
id,
|
id,
|
||||||
emptyText,
|
emptyText,
|
||||||
errorText,
|
errorText,
|
||||||
|
boostsCarousel,
|
||||||
fetchItems = () => {},
|
fetchItems = () => {},
|
||||||
}) {
|
}) {
|
||||||
if (title) {
|
|
||||||
useTitle(title, path);
|
|
||||||
}
|
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
const [uiState, setUIState] = useState('default');
|
const [uiState, setUIState] = useState('default');
|
||||||
const [showMore, setShowMore] = useState(false);
|
const [showMore, setShowMore] = useState(false);
|
||||||
const scrollableRef = useRef(null);
|
const scrollableRef = useRef(null);
|
||||||
const { nearReachEnd, reachStart } = useScroll({
|
const { nearReachEnd, reachStart, reachEnd } = useScroll({
|
||||||
scrollableElement: scrollableRef.current,
|
scrollableElement: scrollableRef.current,
|
||||||
|
distanceFromEnd: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadItems = (firstLoad) => {
|
const loadItems = useDebouncedCallback(
|
||||||
|
(firstLoad) => {
|
||||||
|
if (uiState === 'loading') return;
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const { done, value } = await fetchItems(firstLoad);
|
let { done, value } = await fetchItems(firstLoad);
|
||||||
if (value?.length) {
|
if (value?.length) {
|
||||||
|
if (boostsCarousel) {
|
||||||
|
value = groupBoosts(value);
|
||||||
|
}
|
||||||
|
console.log(value);
|
||||||
if (firstLoad) {
|
if (firstLoad) {
|
||||||
setItems(value);
|
setItems(value);
|
||||||
} else {
|
} else {
|
||||||
|
@ -49,7 +53,13 @@ function Timeline({
|
||||||
setUIState('error');
|
setUIState('error');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
};
|
},
|
||||||
|
1500,
|
||||||
|
{
|
||||||
|
leading: true,
|
||||||
|
trailing: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
scrollableRef.current?.scrollTo({ top: 0 });
|
scrollableRef.current?.scrollTo({ top: 0 });
|
||||||
|
@ -63,7 +73,7 @@ function Timeline({
|
||||||
}, [reachStart]);
|
}, [reachStart]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (nearReachEnd && showMore) {
|
if (nearReachEnd || (reachEnd && showMore)) {
|
||||||
loadItems();
|
loadItems();
|
||||||
}
|
}
|
||||||
}, [nearReachEnd, showMore]);
|
}, [nearReachEnd, showMore]);
|
||||||
|
@ -100,8 +110,15 @@ function Timeline({
|
||||||
<>
|
<>
|
||||||
<ul class="timeline">
|
<ul class="timeline">
|
||||||
{items.map((status) => {
|
{items.map((status) => {
|
||||||
const { id: statusID, reblog } = status;
|
const { id: statusID, reblog, boosts } = status;
|
||||||
const actualStatusID = reblog?.id || statusID;
|
const actualStatusID = reblog?.id || statusID;
|
||||||
|
if (boosts) {
|
||||||
|
return (
|
||||||
|
<li key={`timeline-${statusID}`}>
|
||||||
|
<BoostsCarousel boosts={boosts} />
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<li key={`timeline-${statusID}`}>
|
<li key={`timeline-${statusID}`}>
|
||||||
<Link class="status-link" to={`/s/${actualStatusID}`}>
|
<Link class="status-link" to={`/s/${actualStatusID}`}>
|
||||||
|
@ -111,21 +128,19 @@ function Timeline({
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
{showMore && (
|
{uiState === 'default' &&
|
||||||
|
(showMore ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="plain block"
|
class="plain block"
|
||||||
disabled={uiState === 'loading'}
|
|
||||||
onClick={() => loadItems()}
|
onClick={() => loadItems()}
|
||||||
style={{ marginBlockEnd: '6em' }}
|
style={{ marginBlockEnd: '6em' }}
|
||||||
>
|
>
|
||||||
{uiState === 'loading' ? (
|
Show more…
|
||||||
<Loader abrupt />
|
|
||||||
) : (
|
|
||||||
<>Show more…</>
|
|
||||||
)}
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
) : (
|
||||||
|
<p class="ui-state insignificant">The end.</p>
|
||||||
|
))}
|
||||||
</>
|
</>
|
||||||
) : uiState === 'loading' ? (
|
) : uiState === 'loading' ? (
|
||||||
<ul class="timeline">
|
<ul class="timeline">
|
||||||
|
@ -136,9 +151,9 @@ function Timeline({
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
uiState !== 'loading' && <p class="ui-state">{emptyText}</p>
|
uiState !== 'error' && <p class="ui-state">{emptyText}</p>
|
||||||
)}
|
)}
|
||||||
{uiState === 'error' ? (
|
{uiState === 'error' && (
|
||||||
<p class="ui-state">
|
<p class="ui-state">
|
||||||
{errorText}
|
{errorText}
|
||||||
<br />
|
<br />
|
||||||
|
@ -150,14 +165,112 @@ function Timeline({
|
||||||
Try again
|
Try again
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
) : (
|
|
||||||
uiState !== 'loading' &&
|
|
||||||
!!items.length &&
|
|
||||||
!showMore && <p class="ui-state insignificant">The end.</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function groupBoosts(values) {
|
||||||
|
let newValues = [];
|
||||||
|
let boostStash = [];
|
||||||
|
let serialBoosts = 0;
|
||||||
|
for (let i = 0; i < values.length; i++) {
|
||||||
|
const item = values[i];
|
||||||
|
if (item.reblog) {
|
||||||
|
boostStash.push(item);
|
||||||
|
serialBoosts++;
|
||||||
|
} else {
|
||||||
|
newValues.push(item);
|
||||||
|
if (serialBoosts < 3) {
|
||||||
|
serialBoosts = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if boostStash is more than quarter of values
|
||||||
|
// or if there are 3 or more boosts in a row
|
||||||
|
if (boostStash.length > values.length / 4 || serialBoosts >= 3) {
|
||||||
|
// if boostStash is more than 3 quarter of values
|
||||||
|
const boostStashID = boostStash.map((status) => status.id);
|
||||||
|
if (boostStash.length > (values.length * 3) / 4) {
|
||||||
|
// insert boost array at the end of specialHome list
|
||||||
|
newValues = [...newValues, { id: boostStashID, boosts: boostStash }];
|
||||||
|
} else {
|
||||||
|
// insert boosts array in the middle of specialHome list
|
||||||
|
const half = Math.floor(newValues.length / 2);
|
||||||
|
newValues = [
|
||||||
|
...newValues.slice(0, half),
|
||||||
|
{
|
||||||
|
id: boostStashID,
|
||||||
|
boosts: boostStash,
|
||||||
|
},
|
||||||
|
...newValues.slice(half),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return newValues;
|
||||||
|
} else {
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function BoostsCarousel({ boosts }) {
|
||||||
|
const carouselRef = useRef();
|
||||||
|
const { reachStart, reachEnd, init } = useScroll({
|
||||||
|
scrollableElement: carouselRef.current,
|
||||||
|
direction: 'horizontal',
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
init?.();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="boost-carousel">
|
||||||
|
<header>
|
||||||
|
<h3>{boosts.length} Boosts</h3>
|
||||||
|
<span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="small plain2"
|
||||||
|
disabled={reachStart}
|
||||||
|
onClick={() => {
|
||||||
|
carouselRef.current?.scrollBy({
|
||||||
|
left: -Math.min(320, carouselRef.current?.offsetWidth),
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon icon="chevron-left" />
|
||||||
|
</button>{' '}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="small plain2"
|
||||||
|
disabled={reachEnd}
|
||||||
|
onClick={() => {
|
||||||
|
carouselRef.current?.scrollBy({
|
||||||
|
left: Math.min(320, carouselRef.current?.offsetWidth),
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon icon="chevron-right" />
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</header>
|
||||||
|
<ul ref={carouselRef}>
|
||||||
|
{boosts.map((boost) => {
|
||||||
|
const { id: statusID, reblog } = boost;
|
||||||
|
const actualStatusID = reblog?.id || statusID;
|
||||||
|
return (
|
||||||
|
<li key={statusID}>
|
||||||
|
<Link class="status-boost-link" to={`/s/${actualStatusID}`}>
|
||||||
|
<Status status={boost} size="s" />
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default Timeline;
|
export default Timeline;
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import { useEffect, useRef, useState } from 'preact/hooks';
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
import Timeline from '../components/timeline';
|
import Timeline from '../components/timeline';
|
||||||
import states from '../utils/states';
|
import states from '../utils/states';
|
||||||
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
|
||||||
function AccountStatuses() {
|
function AccountStatuses() {
|
||||||
|
const snapStates = useSnapshot(states);
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const accountStatusesIterator = useRef();
|
const accountStatusesIterator = useRef();
|
||||||
async function fetchAccountStatuses(firstLoad) {
|
async function fetchAccountStatuses(firstLoad) {
|
||||||
|
@ -19,6 +22,7 @@ function AccountStatuses() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [account, setAccount] = useState({});
|
const [account, setAccount] = useState({});
|
||||||
|
useTitle(`${account?.acct ? '@' + account.acct : 'Posts'}`, '/a/:id');
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -48,11 +52,11 @@ function AccountStatuses() {
|
||||||
</div>
|
</div>
|
||||||
</h1>
|
</h1>
|
||||||
}
|
}
|
||||||
path="/a/:id"
|
|
||||||
id="account_statuses"
|
id="account_statuses"
|
||||||
emptyText="Nothing to see here yet."
|
emptyText="Nothing to see here yet."
|
||||||
errorText="Unable to load statuses"
|
errorText="Unable to load statuses"
|
||||||
fetchItems={fetchAccountStatuses}
|
fetchItems={fetchAccountStatuses}
|
||||||
|
boostsCarousel={snapStates.settings.boostsCarousel}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { useRef } from 'preact/hooks';
|
import { useRef } from 'preact/hooks';
|
||||||
|
|
||||||
import Timeline from '../components/timeline';
|
import Timeline from '../components/timeline';
|
||||||
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
|
||||||
function Bookmarks() {
|
function Bookmarks() {
|
||||||
|
useTitle('Bookmarks', '/b');
|
||||||
const bookmarksIterator = useRef();
|
const bookmarksIterator = useRef();
|
||||||
async function fetchBookmarks(firstLoad) {
|
async function fetchBookmarks(firstLoad) {
|
||||||
if (firstLoad || !bookmarksIterator.current) {
|
if (firstLoad || !bookmarksIterator.current) {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { useRef } from 'preact/hooks';
|
import { useRef } from 'preact/hooks';
|
||||||
|
|
||||||
import Timeline from '../components/timeline';
|
import Timeline from '../components/timeline';
|
||||||
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
|
||||||
function Favourites() {
|
function Favourites() {
|
||||||
|
useTitle('Favourites', '/f');
|
||||||
const favouritesIterator = useRef();
|
const favouritesIterator = useRef();
|
||||||
async function fetchFavourites(firstLoad) {
|
async function fetchFavourites(firstLoad) {
|
||||||
if (firstLoad || !favouritesIterator.current) {
|
if (firstLoad || !favouritesIterator.current) {
|
||||||
|
|
32
src/pages/following.jsx
Normal file
32
src/pages/following.jsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import { useRef } from 'preact/hooks';
|
||||||
|
import { useSnapshot } from 'valtio';
|
||||||
|
|
||||||
|
import Timeline from '../components/timeline';
|
||||||
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
|
const LIMIT = 20;
|
||||||
|
|
||||||
|
function Following() {
|
||||||
|
useTitle('Following', '/l/f');
|
||||||
|
const snapStates = useSnapshot(states);
|
||||||
|
const homeIterator = useRef();
|
||||||
|
async function fetchHome(firstLoad) {
|
||||||
|
if (firstLoad || !homeIterator.current) {
|
||||||
|
homeIterator.current = masto.v1.timelines.listHome({ limit: LIMIT });
|
||||||
|
}
|
||||||
|
return await homeIterator.current.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Timeline
|
||||||
|
title="Following"
|
||||||
|
id="following"
|
||||||
|
emptyText="Nothing to see here."
|
||||||
|
errorText="Unable to load posts."
|
||||||
|
fetchItems={fetchHome}
|
||||||
|
boostsCarousel={snapStates.settings.boostsCarousel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Following;
|
|
@ -2,11 +2,13 @@ import { useRef } from 'preact/hooks';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import Timeline from '../components/timeline';
|
import Timeline from '../components/timeline';
|
||||||
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
|
||||||
function Hashtags() {
|
function Hashtags() {
|
||||||
const { hashtag } = useParams();
|
const { hashtag } = useParams();
|
||||||
|
useTitle(`#${hashtag}`, `/t/${hashtag}`);
|
||||||
const hashtagsIterator = useRef();
|
const hashtagsIterator = useRef();
|
||||||
async function fetchHashtags(firstLoad) {
|
async function fetchHashtags(firstLoad) {
|
||||||
if (firstLoad || !hashtagsIterator.current) {
|
if (firstLoad || !hashtagsIterator.current) {
|
||||||
|
|
|
@ -118,10 +118,9 @@ function Home({ hidden }) {
|
||||||
return allStatuses;
|
return allStatuses;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadingStatuses = useRef(false);
|
const loadStatuses = useDebouncedCallback(
|
||||||
const loadStatuses = (firstLoad) => {
|
(firstLoad) => {
|
||||||
if (loadingStatuses.current) return;
|
if (uiState === 'loading') return;
|
||||||
loadingStatuses.current = true;
|
|
||||||
setUIState('loading');
|
setUIState('loading');
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -132,14 +131,15 @@ function Home({ hidden }) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
setUIState('error');
|
setUIState('error');
|
||||||
} finally {
|
} finally {
|
||||||
loadingStatuses.current = false;
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
};
|
},
|
||||||
const debouncedLoadStatuses = useDebouncedCallback(loadStatuses, 3000, {
|
1500,
|
||||||
|
{
|
||||||
leading: true,
|
leading: true,
|
||||||
trailing: false,
|
trailing: false,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadStatuses(true);
|
loadStatuses(true);
|
||||||
|
@ -271,7 +271,6 @@ function Home({ hidden }) {
|
||||||
reachEnd,
|
reachEnd,
|
||||||
} = useScroll({
|
} = useScroll({
|
||||||
scrollableElement: scrollableRef.current,
|
scrollableElement: scrollableRef.current,
|
||||||
distanceFromStart: 1,
|
|
||||||
distanceFromEnd: 3,
|
distanceFromEnd: 3,
|
||||||
scrollThresholdStart: 44,
|
scrollThresholdStart: 44,
|
||||||
});
|
});
|
||||||
|
@ -284,7 +283,7 @@ function Home({ hidden }) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (reachStart) {
|
if (reachStart) {
|
||||||
debouncedLoadStatuses(true);
|
loadStatuses(true);
|
||||||
}
|
}
|
||||||
}, [reachStart]);
|
}, [reachStart]);
|
||||||
|
|
||||||
|
@ -324,7 +323,7 @@ function Home({ hidden }) {
|
||||||
scrollableRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
scrollableRef.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
}}
|
}}
|
||||||
onDblClick={() => {
|
onDblClick={() => {
|
||||||
debouncedLoadStatuses(true);
|
loadStatuses(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="header-side">
|
<div class="header-side">
|
||||||
|
@ -372,7 +371,7 @@ function Home({ hidden }) {
|
||||||
);
|
);
|
||||||
states.home.unshift(...uniqueHomeNew);
|
states.home.unshift(...uniqueHomeNew);
|
||||||
}
|
}
|
||||||
debouncedLoadStatuses(true);
|
loadStatuses(true);
|
||||||
states.homeNew = [];
|
states.homeNew = [];
|
||||||
|
|
||||||
scrollableRef.current?.scrollTo({
|
scrollableRef.current?.scrollTo({
|
||||||
|
@ -404,7 +403,7 @@ function Home({ hidden }) {
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{showMore && (
|
{showMore && uiState === 'loading' && (
|
||||||
<>
|
<>
|
||||||
<li
|
<li
|
||||||
style={{
|
style={{
|
||||||
|
@ -423,10 +422,21 @@ function Home({ hidden }) {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</>
|
{uiState === 'default' &&
|
||||||
|
(showMore ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="plain block"
|
||||||
|
onClick={() => loadStatuses()}
|
||||||
|
style={{ marginBlockEnd: '6em' }}
|
||||||
|
>
|
||||||
|
Show more…
|
||||||
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<p class="ui-state insignificant">The end.</p>
|
||||||
{uiState === 'loading' && (
|
))}
|
||||||
|
</>
|
||||||
|
) : uiState === 'loading' ? (
|
||||||
<ul class="timeline">
|
<ul class="timeline">
|
||||||
{Array.from({ length: 5 }).map((_, i) => (
|
{Array.from({ length: 5 }).map((_, i) => (
|
||||||
<li key={i}>
|
<li key={i}>
|
||||||
|
@ -434,6 +444,8 @@ function Home({ hidden }) {
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
) : (
|
||||||
|
uiState !== 'error' && <p class="ui-state">Nothing to see here.</p>
|
||||||
)}
|
)}
|
||||||
{uiState === 'error' && (
|
{uiState === 'error' && (
|
||||||
<p class="ui-state">
|
<p class="ui-state">
|
||||||
|
@ -443,15 +455,13 @@ function Home({ hidden }) {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
debouncedLoadStatuses(true);
|
loadStatuses(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Try again
|
Try again
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'preact/hooks';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import Timeline from '../components/timeline';
|
import Timeline from '../components/timeline';
|
||||||
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ function Lists() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [title, setTitle] = useState(`List ${id}`);
|
const [title, setTitle] = useState(`List ${id}`);
|
||||||
|
useTitle(title, `/l/${id}`);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -36,6 +38,7 @@ function Lists() {
|
||||||
emptyText="Nothing yet."
|
emptyText="Nothing yet."
|
||||||
errorText="Unable to load posts."
|
errorText="Unable to load posts."
|
||||||
fetchItems={fetchLists}
|
fetchItems={fetchLists}
|
||||||
|
boostsCarousel
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { useMatch, useParams } from 'react-router-dom';
|
import { useMatch, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import Timeline from '../components/timeline';
|
import Timeline from '../components/timeline';
|
||||||
|
import useTitle from '../utils/useTitle';
|
||||||
|
|
||||||
const LIMIT = 20;
|
const LIMIT = 20;
|
||||||
|
|
||||||
|
@ -11,6 +12,8 @@ function Public() {
|
||||||
const isLocal = !!useMatch('/p/l/:instance');
|
const isLocal = !!useMatch('/p/l/:instance');
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const { instance = '' } = params;
|
const { instance = '' } = params;
|
||||||
|
const title = `${instance} (${isLocal ? 'local' : 'federated'})`;
|
||||||
|
useTitle(title, `/p/${instance}`);
|
||||||
async function fetchPublic(firstLoad) {
|
async function fetchPublic(firstLoad) {
|
||||||
const url = firstLoad
|
const url = firstLoad
|
||||||
? `https://${instance}/api/v1/timelines/public?limit=${LIMIT}&local=${isLocal}`
|
? `https://${instance}/api/v1/timelines/public?limit=${LIMIT}&local=${isLocal}`
|
||||||
|
@ -37,7 +40,7 @@ function Public() {
|
||||||
return (
|
return (
|
||||||
<Timeline
|
<Timeline
|
||||||
key={instance + isLocal}
|
key={instance + isLocal}
|
||||||
title={`${instance} (${isLocal ? 'local' : 'federated'})`}
|
title={title}
|
||||||
id="public"
|
id="public"
|
||||||
emptyText="No one has posted anything yet."
|
emptyText="No one has posted anything yet."
|
||||||
errorText="Unable to load posts"
|
errorText="Unable to load posts"
|
||||||
|
|
|
@ -38,8 +38,14 @@ export default function useScroll({
|
||||||
const scrollDimension = isVertical ? scrollHeight : scrollWidth;
|
const scrollDimension = isVertical ? scrollHeight : scrollWidth;
|
||||||
const clientDimension = isVertical ? clientHeight : clientWidth;
|
const clientDimension = isVertical ? clientHeight : clientWidth;
|
||||||
const scrollDistance = Math.abs(scrollStart - previousScrollStart);
|
const scrollDistance = Math.abs(scrollStart - previousScrollStart);
|
||||||
const distanceFromStartPx = clientDimension * distanceFromStart;
|
const distanceFromStartPx = Math.min(
|
||||||
const distanceFromEndPx = clientDimension * distanceFromEnd;
|
clientDimension * distanceFromStart,
|
||||||
|
scrollDimension,
|
||||||
|
);
|
||||||
|
const distanceFromEndPx = Math.min(
|
||||||
|
clientDimension * distanceFromEnd,
|
||||||
|
scrollDimension,
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
scrollDistance >=
|
scrollDistance >=
|
||||||
|
|
Loading…
Reference in a new issue