feat: client side loading

This commit is contained in:
max_richter 2025-01-20 23:37:03 +01:00
parent 463141981b
commit 19a1344d3d
7 changed files with 75 additions and 15 deletions

View File

@ -2,6 +2,7 @@ import { isYoutubeLink } from "@lib/string.ts";
import { IconBrandYoutube } from "@components/icons.tsx"; import { IconBrandYoutube } from "@components/icons.tsx";
import { GenericResource } from "@lib/types.ts"; import { GenericResource } from "@lib/types.ts";
import { SmallRating } from "@components/Rating.tsx"; import { SmallRating } from "@components/Rating.tsx";
import { Link } from "@islands/Link.tsx";
export function Card( export function Card(
{ {
@ -22,7 +23,7 @@ export function Card(
rating?: number; rating?: number;
}, },
) { ) {
const backgroundStyle = { const backgroundStyle: preact.JSX.CSSProperties = {
backgroundSize: "cover", backgroundSize: "cover",
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
}; };
@ -34,7 +35,7 @@ export function Card(
} }
return ( return (
<a <Link
href={link} href={link}
style={backgroundStyle} style={backgroundStyle}
data-thumb={thumbnail} data-thumb={thumbnail}
@ -88,7 +89,7 @@ export function Card(
)} )}
</div> </div>
<div class="absolute inset-x-0 bottom-0 h-3/4" /> <div class="absolute inset-x-0 bottom-0 h-3/4" />
</a> </Link>
); );
} }

View File

@ -57,6 +57,7 @@ import * as $KMenu_commands_create_recipe from "./islands/KMenu/commands/create_
import * as $KMenu_commands_create_recommendations from "./islands/KMenu/commands/create_recommendations.ts"; import * as $KMenu_commands_create_recommendations from "./islands/KMenu/commands/create_recommendations.ts";
import * as $KMenu_commands_create_series from "./islands/KMenu/commands/create_series.ts"; import * as $KMenu_commands_create_series from "./islands/KMenu/commands/create_series.ts";
import * as $KMenu_types from "./islands/KMenu/types.ts"; import * as $KMenu_types from "./islands/KMenu/types.ts";
import * as $Link from "./islands/Link.tsx";
import * as $Recommendations from "./islands/Recommendations.tsx"; import * as $Recommendations from "./islands/Recommendations.tsx";
import * as $Search from "./islands/Search.tsx"; import * as $Search from "./islands/Search.tsx";
import type { Manifest } from "$fresh/server.ts"; import type { Manifest } from "$fresh/server.ts";
@ -125,6 +126,7 @@ const manifest = {
$KMenu_commands_create_recommendations, $KMenu_commands_create_recommendations,
"./islands/KMenu/commands/create_series.ts": $KMenu_commands_create_series, "./islands/KMenu/commands/create_series.ts": $KMenu_commands_create_series,
"./islands/KMenu/types.ts": $KMenu_types, "./islands/KMenu/types.ts": $KMenu_types,
"./islands/Link.tsx": $Link,
"./islands/Recommendations.tsx": $Recommendations, "./islands/Recommendations.tsx": $Recommendations,
"./islands/Search.tsx": $Search, "./islands/Search.tsx": $Search,
}, },

42
islands/Link.tsx Normal file
View File

@ -0,0 +1,42 @@
import { useEffect } from "preact/hooks";
declare global {
// deno-lint-ignore no-var
var loadingTimeout: ReturnType<typeof setTimeout>;
}
export function Link(
{ href, children, class: _class, style }: {
href?: string;
class?: string;
style?: preact.JSX.CSSProperties;
children: preact.ComponentChildren;
},
) {
function handleClick() {
if (globalThis.loadingTimeout) {
return;
}
globalThis.loadingTimeout = setTimeout(() => {
document.querySelector("main")?.classList.add("loading");
}, 100);
}
useEffect(() => {
clearTimeout(globalThis.loadingTimeout);
setTimeout(() => {
document.querySelector("main")?.classList.remove("loading");
}, 100);
});
return (
<a
href={href}
style={style}
onClick={handleClick}
class={_class}
>
{children}
</a>
);
}

View File

