Compare commits

...

2 Commits

Author SHA1 Message Date
20cdd8e688
Animate loading button and new posts arriving 2023-04-07 09:50:02 +02:00
c16bfd9c82
Improved button legibility 2023-04-06 18:29:05 +02:00
3 changed files with 114 additions and 10 deletions

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import LoadingSpinnerComponent from '$lib/components/LoadingSpinnerComponent.svelte';
export let moreAvailable: boolean = false; export let moreAvailable: boolean = false;
export let isLoading: boolean = false; export let isLoading: boolean = false;
@ -24,7 +25,12 @@
} }
</script> </script>
<button on:click={loadOlderPosts} {disabled} {title}>{displayText}</button> <button on:click={loadOlderPosts} {disabled} {title}>
<div class="loading" class:collapsed={!isLoading}>
<LoadingSpinnerComponent size='0.5em' thickness='6px' />
</div>
<span>{displayText}</span>
</button>
<style> <style>
button { button {
@ -34,7 +40,11 @@
background-color: var(--color-button); background-color: var(--color-button);
color: var(--color-button-text); color: var(--color-button-text);
cursor: grab; cursor: grab;
transition: all 0.2s ease-in-out; transition: all 0.3s ease-in-out;
font-size: large;
font-weight: bold;
display: flex;
align-items: center;
} }
button:hover:not(:disabled) { button:hover:not(:disabled) {
@ -42,7 +52,7 @@
} }
button:hover:not(:disabled):not(:active) { button:hover:not(:disabled):not(:active) {
box-shadow: 4px 4px 3px 0 var(--color-button-shadow); box-shadow: 6px 6px 5px 0 var(--color-button-shadow);
translate: -2px -2px; translate: -2px -2px;
} }
@ -52,6 +62,20 @@
} }
button:not(:disabled) { button:not(:disabled) {
box-shadow: 2px 2px 1px 0 var(--color-button-shadow); box-shadow: 4px 4px 2px 0 var(--color-button-shadow);
}
.loading {
margin-right: 3px;
display: flex;
overflow: hidden;
max-width: 100%;
transition: all 0.3s;
}
/* Cannot be removed, so that it animates its width change */
.collapsed {
max-width: 0;
margin-right: 0;
} }
</style> </style>

View File

@ -0,0 +1,38 @@
<script lang="ts">
export let size: string = '64px';
export let thickness: string = '6px';
</script>
<div class="lds-dual-ring" style="--size: {size}; --thickness: {thickness}"></div>
<style>
.lds-dual-ring {
display: inline-block;
width: 100%;
height: 100%;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: var(--size);
height: var(--size);
border-radius: 50%;
border: var(--thickness) solid #fff;
border-color: #fff transparent #fff transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(36deg);
}
75% {
transform: rotate(234deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -5,20 +5,52 @@ import type { Post } from '$lib/mastodon/response';
import { PUBLIC_REFRESH_INTERVAL, PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME } from '$env/static/public'; import { PUBLIC_REFRESH_INTERVAL, PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME } from '$env/static/public';
import PostComponent from '$lib/components/PostComponent.svelte'; import PostComponent from '$lib/components/PostComponent.svelte';
import LoadMoreComponent from '$lib/components/LoadMoreComponent.svelte'; import LoadMoreComponent from '$lib/components/LoadMoreComponent.svelte';
import { fly, type FlyParams } from 'svelte/transition';
import { cubicInOut } from 'svelte/easing';
export let data: PageData; export let data: PageData;
const refreshInterval = parseInt(PUBLIC_REFRESH_INTERVAL);
let interval: NodeJS.Timer | null = null;
let moreOlderPostsAvailable = true;
let loadingOlderPosts = false;
interface FetchOptions { interface FetchOptions {
since?: string, since?: string,
before?: string, before?: string,
count?: number count?: number
} }
interface EdgeFlyParams extends FlyParams {
created_at: string
}
const refreshInterval = parseInt(PUBLIC_REFRESH_INTERVAL) * 10;
let interval: NodeJS.Timer | null = null;
let moreOlderPostsAvailable = true;
let loadingOlderPosts = false;
// Needed, so that edgeFly() can do its thing:
// To determine whether a newly loaded post is older than the existing ones, is required to know what the oldest
// post was, before the fetch happened.
let oldestBeforeLastFetch: number | null = null;
/**
* Animate either from the top, or the bottom of the window, depending if the post is
* newer than the existing ones or older.
*/
function edgeFly(node: Element, opts: EdgeFlyParams) {
const createdAt = new Date(opts.created_at).getTime();
const diffNewest = Math.abs(new Date(data.posts[0].created_at).getTime() - createdAt);
const oldest = oldestBeforeLastFetch !== null
? oldestBeforeLastFetch
: new Date(data.posts[data.posts.length - 1].created_at).getTime();
const diffOldest = Math.abs(oldest - createdAt);
const fromTop = diffNewest <= diffOldest;
const rect = node.getBoundingClientRect();
const paramY = +`${opts.y}`;
let offset = isNaN(paramY) ? 0 : paramY + rect.height;
opts.y = fromTop ? -offset : window.innerHeight + offset;
return fly(node, opts);
}
async function fetchPosts(options: FetchOptions): Promise<Post[]> { async function fetchPosts(options: FetchOptions): Promise<Post[]> {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (options?.since !== undefined) { if (options?.since !== undefined) {
@ -60,6 +92,9 @@ function refresh() {
} }
onMount(async () => { onMount(async () => {
if (data.posts.length > 0) {
oldestBeforeLastFetch = new Date(data.posts[data.posts.length - 1].created_at).getTime();
}
interval = setInterval(refresh, refreshInterval); interval = setInterval(refresh, refreshInterval);
// - If the page is hidden, slow down refresh rate // - If the page is hidden, slow down refresh rate
@ -84,6 +119,7 @@ function loadOlderPosts() {
const filter: FetchOptions = { count: 20 }; const filter: FetchOptions = { count: 20 };
if (data.posts.length > 0) { if (data.posts.length > 0) {
filter.before = data.posts[data.posts.length - 1].created_at; filter.before = data.posts[data.posts.length - 1].created_at;
oldestBeforeLastFetch = new Date(filter.before).getTime();
} }
@ -119,7 +155,12 @@ function loadOlderPosts() {
Sorry, no posts recommending music aave been found yet Sorry, no posts recommending music aave been found yet
{/if} {/if}
{#each data.posts as post (post.url)} {#each data.posts as post (post.url)}
<div class="post"><PostComponent {post} /></div> <div
class="post"
transition:edgeFly="{{ y: 10, created_at: post.created_at, duration: 300, easing: cubicInOut }}"
>
<PostComponent {post} />
</div>
{/each} {/each}
<LoadMoreComponent <LoadMoreComponent
on:loadOlderPosts={loadOlderPosts} on:loadOlderPosts={loadOlderPosts}
@ -148,5 +189,6 @@ function loadOlderPosts() {
h2 { h2 {
text-align: center; text-align: center;
z-index: 100;
} }
</style> </style>