Implement loading on scroll for patch second column

This commit is contained in:
Rūdolfs Ošiņš
2024-11-15 10:05:26 +01:00
parent ff8fca8dd7
commit 9ff061793d
6 changed files with 103 additions and 38 deletions
+17 -16
View File
@@ -9,33 +9,29 @@
patchStatusColor,
} from "@app/lib/utils";
import { invoke } from "@app/lib/invoke";
import { onMount } from "svelte";
import { push } from "@app/lib/router";
import DiffStatBadge from "./DiffStatBadge.svelte";
import Icon from "./Icon.svelte";
import Id from "./Id.svelte";
import InlineTitle from "./InlineTitle.svelte";
import NodeId from "./NodeId.svelte";
import Id from "./Id.svelte";
let stats: Stats | undefined = $state(undefined);
onMount(async () => {
stats = await invoke<Stats>("diff_stats", {
rid,
base: patch.base,
head: patch.head,
});
});
interface Props {
patch: Patch;
rid: string;
selected?: boolean;
compact?: boolean;
loadPatch?: (rid: string, patchId: string) => void;
}
const { patch, rid, selected = false, compact = false }: Props = $props();
const {
patch,
rid,
selected = false,
compact = false,
loadPatch,
}: Props = $props();
</script>
<style>
@@ -79,7 +75,11 @@
class:selected
class="patch-teaser"
onclick={() => {
void push({ resource: "repo.patch", rid, patch: patch.id });
if (loadPatch) {
loadPatch(rid, patch.id);
} else {
void push({ resource: "repo.patch", rid, patch: patch.id });
}
}}>
<div class="global-flex">
<div
@@ -104,9 +104,10 @@
{#if !compact}
<div class="global-flex">
{#if stats}
{#await invoke<Stats>( "diff_stats", { rid, base: patch.base, head: patch.head }, ) then stats}
<DiffStatBadge {stats} />
{/if}
{/await}
{#each patch.labels as label}
<div class="global-counter txt-small">{label}</div>
{/each}
+1
View File
@@ -71,6 +71,7 @@
{#if activeTab === "patches"}
<Border
styleCursor="pointer"
onclick={() => {
void router.push({
resource: "repo.patches",
+36 -14
View File
@@ -13,7 +13,8 @@
headerCenter?: Snippet;
secondColumn: Snippet;
sidebar: Snippet;
loadMore?: () => Promise<void>;
loadMoreContent?: () => Promise<void>;
loadMoreSecondColumn?: () => Promise<void>;
hideSidebar?: boolean;
styleSecondColumnOverflow?: string;
}
@@ -24,7 +25,8 @@
headerCenter = undefined,
secondColumn,
sidebar,
loadMore = undefined,
loadMoreContent = undefined,
loadMoreSecondColumn = undefined,
hideSidebar = false,
styleSecondColumnOverflow = "scroll",
}: Props = $props();
@@ -34,8 +36,10 @@
let oneColumnLayout = $state(
localStorage ? localStorage.getItem(LAYOUT_KEY) === "one-column" : false,
);
let listElement: HTMLElement | undefined = $state();
let loading = false;
let contentContainer: HTMLElement | undefined = $state();
let secondColumnContainer: HTMLElement | undefined = $state();
let loadingContent = false;
let loadingSecondColumn = false;
function storeLayout(newValue: LayoutState): void {
oneColumnLayout = newValue === "one-column";
@@ -49,16 +53,33 @@
}
onMount(() => {
if (listElement && loadMore) {
listElement.addEventListener("scroll", async () => {
if (contentContainer && loadMoreContent) {
contentContainer.addEventListener("scroll", async () => {
if (
listElement &&
listElement.scrollTop + listElement.clientHeight >=
listElement.scrollHeight - 600 &&
loading === false
contentContainer &&
contentContainer.scrollTop + contentContainer.clientHeight >=
contentContainer.scrollHeight - 600 &&
loadingContent === false
) {
loading = true;
void loadMore().finally(() => (loading = false));
loadingContent = true;
void loadMoreContent().finally(() => (loadingContent = false));
}
});
}
if (secondColumnContainer && loadMoreSecondColumn) {
secondColumnContainer.addEventListener("scroll", async () => {
if (
secondColumnContainer &&
secondColumnContainer.scrollTop +
secondColumnContainer.clientHeight >=
secondColumnContainer.scrollHeight - 600 &&
loadingSecondColumn === false
) {
loadingSecondColumn = true;
void loadMoreSecondColumn().finally(
() => (loadingSecondColumn = false),
);
}
});
}
@@ -93,7 +114,7 @@
.secondColumn {
grid-column: 2 / 3;
margin: 1rem 0 0 0;
max-width: 30rem;
max-width: 28rem;
min-width: 14rem;
margin-right: 1rem;
}
@@ -162,6 +183,7 @@
<div
class="secondColumn"
bind:this={secondColumnContainer}
style:display={oneColumnLayout ? "none" : undefined}
style:overflow={styleSecondColumnOverflow}>
{@render secondColumn()}
@@ -169,7 +191,7 @@
<div
class="content global-reset-scroll-after-navigate"
bind:this={listElement}>
bind:this={contentContainer}>
{@render children()}
</div>
</div>
+45 -4
View File
@@ -1,9 +1,12 @@
<script lang="ts">
import type { Config } from "@bindings/config/Config";
import type { PaginatedQuery } from "@bindings/cob/PaginatedQuery";
import type { Patch } from "@bindings/cob/patch/Patch";
import type { RepoInfo } from "@bindings/repo/RepoInfo";
import type { Revision } from "@bindings/cob/patch/Revision";
import { invoke } from "@app/lib/invoke";
import CommentComponent from "@app/components/Comment.svelte";
import CopyableId from "@app/components/CopyableId.svelte";
import Icon from "@app/components/Icon.svelte";
@@ -18,12 +21,49 @@
interface Props {
repo: RepoInfo;
patch: Patch;
patches: Patch[];
patches: PaginatedQuery<Patch[]>;
revisions: Revision[];
config: Config;
}
const { repo, patch, patches, revisions, config }: Props = $props();
/* eslint-disable prefer-const */
let { repo, patch, patches, revisions, config }: Props = $props();
/* eslint-enable prefer-const */
let items = $state(patches.content);
let cursor = patches.cursor;
let more = patches.more;
$effect(() => {
items = patches.content;
cursor = patches.cursor;
more = patches.more;
});
async function loadPatch(rid: string, patchId: string) {
patch = await invoke<Patch>("patch_by_id", {
rid: rid,
id: patchId,
});
revisions = await invoke<Revision[]>("revisions_by_patch", {
rid: rid,
id: patchId,
});
}
async function loadMoreSecondColumn() {
if (more) {
const p = await invoke<PaginatedQuery<Patch[]>>("list_patches", {
rid: repo.rid,
skip: cursor + 20,
take: 20,
});
cursor = p.cursor;
more = p.more;
items = [...items, ...p.content];
}
}
const project = $derived(repo.payloads["xyz.radicle.project"]!);
</script>
@@ -66,7 +106,7 @@
}
</style>
<Layout>
<Layout {loadMoreSecondColumn}>
{#snippet breadcrumbs()}
<Link route={{ resource: "home" }}>
<NodeId
@@ -105,9 +145,10 @@
Patches
</div>
<div class="patch-list">
{#each patches as p}
{#each items as p}
<PatchTeaser
compact
{loadPatch}
patch={p}
rid={repo.rid}
selected={patch && p.id === patch.id} />
+2 -2
View File
@@ -36,7 +36,7 @@
more = patches.more;
});
async function loadMore() {
async function loadMoreContent() {
if (more) {
const p = await invoke<PaginatedQuery<Patch[]>>("list_patches", {
rid: repo.rid,
@@ -71,7 +71,7 @@
}
</style>
<Layout {loadMore} hideSidebar styleSecondColumnOverflow="visible">
<Layout {loadMoreContent} hideSidebar styleSecondColumnOverflow="visible">
{#snippet breadcrumbs()}
<Link route={{ resource: "home" }}>
<NodeId
+2 -2
View File
@@ -74,7 +74,7 @@ export interface LoadedRepoPatchRoute {
repo: RepoInfo;
config: Config;
patch: Patch;
patches: Patch[];
patches: PaginatedQuery<Patch[]>;
revisions: Revision[];
};
}
@@ -131,7 +131,7 @@ export async function loadPatch(
return {
resource: "repo.patch",
params: { repo, config, patch, patches: patches.content, revisions },
params: { repo, config, patch, patches, revisions },
};
}