152 lines
4.0 KiB
Svelte
152 lines
4.0 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from "svelte";
|
|
import type { PageData } from './$types';
|
|
import type { Post } from '$lib/mastodon/response';
|
|
import { PUBLIC_REFRESH_INTERVAL, PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME } from '$env/static/public';
|
|
import PostComponent from '$lib/components/PostComponent.svelte';
|
|
import LoadMoreComponent from '$lib/components/LoadMoreComponent.svelte';
|
|
|
|
export let data: PageData;
|
|
|
|
const refreshInterval = parseInt(PUBLIC_REFRESH_INTERVAL);
|
|
let interval: NodeJS.Timer | null = null;
|
|
let moreOlderPostsAvailable = true;
|
|
let loadingOlderPosts = false;
|
|
|
|
interface FetchOptions {
|
|
since?: string,
|
|
before?: string,
|
|
count?: number
|
|
}
|
|
|
|
async function fetchPosts(options: FetchOptions): Promise<Post[]> {
|
|
const params = new URLSearchParams();
|
|
if (options?.since !== undefined) {
|
|
params.set('since', options.since);
|
|
}
|
|
if (options?.before !== undefined) {
|
|
params.set('before', options.before);
|
|
}
|
|
if (options?.count !== undefined) {
|
|
params.set('count', options.count.toFixed(0));
|
|
}
|
|
|
|
const response = await fetch(`/api/posts?${params}`);
|
|
return await response.json();
|
|
}
|
|
|
|
function filterDuplicates(posts: Post[]): Post[] {
|
|
return posts.filter((obj, index, arr) => {
|
|
return arr.map(mapObj => mapObj.url).indexOf(obj.url) === index;
|
|
});
|
|
}
|
|
|
|
function refresh() {
|
|
let filter: FetchOptions = {};
|
|
if (data.posts.length > 0) {
|
|
filter = { since: data.posts[0].created_at };
|
|
}
|
|
fetchPosts(filter).then(resp => {
|
|
if (resp.length > 0) {
|
|
// Prepend new posts, filter dupes
|
|
// There shouldn't be any duplicates, but better be safe than sorry
|
|
data.posts = filterDuplicates(resp.concat(data.posts));
|
|
}
|
|
})
|
|
.catch(e => {
|
|
// TODO: Show error in UI
|
|
console.error('Error loading newest posts', e);
|
|
});
|
|
}
|
|
|
|
onMount(async () => {
|
|
interval = setInterval(refresh, refreshInterval);
|
|
|
|
// - If the page is hidden, slow down refresh rate
|
|
// - If the page is shown, bump up refresh rate
|
|
document.addEventListener('visibilitychange', () => {
|
|
const delay = document.hidden ? refreshInterval * 10 : refreshInterval;
|
|
if (interval) {
|
|
clearInterval(interval);
|
|
}
|
|
interval = setInterval(refresh, delay);
|
|
});
|
|
|
|
return () => {
|
|
if (interval !== null) {
|
|
clearInterval(interval)
|
|
}
|
|
}
|
|
});
|
|
|
|
function loadOlderPosts() {
|
|
loadingOlderPosts = true;
|
|
const filter: FetchOptions = { count: 20 };
|
|
if (data.posts.length > 0) {
|
|
filter.before = data.posts[data.posts.length - 1].created_at;
|
|
}
|
|
|
|
|
|
fetchPosts(filter).then(resp => {
|
|
if (resp.length > 0) {
|
|
// Append old posts, filter dupes
|
|
// There shouldn't be any duplicates, but better be safe than sorry
|
|
data.posts = filterDuplicates(data.posts.concat(resp));
|
|
// If we got less than we expected, there are no older posts available
|
|
moreOlderPostsAvailable = resp.length < (filter.count ?? 20);
|
|
} else {
|
|
moreOlderPostsAvailable = false;
|
|
}
|
|
loadingOlderPosts = false;
|
|
})
|
|
.catch(e => {
|
|
loadingOlderPosts = false;
|
|
// TODO: Show error in UI
|
|
console.error('Error loading older posts', e);
|
|
});
|
|
|
|
}
|
|
|
|
</script>
|
|
<svelte:head>
|
|
<title>{PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME} music list</title>
|
|
</svelte:head>
|
|
<h2>{PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME} music list</h2>
|
|
<div class="wrapper">
|
|
<div></div>
|
|
<div class="posts">
|
|
{#if data.posts.length === 0}
|
|
Sorry, no posts recommending music aave been found yet
|
|
{/if}
|
|
{#each data.posts as post (post.url)}
|
|
<div class="post"><PostComponent {post} /></div>
|
|
{/each}
|
|
<LoadMoreComponent
|
|
on:loadOlderPosts={loadOlderPosts}
|
|
moreAvailable={moreOlderPostsAvailable}
|
|
isLoading={loadingOlderPosts}/>
|
|
</div>
|
|
<div></div>
|
|
</div>
|
|
<style>
|
|
.posts {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
.post {
|
|
width: 100%;
|
|
max-width: 600px;
|
|
margin-bottom: 1em;
|
|
border-bottom: 1px solid var(--color-border);
|
|
padding: 1em;
|
|
}
|
|
.wrapper {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
h2 {
|
|
text-align: center;
|
|
}
|
|
</style> |