aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/routes/+page.svelte282
1 files changed, 220 insertions, 62 deletions
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 268e9713..ad3971d8 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,5 +1,6 @@
<script lang="ts">
import type { Component } from "svelte";
+import { browser } from "$app/environment";
import Spacer from "$lib/Layout/Spacer.svelte";
import { onDestroy, onMount } from "svelte";
import userIdentity from "$stores/identity.js";
@@ -41,6 +42,86 @@ let IndexColumnComponent: IndexColumnSvelteComponent | null = $state(null);
let MangaListTemplateComponent: MangaListTemplateSvelteComponent | null =
$state(null);
let authenticatedHomeSurfaceImport: Promise<void> | null = null;
+let balancedListFlowElement: HTMLDivElement | undefined = $state();
+let balancedListResizeObserver: ResizeObserver | undefined;
+let balancedListMeasurementFrame = 0;
+const balancedListPanelElements = new Map<string, HTMLElement>();
+let balancedListPanelAssignments = $state<Record<string, "left" | "right">>({});
+
+const resetBalancedListLayout = () => {
+ balancedListPanelAssignments = {};
+};
+
+const updateBalancedListLayout = () => {
+ if (!balancedListFlowElement) return;
+
+ const balancedListFlowStyles = getComputedStyle(balancedListFlowElement);
+ const balancedListRowGap = parseFloat(
+ balancedListFlowStyles.getPropertyValue("row-gap"),
+ );
+ const balancedListColumnCount = balancedListFlowStyles
+ .getPropertyValue("grid-template-columns")
+ .split(" ")
+ .filter(Boolean).length;
+
+ if (balancedListColumnCount <= 1 || !Number.isFinite(balancedListRowGap)) {
+ balancedListPanelAssignments = Object.fromEntries(
+ Array.from(balancedListPanelElements.keys()).map((key) => [key, "left"]),
+ );
+
+ return;
+ }
+
+ const balancedListKeys = ["upcoming", "due", "manga"].filter((key) =>
+ balancedListPanelElements.has(key),
+ );
+ const balancedListHeights = { left: 0, right: 0 };
+ const nextBalancedListAssignments: Record<string, "left" | "right"> = {};
+
+ balancedListKeys.forEach((key) => {
+ const balancedListPanel = balancedListPanelElements.get(key);
+ if (!balancedListPanel) return;
+
+ const balancedListColumn =
+ balancedListHeights.left <= balancedListHeights.right ? "left" : "right";
+ const balancedListPanelHeight =
+ balancedListPanel.getBoundingClientRect().height;
+
+ nextBalancedListAssignments[key] = balancedListColumn;
+ balancedListHeights[balancedListColumn] +=
+ balancedListPanelHeight +
+ (balancedListHeights[balancedListColumn] > 0 ? balancedListRowGap : 0);
+ });
+
+ balancedListPanelAssignments = nextBalancedListAssignments;
+};
+
+const queueBalancedListLayout = () => {
+ if (!browser) return;
+
+ cancelAnimationFrame(balancedListMeasurementFrame);
+
+ balancedListMeasurementFrame = requestAnimationFrame(() => {
+ updateBalancedListLayout();
+ });
+};
+
+const observeBalancedListPanel = (panel: HTMLElement, key: string) => {
+ balancedListPanelElements.set(key, panel);
+ balancedListResizeObserver?.observe(panel);
+ queueBalancedListLayout();
+
+ return {
+ destroy: () => {
+ balancedListPanelElements.delete(key);
+ balancedListResizeObserver?.unobserve(panel);
+ queueBalancedListLayout();
+ },
+ };
+};
+
+const isBalancedListPanelInColumn = (key: string, column: "left" | "right") =>
+ (balancedListPanelAssignments[key] ?? "left") === column;
const loadAuthenticatedHomeSurface = () => {
if (data.user === undefined) return null;
@@ -68,10 +149,30 @@ onMount(async () => {
await hydrateStateBin();
$stateBin.upcomingAnimeListOpen ??= true;
$stateBin.dueMangaListOpen ??= true;
+
+ balancedListResizeObserver = new ResizeObserver(() => {
+ queueBalancedListLayout();
+ });
+
+ balancedListPanelElements.forEach((panel) => {
+ balancedListResizeObserver?.observe(panel);
+ });
+
+ window.addEventListener("resize", queueBalancedListLayout);
void loadAuthenticatedHomeSurface();
+
+ queueBalancedListLayout();
});
-onDestroy(() => removeHeightObserver?.());
+onDestroy(() => {
+ removeHeightObserver?.();
+ balancedListResizeObserver?.disconnect();
+ if (browser) {
+ cancelAnimationFrame(balancedListMeasurementFrame);
+ window.removeEventListener("resize", queueBalancedListLayout);
+ }
+ resetBalancedListLayout();
+});
</script>
<HeadTitle />
@@ -87,85 +188,140 @@ onDestroy(() => removeHeightObserver?.());
<Landing />
{:else}
+ {@const balancedListColumnCount =
+ [!$settings.disableUpcomingAnime, !$settings.disableAnime, !$settings.disableManga]
+ .map(Number)
+ .reduce((a, b) => a + b) > 1
+ ? 2
+ : 1}
<div
- class="grid-container"
+ bind:this={balancedListFlowElement}
+ class="balanced-list-flow"
style={`
- grid-template-columns: ${
- [!$settings.disableUpcomingAnime, !$settings.disableAnime, !$settings.disableManga]
- .map(Number)
- .reduce((a, b) => a + b) > 1
- ? '1fr 1fr'
- : '1fr'
- }
+ --balanced-list-columns: ${balancedListColumnCount}
`}
>
- <div class="left-column">
- {#if !$settings.disableUpcomingAnime}
- <details bind:open={$stateBin.upcomingAnimeListOpen} class="list list-upcoming">
- {#if $userIdentity.id !== -2}
- {#if UpcomingAnimeListComponent}
- <UpcomingAnimeListComponent user={data.user} />
+ <div class="balanced-list-column">
+ {#if !$settings.disableUpcomingAnime && isBalancedListPanelInColumn("upcoming", "left")}
+ <div class="balanced-list-panel" use:observeBalancedListPanel={"upcoming"}>
+ <details bind:open={$stateBin.upcomingAnimeListOpen} class="list list-upcoming">
+ {#if $userIdentity.id !== -2}
+ {#if UpcomingAnimeListComponent}
+ <UpcomingAnimeListComponent user={data.user} />
+ {:else}
+ <ListTitle title={$locale().lists.upcoming.episodes} />
+
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
{:else}
<ListTitle title={$locale().lists.upcoming.episodes} />
<Skeleton card={false} count={5} height="0.9rem" list />
{/if}
+ </details>
+ </div>
+ {/if}
+
+ {#if !$settings.disableAnime && isBalancedListPanelInColumn("due", "left")}
+ <div class="balanced-list-panel" use:observeBalancedListPanel={"due"}>
+ {#if IndexColumnComponent}
+ <IndexColumnComponent user={data.user} userIdentity={$userIdentity} />
{:else}
- <ListTitle title={$locale().lists.upcoming.episodes} />
+ <details bind:open={$stateBin.dueAnimeListOpen} class="list list-due">
+ <ListTitle title={$locale().lists.due.episodes} />
- <Skeleton card={false} count={5} height="0.9rem" list />
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ </details>
{/if}
- </details>
+ </div>
{/if}
- {#if !$settings.disableAnime && !$settings.disableManga}
- {#if IndexColumnComponent}
- <IndexColumnComponent user={data.user} userIdentity={$userIdentity} />
- {:else}
- <details bind:open={$stateBin.dueAnimeListOpen} class="list list-due">
- <ListTitle title={$locale().lists.due.episodes} />
+ {#if !$settings.disableManga && isBalancedListPanelInColumn("manga", "left")}
+ <div class="balanced-list-panel" use:observeBalancedListPanel={"manga"}>
+ <details bind:open={$stateBin.dueMangaListOpen} class="list list-manga">
+ {#if $userIdentity.id !== -2}
+ {#if MangaListTemplateComponent}
+ <MangaListTemplateComponent
+ user={data.user}
+ displayUnresolved={$settings.displayUnresolved}
+ due={true}
+ />
+ {:else}
+ <ListTitle title={$locale().lists.due.mangaAndLightNovels} />
- <Skeleton card={false} count={5} height="0.9rem" list />
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
+ {:else}
+ <ListTitle title={$locale().lists.due.mangaAndLightNovels} />
+
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
</details>
- {/if}
+ </div>
{/if}
</div>
- <div class="right-column">
- {#if !$settings.disableAnime && $settings.disableManga}
- {#if IndexColumnComponent}
- <IndexColumnComponent user={data.user} userIdentity={$userIdentity} />
- {:else}
- <details bind:open={$stateBin.dueAnimeListOpen} class="list list-due">
- <ListTitle title={$locale().lists.due.episodes} />
+ {#if balancedListColumnCount > 1}
+ <div class="balanced-list-column">
+ {#if !$settings.disableUpcomingAnime && isBalancedListPanelInColumn("upcoming", "right")}
+ <div class="balanced-list-panel" use:observeBalancedListPanel={"upcoming"}>
+ <details bind:open={$stateBin.upcomingAnimeListOpen} class="list list-upcoming">
+ {#if $userIdentity.id !== -2}
+ {#if UpcomingAnimeListComponent}
+ <UpcomingAnimeListComponent user={data.user} />
+ {:else}
+ <ListTitle title={$locale().lists.upcoming.episodes} />
- <Skeleton card={false} count={5} height="0.9rem" list />
- </details>
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
+ {:else}
+ <ListTitle title={$locale().lists.upcoming.episodes} />
+
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
+ </details>
+ </div>
{/if}
- {/if}
- {#if !$settings.disableManga}
- <details bind:open={$stateBin.dueMangaListOpen} class="list list-manga">
- {#if $userIdentity.id !== -2}
- {#if MangaListTemplateComponent}
- <MangaListTemplateComponent
- user={data.user}
- displayUnresolved={$settings.displayUnresolved}
- due={true}
- />
+ {#if !$settings.disableAnime && isBalancedListPanelInColumn("due", "right")}
+ <div class="balanced-list-panel" use:observeBalancedListPanel={"due"}>
+ {#if IndexColumnComponent}
+ <IndexColumnComponent user={data.user} userIdentity={$userIdentity} />
{:else}
- <ListTitle title={$locale().lists.due.mangaAndLightNovels} />
+ <details bind:open={$stateBin.dueAnimeListOpen} class="list list-due">
+ <ListTitle title={$locale().lists.due.episodes} />
- <Skeleton card={false} count={5} height="0.9rem" list />
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ </details>
{/if}
- {:else}
- <ListTitle title={$locale().lists.due.mangaAndLightNovels} />
+ </div>
+ {/if}
- <Skeleton card={false} count={5} height="0.9rem" list />
- {/if}
- </details>
- {/if}
- </div>
+ {#if !$settings.disableManga && isBalancedListPanelInColumn("manga", "right")}
+ <div class="balanced-list-panel" use:observeBalancedListPanel={"manga"}>
+ <details bind:open={$stateBin.dueMangaListOpen} class="list list-manga">
+ {#if $userIdentity.id !== -2}
+ {#if MangaListTemplateComponent}
+ <MangaListTemplateComponent
+ user={data.user}
+ displayUnresolved={$settings.displayUnresolved}
+ due={true}
+ />
+ {:else}
+ <ListTitle title={$locale().lists.due.mangaAndLightNovels} />
+
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
+ {:else}
+ <ListTitle title={$locale().lists.due.mangaAndLightNovels} />
+
+ <Skeleton card={false} count={5} height="0.9rem" list />
+ {/if}
+ </details>
+ </div>
+ {/if}
+ </div>
+ {/if}
{#if $settings.disableUpcomingAnime && $settings.disableAnime && $settings.disableManga}
<video src="https://video.twimg.com/tweet_video/Do_eDPnX0AAKV9f.mp4" autoplay loop>
@@ -176,30 +332,32 @@ onDestroy(() => removeHeightObserver?.());
{/if}
<style>
- .grid-container {
+ .balanced-list-flow {
display: grid;
+ grid-template-columns: repeat(var(--balanced-list-columns), minmax(0, 1fr));
gap: 1rem;
+ align-items: start;
}
- .left-column {
+ .balanced-list-column {
display: grid;
gap: 1rem;
align-content: start;
+ min-width: 0;
}
- .right-column {
- align-self: start;
+ .balanced-list-panel {
+ min-width: 0;
}
.list {
overflow-y: auto;
- break-inside: avoid;
- page-break-inside: avoid;
+ margin: 0;
}
@media (max-width: 800px) {
- .grid-container {
- grid-template-columns: 1fr !important;
+ .balanced-list-flow {
+ grid-template-columns: 1fr;
}
}
</style>