Improve layout for smaller devices
This commit is contained in:
parent
e3c15be31c
commit
268128c2f4
@ -4,24 +4,28 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<div>
|
<div>
|
||||||
<span>Made with 🤘 by </span>
|
<span class="label"
|
||||||
<a href="https://metalhead.club/@aymm" rel="me">@aymm@metalhead.club</a>
|
>Made<span class="secretIngredient"> with 🤘</span> by </span
|
||||||
</div>
|
>
|
||||||
|
|
<a href="https://metalhead.club/@aymm" rel="me"
|
||||||
<div>
|
>@aymm<span class="mastodonInstance">@metalhead.club</span></a
|
||||||
<a href="https://phlaym.net/git/phlaym/moshing-mammut">
|
>
|
||||||
<img alt="Git branch" src={git} class="icon" />
|
</div>
|
||||||
Source Code
|
|
|
||||||
</a>
|
<div>
|
||||||
</div>
|
<a href="https://phlaym.net/git/phlaym/moshing-mammut">
|
||||||
|
|
<img alt="Git branch" src={git} class="icon" />
|
||||||
<div>
|
<span class="label">Source Code</span>
|
||||||
<a href="/feed.xml">
|
</a>
|
||||||
<img alt="RSS" src={rss} class="icon" />
|
</div>
|
||||||
RSS Feed
|
|
|
||||||
</a>
|
<div>
|
||||||
</div>
|
<a href="/feed.xml">
|
||||||
|
<img alt="RSS" src={rss} class="icon" />
|
||||||
|
<span class="label">RSS<span class="feedSuffix"> Feed</span></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@ -53,4 +57,27 @@
|
|||||||
background-color: var(--color-grey-translucent);
|
background-color: var(--color-grey-translucent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
@media only screen and (max-device-width: 620px) {
|
||||||
|
.mastodonInstance,
|
||||||
|
.feedSuffix {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-device-width: 430px) {
|
||||||
|
.mastodonInstance,
|
||||||
|
.feedSuffix,
|
||||||
|
.secretIngredient {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-device-width: 370px) {
|
||||||
|
.label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FooterComponent from '$lib/components/FooterComponent.svelte'
|
import FooterComponent from '$lib/components/FooterComponent.svelte';
|
||||||
import { SvelteToast } from '@zerodevx/svelte-toast';
|
import { SvelteToast } from '@zerodevx/svelte-toast';
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
@ -7,6 +7,7 @@
|
|||||||
classes: ['toast']
|
classes: ['toast']
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<slot />
|
<slot />
|
||||||
<SvelteToast {options} />
|
<SvelteToast {options} />
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
@ -33,4 +34,9 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
</style>
|
@media only screen and (max-device-width: 620px) {
|
||||||
|
.footer {
|
||||||
|
width: calc(100% + 16px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -1,174 +1,184 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from "svelte";
|
import { onMount } from 'svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import type { Post } from '$lib/mastodon/response';
|
import type { Post } from '$lib/mastodon/response';
|
||||||
import { PUBLIC_REFRESH_INTERVAL, PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME } from '$env/static/public';
|
import {
|
||||||
import PostComponent from '$lib/components/PostComponent.svelte';
|
PUBLIC_REFRESH_INTERVAL,
|
||||||
import LoadMoreComponent from '$lib/components/LoadMoreComponent.svelte';
|
PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME
|
||||||
import { fly, type FlyParams } from 'svelte/transition';
|
} from '$env/static/public';
|
||||||
import { cubicInOut } from 'svelte/easing';
|
import PostComponent from '$lib/components/PostComponent.svelte';
|
||||||
import { errorToast } from '$lib/errorToast'
|
import LoadMoreComponent from '$lib/components/LoadMoreComponent.svelte';
|
||||||
|
import { fly, type FlyParams } from 'svelte/transition';
|
||||||
|
import { cubicInOut } from 'svelte/easing';
|
||||||
|
import { errorToast } from '$lib/errorToast';
|
||||||
|
|
||||||
|
export let data: PageData;
|
||||||
|
|
||||||
export let data: PageData;
|
interface FetchOptions {
|
||||||
|
since?: string;
|
||||||
interface FetchOptions {
|
before?: string;
|
||||||
since?: string,
|
count?: number;
|
||||||
before?: string,
|
|
||||||
count?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EdgeFlyParams extends FlyParams {
|
|
||||||
created_at: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshInterval = parseInt(PUBLIC_REFRESH_INTERVAL);
|
|
||||||
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[]> {
|
|
||||||
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}`);
|
interface EdgeFlyParams extends FlyParams {
|
||||||
return await response.json();
|
created_at: string;
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
const refreshInterval = parseInt(PUBLIC_REFRESH_INTERVAL);
|
||||||
// Prepend new posts, filter dupes
|
let interval: NodeJS.Timer | null = null;
|
||||||
// There shouldn't be any duplicates, but better be safe than sorry
|
let moreOlderPostsAvailable = true;
|
||||||
data.posts = filterDuplicates(resp.concat(data.posts));
|
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[]> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (options?.since !== undefined) {
|
||||||
|
params.set('since', options.since);
|
||||||
}
|
}
|
||||||
})
|
if (options?.before !== undefined) {
|
||||||
.catch((e: Error) => {
|
params.set('before', options.before);
|
||||||
errorToast('Error loading newest posts: ' + e.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
if (data.posts.length > 0) {
|
|
||||||
oldestBeforeLastFetch = new Date(data.posts[data.posts.length - 1].created_at).getTime();
|
|
||||||
}
|
|
||||||
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);
|
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: Error) => {
|
||||||
|
errorToast('Error loading newest posts: ' + e.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
if (data.posts.length > 0) {
|
||||||
|
oldestBeforeLastFetch = new Date(data.posts[data.posts.length - 1].created_at).getTime();
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
function loadOlderPosts() {
|
||||||
if (interval !== null) {
|
loadingOlderPosts = true;
|
||||||
clearInterval(interval)
|
const filter: FetchOptions = { count: 20 };
|
||||||
|
if (data.posts.length > 0) {
|
||||||
|
const before = data.posts[data.posts.length - 1].created_at;
|
||||||
|
filter.before = before;
|
||||||
|
oldestBeforeLastFetch = new Date(before).getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
errorToast('Error loading older posts: ' + e.message);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
function loadOlderPosts() {
|
|
||||||
loadingOlderPosts = true;
|
|
||||||
const filter: FetchOptions = { count: 20 };
|
|
||||||
if (data.posts.length > 0) {
|
|
||||||
const before = data.posts[data.posts.length - 1].created_at;
|
|
||||||
filter.before = before;
|
|
||||||
oldestBeforeLastFetch = new Date(before).getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
errorToast('Error loading older posts: ' + e.message);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>{PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME} music list</title>
|
<title>{PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME} music list</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<h2>{PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME} music list</h2>
|
<h2>{PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME} music list</h2>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div></div>
|
<div />
|
||||||
<div class="posts">
|
<div class="posts">
|
||||||
{#if data.posts.length === 0}
|
{#if data.posts.length === 0}
|
||||||
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
|
<div
|
||||||
class="post"
|
class="post"
|
||||||
transition:edgeFly="{{ y: 10, created_at: post.created_at, duration: 300, easing: cubicInOut }}"
|
transition:edgeFly={{
|
||||||
|
y: 10,
|
||||||
|
created_at: post.created_at,
|
||||||
|
duration: 300,
|
||||||
|
easing: cubicInOut
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<PostComponent {post} />
|
<PostComponent {post} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<LoadMoreComponent
|
<LoadMoreComponent
|
||||||
on:loadOlderPosts={loadOlderPosts}
|
on:loadOlderPosts={loadOlderPosts}
|
||||||
moreAvailable={moreOlderPostsAvailable}
|
moreAvailable={moreOlderPostsAvailable}
|
||||||
isLoading={loadingOlderPosts}/>
|
isLoading={loadingOlderPosts}
|
||||||
</div>
|
/>
|
||||||
<div></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div />
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.posts {
|
.posts {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -191,4 +201,10 @@ function loadOlderPosts() {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
@media only screen and (max-device-width: 650px) {
|
||||||
|
.post {
|
||||||
|
max-width: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user