Skip to content

Commit

Permalink
Merge pull request #96 from WhyAsh5114:migrate-to-svelte-infinite-loa…
Browse files Browse the repository at this point in the history
…ding

fix: Migrate to svelte-infinite-loading
  • Loading branch information
WhyAsh5114 authored Sep 17, 2024
2 parents 140e53c + 062be27 commit 78e84e4
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 317 deletions.
16 changes: 7 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"mode-watcher": "^0.4.0",
"paneforge": "^0.0.5",
"posthog-js": "^1.160.3",
"svelte-infinite": "^0.3.2",
"svelte-infinite-loading": "^1.4.0",
"svelte-sonner": "^0.3.26",
"tailwind-merge": "^2.4.0",
"tailwind-variants": "^0.2.1",
Expand Down
25 changes: 25 additions & 0 deletions src/lib/components/DefaultInfiniteLoader.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script lang="ts">
import Separator from '$lib/components/ui/separator/separator.svelte';
import InfiniteLoading, { type InfiniteEvent } from 'svelte-infinite-loading';
import LoaderCircle from 'virtual:icons/lucide/loader-circle';
type PropsType = {
loadMore: (event: InfiniteEvent) => Promise<void>;
identifier: unknown;
entityPlural: string;
};
let { loadMore, identifier, entityPlural }: PropsType = $props();
</script>

<InfiniteLoading on:infinite={loadMore} {identifier}>
<div class="flex items-center justify-center gap-2 py-2 text-muted-foreground" slot="noMore">
<Separator class="w-20" />
That's all
<Separator class="w-20" />
</div>
<div class="muted-text-box text-left" slot="noResults">No {entityPlural} found</div>
<div slot="spinner">
<LoaderCircle class="mx-auto my-2 animate-spin" />
</div>
</InfiniteLoading>
11 changes: 0 additions & 11 deletions src/routes/exercise-splits/+page.server.ts

This file was deleted.