@ -1,14 +1,15 @@
import { PageProps } from "$fresh/server.ts"; import { PageProps } from "$fresh/server.ts";
import { Partial } from "$fresh/runtime.ts";
import { useEffect } from "preact/hooks";
import { Head } from "$fresh/runtime.ts";
export default function App({ Component }: PageProps) { export default function App({ Component }: PageProps) {
const globalCss = Deno const globalCss = Deno
.readTextFileSync("./static/global.css") .readTextFileSync("./static/global.css")
.replaceAll("\n", ""); .replaceAll("\n", "");
return ( return (
<> <html>
<Head> <head>
<link rel="stylesheet" href="/prism-material-dark.css" /> <link rel="stylesheet" href="/prism-material-dark.css" />
<link rel="stylesheet" href="/styles.css" /> <link rel="stylesheet" href="/styles.css" />
<link <link
@ -21,9 +22,13 @@ export default function App({ Component }: PageProps) {
<meta name="theme-color" content="#141218" /> <meta name="theme-color" content="#141218" />
<style>{globalCss}</style> <style>{globalCss}</style>
<title>Memorium</title> <title>Memorium</title>
</Head> </head>
<Component /> <body f-client-nav>
<Partial name="body">
<Component />
</Partial>
</body>
<script src="/thumbnails.js" type="module" async defer /> <script src="/thumbnails.js" type="module" async defer />
</> </html>
); );
} }

View File

@ -1,5 +1,6 @@
import { PageProps } from "$fresh/server.ts"; import { PageProps } from "$fresh/server.ts";
import { resources } from "@lib/resources.ts"; import { resources } from "@lib/resources.ts";
import { Link } from "@islands/Link.tsx";
import { Emoji } from "@components/Emoji.tsx"; import { Emoji } from "@components/Emoji.tsx";
export default function MyLayout({ Component }: PageProps) { export default function MyLayout({ Component }: PageProps) {
@ -12,12 +13,12 @@ export default function MyLayout({ Component }: PageProps) {
<nav class="min-h-fit rounded-3xl p-3 grid gap-3 fixed t-0"> <nav class="min-h-fit rounded-3xl p-3 grid gap-3 fixed t-0">
{Object.values(resources).map((m) => { {Object.values(resources).map((m) => {
return ( return (
<a <Link
href={m.link} href={m.link}
class={`flex items-center gap-2 text-white data-[current]:bg-white data-[current]:text-black p-3 text-xl w-full rounded-2xl`} class="flex items-center gap-2 text-white data-[current]:bg-white data-[current]:text-black p-3 text-xl w-full rounded-2xl"
> >
{<Emoji class="w-6 h-6" name={m.emoji} />} {m.name} <Emoji class="w-6 h-6" name={m.emoji} /> {m.name}
</a> </Link>
); );
})} })}
</nav> </nav>

View File

@ -8,6 +8,7 @@ import { RedirectSearchHandler } from "@islands/Search.tsx";
import { parseResourceUrl, searchResource } from "@lib/search.ts"; import { parseResourceUrl, searchResource } from "@lib/search.ts";
import { GenericResource } from "@lib/types.ts"; import { GenericResource } from "@lib/types.ts";
import { ResourceCard } from "@components/Card.tsx"; import { ResourceCard } from "@components/Card.tsx";
import { Link } from "@islands/Link.tsx";
export const handler: Handlers< export const handler: Handlers<
{ articles: Article[] | null; searchResults?: GenericResource[] } { articles: Article[] | null; searchResults?: GenericResource[] }
@ -35,13 +36,13 @@ export default function Greet(
searchResults={searchResults} searchResults={searchResults}
> >
<header class="flex gap-4 items-center mb-5 md:hidden"> <header class="flex gap-4 items-center mb-5 md:hidden">
<a <Link
class="px-4 ml-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex items-center gap-1" class="px-4 ml-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex items-center gap-1"
href="/" href="/"
> >
<IconArrowLeft class="w-5 h-5" /> <IconArrowLeft class="w-5 h-5" />
Back Back
</a> </Link>
<h3 class="text-2xl text-white font-light">📝 Articles</h3> <h3 class="text-2xl text-white font-light">📝 Articles</h3>
</header> </header>

View File

@ -120,3 +120,11 @@ input[type=number] {
.list-decimal li::marker { .list-decimal li::marker {
color: #8a898c; color: #8a898c;
} }
main {
transition: opacity 0.3s;
}
main.loading {
opacity: 0;
}