101 changes: 38 additions & 63 deletions src/routes/exercise-splits/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,52 +1,50 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { Badge } from '$lib/components/ui/badge/index.js';
import Button from '$lib/components/ui/button/button.svelte';
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
import { Input } from '$lib/components/ui/input';
import H2 from '$lib/components/ui/typography/H2.svelte';
import { trpc } from '$lib/trpc/client';
import type { RouterOutputs } from '$lib/trpc/router.js';
import type { InfiniteEvent } from 'svelte-infinite-loading';
import AddIcon from 'virtual:icons/lucide/plus';
import SearchIcon from 'virtual:icons/lucide/search';
import LoaderCircle from 'virtual:icons/lucide/loader-circle';
import { afterNavigate, goto } from '$app/navigation';
import { trpc } from '$lib/trpc/client';
import { InfiniteLoader, loaderState } from 'svelte-infinite';
import { page } from '$app/stores';
import type { ExerciseSplit, ExerciseSplitDay } from '@prisma/client';
import { Badge } from '$lib/components/ui/badge/index.js';
import { Skeleton } from '$lib/components/ui/skeleton/index.js';
import Separator from '$lib/components/ui/separator/separator.svelte';
import DefaultInfiniteLoader from '../../lib/components/DefaultInfiniteLoader.svelte';
import { exerciseSplitRunes } from './manage/exerciseSplitRunes.svelte.js';
type ExerciseSplitsWithSplitDays = (ExerciseSplit & { exerciseSplitDays: ExerciseSplitDay[] })[];
let { data } = $props();
let exerciseSplits: ExerciseSplitsWithSplitDays | 'loading' = $state('loading');
let exerciseSplits: RouterOutputs['exerciseSplits']['load'] = $state([]);
let searchString = $state($page.url.searchParams.get('search') ?? '');
afterNavigate(async () => {
loaderState.reset();
exerciseSplits = await data.exerciseSplits;
if (exerciseSplits.length !== 10) loaderState.complete();
});
function updateSearchParam(e: Event) {
e.preventDefault();
const url = new URL($page.url);
if (searchString === (url.searchParams.get('search') ?? '')) return;
if (searchString) url.searchParams.set('search', searchString);
else url.searchParams.delete('search');
exerciseSplits = 'loading';
exerciseSplits = [];
goto(url);
}
async function loadMore() {
async function loadMore(infiniteEvent: InfiniteEvent) {
const lastExerciseSplit = exerciseSplits.at(-1);
if (typeof lastExerciseSplit === 'string' || lastExerciseSplit === undefined) return;
const newExerciseSplits = await trpc().exerciseSplits.load.query({
cursorId: lastExerciseSplit.id
cursorId: lastExerciseSplit?.id,
searchString
});
if (exerciseSplits !== 'loading') exerciseSplits.push(...newExerciseSplits);
if (newExerciseSplits.length !== 10) loaderState.complete();
if (newExerciseSplits.length === 0) {
infiniteEvent.detail.complete();
return;
}
infiniteEvent.detail.loaded();
exerciseSplits.push(...newExerciseSplits);
if (newExerciseSplits.length < 10) infiniteEvent.detail.complete();
}
function createNewExerciseSplit() {
Expand Down Expand Up @@ -78,43 +76,20 @@
</DropdownMenu.Root>
</div>
<div class="flex h-px grow flex-col gap-1 overflow-y-auto">
{#if exerciseSplits === 'loading'}
{#each Array(10) as _}
<div class="flex h-12 items-center justify-between rounded-md border bg-card p-2">
<Skeleton class="text-lg-skeleton" />
<Skeleton class="badge-skeleton" />
</div>
{/each}
{:else}
<InfiniteLoader triggerLoad={loadMore}>
{#each exerciseSplits as exerciseSplit}
<Button
class="mb-1 flex h-12 items-center justify-between rounded-md border bg-card p-2"
href="/exercise-splits/{exerciseSplit.id}"
variant="outline"
>
<span class="truncate text-lg font-semibold">{exerciseSplit.name}</span>
<Badge>{exerciseSplit.exerciseSplitDays.length} days / cycle</Badge>
</Button>
{:else}
<div class="muted-text-box">No exercise splits found</div>
{/each}
{#snippet loading()}
<LoaderCircle class="animate-spin" />
{/snippet}
{#snippet error(load)}
<Button onclick={load} variant="outline">An error occurred. Retry?</Button>
{/snippet}
{#snippet noData()}
{#if exerciseSplits.length > 0}
<div class="flex items-center justify-start gap-2 font-semibold text-muted-foreground">
<Separator class="h-0.5 w-20" />
<span class="whitespace-nowrap">That's all!</span>
<Separator class="h-0.5 w-20" />
</div>
{/if}
{/snippet}
</InfiniteLoader>
{/if}
{#each exerciseSplits as exerciseSplit}
<Button
class="flex h-12 items-center justify-between rounded-md border bg-card p-2"
href="/exercise-splits/{exerciseSplit.id}"
variant="outline"
>
<span class="truncate text-lg font-semibold">{exerciseSplit.name}</span>
<Badge>{exerciseSplit.exerciseSplitDays.length} days / cycle</Badge>
</Button>
{/each}
<DefaultInfiniteLoader
{loadMore}
identifier={$page.url.searchParams.get('search')}
entityPlural="exercise splits"
/>
</div>
</div>
7 changes: 1 addition & 6 deletions src/routes/mesocycles/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,8 @@ import { createContext } from '$lib/trpc/context';
import { createCaller } from '$lib/trpc/router';

export const load = async (event) => {
event.depends('mesocycles:all');
event.depends('mesocycles:active');
const trpc = createCaller(await createContext(event));
const searchString = event.url.searchParams.get('search') ?? undefined;

return {
mesocycles: trpc.mesocycles.load({ searchString }),
activeMesocycle: trpc.mesocycles.findActiveMesocycle()
};
return { activeMesocycle: trpc.mesocycles.findActiveMesocycle() };
};
103 changes: 41 additions & 62 deletions src/routes/mesocycles/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,48 +1,54 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import DefaultInfiniteLoader from '$lib/components/DefaultInfiniteLoader.svelte';
import { Badge } from '$lib/components/ui/badge/index.js';
import Button from '$lib/components/ui/button/button.svelte';
import { Input } from '$lib/components/ui/input';
import H2 from '$lib/components/ui/typography/H2.svelte';
import LoaderCircle from 'virtual:icons/lucide/loader-circle';
import AddIcon from 'virtual:icons/lucide/plus';
import SearchIcon from 'virtual:icons/lucide/search';
import { afterNavigate, goto } from '$app/navigation';
import { page } from '$app/stores';
import Separator from '$lib/components/ui/separator/separator.svelte';
import { Skeleton } from '$lib/components/ui/skeleton/index.js';
import H2 from '$lib/components/ui/typography/H2.svelte';
import { trpc } from '$lib/trpc/client.js';
import type { Mesocycle } from '@prisma/client';
import { InfiniteLoader, loaderState } from 'svelte-infinite';
import { Badge } from '$lib/components/ui/badge/index.js';
import { onMount } from 'svelte';
import type { InfiniteEvent } from 'svelte-infinite-loading';
import AddIcon from 'virtual:icons/lucide/plus';
import SearchIcon from 'virtual:icons/lucide/search';
import { mesocycleRunes } from './manage/mesocycleRunes.svelte.js';
let { data } = $props();
let activeMesocycle: Pick<Mesocycle, 'id' | 'name'> | null | 'loading' = $state('loading');
let mesocycles: Mesocycle[] | 'loading' = $state('loading');
let mesocycles: Mesocycle[] = $state([]);
let searchString = $state($page.url.searchParams.get('search') ?? '');
afterNavigate(async () => {
loaderState.reset();
[activeMesocycle, mesocycles] = await Promise.all([data.activeMesocycle, data.mesocycles]);
if (mesocycles.length !== 10) loaderState.complete();
onMount(async () => {
activeMesocycle = await data.activeMesocycle;
});
function updateSearchParam(e: Event) {
e.preventDefault();
const url = new URL($page.url);
if (searchString === (url.searchParams.get('search') ?? '')) return;
if (searchString) url.searchParams.set('search', searchString);
else url.searchParams.delete('search');
mesocycles = 'loading';
mesocycles = [];
goto(url);
}
async function loadMore() {
async function loadMore(infiniteEvent: InfiniteEvent) {
const lastMesocycle = mesocycles.at(-1);
if (typeof lastMesocycle === 'string' || lastMesocycle === undefined) return;
const newMesocycles = await trpc().mesocycles.load.query({ cursorId: lastMesocycle?.id, searchString });
if (newMesocycles.length === 0) {
infiniteEvent.detail.complete();
return;
}
const newMesocycles = await trpc().mesocycles.load.query({ cursorId: lastMesocycle.id });
if (mesocycles !== 'loading') mesocycles.push(...newMesocycles);
if (newMesocycles.length !== 10) loaderState.complete();
infiniteEvent.detail.loaded();
mesocycles.push(...newMesocycles);
if (newMesocycles.length < 10) infiniteEvent.detail.complete();
}
function createNewMesocycle() {
Expand Down Expand Up @@ -92,49 +98,22 @@
<Separator class="w-px grow" />
</div>
<div class="flex h-px grow flex-col gap-1 overflow-y-auto">
{#if mesocycles === 'loading'}
{#each Array(10) as _}
<div class="flex h-12 items-center justify-between rounded-md border bg-card p-2">
<Skeleton class="text-lg-skeleton" />
<Skeleton class="badge-skeleton" />
</div>
{/each}
{:else}
<InfiniteLoader triggerLoad={loadMore}>
{#each mesocycles as mesocycle}
<Button
class="mb-1 flex h-12 items-center justify-between rounded-md border bg-card p-2"
href="/mesocycles/{mesocycle.id}"
variant="outline"
>
<span class="text-lg font-semibold">{mesocycle.name}</span>
{#if !mesocycle.startDate}
<Badge variant="secondary">Unused</Badge>
{:else if !mesocycle.endDate}
<Badge>Active</Badge>
{:else}
<Badge variant="outline">Completed</Badge>
{/if}
</Button>
{#each mesocycles as mesocycle}
<Button
class="flex h-12 items-center justify-between rounded-md border bg-card p-2"
href="/mesocycles/{mesocycle.id}"
variant="outline"
>
<span class="text-lg font-semibold">{mesocycle.name}</span>
{#if !mesocycle.startDate}
<Badge variant="secondary">Unused</Badge>
{:else if !mesocycle.endDate}
<Badge>Active</Badge>
{:else}
<div class="muted-text-box">No mesocycles found</div>
{/each}
{#snippet loading()}
<LoaderCircle class="animate-spin" />
{/snippet}
{#snippet error(load)}
<Button onclick={load} variant="outline">An error occurred. Retry?</Button>
{/snippet}
{#snippet noData()}
{#if mesocycles.length > 0}
<div class="flex items-center justify-start gap-2 font-semibold text-muted-foreground">
<Separator class="h-0.5 w-20" />
<span class="whitespace-nowrap">That's all!</span>
<Separator class="h-0.5 w-20" />
</div>
{/if}
{/snippet}
</InfiniteLoader>
{/if}
<Badge variant="outline">Completed</Badge>
{/if}
</Button>
{/each}
<DefaultInfiniteLoader {loadMore} identifier={$page.url.searchParams.get('search')} entityPlural="mesocycles" />
</div>
</div>
Loading

0 comments on commit 78e84e4

Please sign in to comment.