mirror of
https://seed.radicle.dev/rad:z4D5UCArafTzTQpDZNQRuqswh3ury.git
synced 2026-10-18 19:10:45 +08:00
Persist sidebar across navigations to preserve scroll position
Move AppSidebar out of per-route view components into App.svelte so it
is never unmounted on navigation. This fixes the sidebar scroll position
resetting to the top whenever the user opens a repository or navigates
between Issues, Patches and the repo home view.
Also removes the redundant invoke<Config>("config") call from each repo
route loader — the value is now taken from sidebarData.config, which
loadSidebarData() already fetches.
This commit is contained in:
+37
-15
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { Config } from "@bindings/config/Config";
|
||||
import type { ErrorWrapper } from "@bindings/error/ErrorWrapper";
|
||||
import type { RepoInfo } from "@bindings/repo/RepoInfo";
|
||||
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
@@ -20,12 +21,14 @@
|
||||
import { invoke } from "@app/lib/invoke";
|
||||
import { hide } from "@app/lib/modal";
|
||||
import * as router from "@app/lib/router";
|
||||
import { isLoadedRepoRoute } from "@app/lib/router/definitions";
|
||||
import {
|
||||
setUnlistenNodeEvents,
|
||||
unlistenNodeEvents,
|
||||
} from "@app/lib/startup.svelte";
|
||||
import { isMac, unreachable } from "@app/lib/utils";
|
||||
|
||||
import AppSidebar from "@app/components/AppSidebar.svelte";
|
||||
import { codeFont } from "@app/components/CodeFontSwitch.svelte";
|
||||
import {
|
||||
followSystemTheme,
|
||||
@@ -50,6 +53,11 @@
|
||||
|
||||
const activeRouteStore = router.activeRouteStore;
|
||||
|
||||
const activeRepo = $derived.by((): RepoInfo | undefined => {
|
||||
const route = $activeRouteStore;
|
||||
return isLoadedRepoRoute(route) ? route.params.repo : undefined;
|
||||
});
|
||||
|
||||
const DRAG_REGION_HEIGHT = 32;
|
||||
const INTERACTIVE_TAGS = new Set([
|
||||
"a",
|
||||
@@ -144,6 +152,13 @@
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<svelte:document
|
||||
@@ -194,20 +209,27 @@
|
||||
{:else if showSpinner}
|
||||
<div class="spinner"><Spinner /></div>
|
||||
{/if}
|
||||
{:else if $activeRouteStore.resource === "inbox"}
|
||||
<InboxView {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "guide"}
|
||||
<GuideView {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "repo.home"}
|
||||
<RepoHome {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "repo.issue"}
|
||||
<Issue {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "repo.issues"}
|
||||
<Issues {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "repo.patch"}
|
||||
<Patch {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "repo.patches"}
|
||||
<Patches {...$activeRouteStore.params} />
|
||||
{:else}
|
||||
{unreachable($activeRouteStore)}
|
||||
<div class="layout">
|
||||
<AppSidebar
|
||||
sidebarData={$activeRouteStore.params.sidebarData}
|
||||
{activeRepo} />
|
||||
{#if $activeRouteStore.resource === "inbox"}
|
||||
<InboxView {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "guide"}
|
||||
<GuideView {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "repo.home"}
|
||||
<RepoHome {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "repo.issue"}
|
||||
<Issue {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "repo.issues"}
|
||||
<Issues {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "repo.patch"}
|
||||
<Patch {...$activeRouteStore.params} />
|
||||
{:else if $activeRouteStore.resource === "repo.patches"}
|
||||
<Patches {...$activeRouteStore.params} />
|
||||
{:else}
|
||||
{unreachable($activeRouteStore)}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -52,6 +52,18 @@ export type LoadedRoute =
|
||||
| LoadedInboxRoute
|
||||
| LoadedGuideRoute;
|
||||
|
||||
export function isLoadedRepoRoute(
|
||||
route: LoadedRoute,
|
||||
): route is LoadedRepoRoute {
|
||||
return (
|
||||
route.resource === "repo.home" ||
|
||||
route.resource === "repo.issue" ||
|
||||
route.resource === "repo.issues" ||
|
||||
route.resource === "repo.patch" ||
|
||||
route.resource === "repo.patches"
|
||||
);
|
||||
}
|
||||
|
||||
export async function loadSidebarData(): Promise<SidebarData> {
|
||||
const [config, repos, notificationCount, seededNotReplicated] =
|
||||
await Promise.all([
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {sidebarData}>
|
||||
<Layout>
|
||||
<div class="page">
|
||||
<div class="hero">
|
||||
<img src="/flower.png" alt="" />
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {sidebarData} selfScroll>
|
||||
<Layout selfScroll>
|
||||
{#if notificationCount.value === 0}
|
||||
<div class="inbox-zero">
|
||||
<div
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
import { show } from "@app/lib/modal";
|
||||
import * as roles from "@app/lib/roles";
|
||||
import * as router from "@app/lib/router";
|
||||
import type { SidebarData } from "@app/lib/router/definitions";
|
||||
import {
|
||||
explorerUrl,
|
||||
issueStatusBackgroundColor,
|
||||
@@ -49,7 +48,6 @@
|
||||
config: Config;
|
||||
threads: Thread[];
|
||||
status: IssueStatus;
|
||||
sidebarData: SidebarData;
|
||||
}
|
||||
|
||||
/* eslint-disable prefer-const */
|
||||
@@ -61,7 +59,6 @@
|
||||
config,
|
||||
threads,
|
||||
status: initialStatus,
|
||||
sidebarData,
|
||||
}: Props = $props();
|
||||
/* eslint-enable prefer-const */
|
||||
|
||||
@@ -346,7 +343,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {sidebarData} activeRepo={repo}>
|
||||
<Layout>
|
||||
<div class="page">
|
||||
<div class="topbar">
|
||||
<Icon name={issue.state.status === "open" ? "issue" : "issue-closed"} />
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
} from "@app/lib/issueCounts.svelte";
|
||||
import { show } from "@app/lib/modal";
|
||||
import * as router from "@app/lib/router";
|
||||
import type { SidebarData } from "@app/lib/router/definitions";
|
||||
import { modifierKey } from "@app/lib/utils";
|
||||
|
||||
import Button from "@app/components/Button.svelte";
|
||||
@@ -32,11 +31,10 @@
|
||||
repo: RepoInfo;
|
||||
issues: Issue[];
|
||||
status: IssueStatus;
|
||||
sidebarData: SidebarData;
|
||||
}
|
||||
|
||||
/* eslint-disable prefer-const */
|
||||
let { repo, issues, status, sidebarData }: Props = $props();
|
||||
let { repo, issues, status }: Props = $props();
|
||||
/* eslint-enable prefer-const */
|
||||
|
||||
let cacheState: CacheEvent | undefined = $state();
|
||||
@@ -156,7 +154,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {sidebarData} activeRepo={repo} selfScroll>
|
||||
<Layout selfScroll>
|
||||
<div class="page">
|
||||
<div class="topbar">
|
||||
<span class="topbar-title">Issues</span>
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
<script lang="ts">
|
||||
import type { RepoInfo } from "@bindings/repo/RepoInfo";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
import type { SidebarData } from "@app/lib/router/definitions";
|
||||
|
||||
import AppSidebar from "@app/components/AppSidebar.svelte";
|
||||
import ScrollArea from "@app/components/ScrollArea.svelte";
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
sidebarData: SidebarData;
|
||||
activeRepo?: RepoInfo;
|
||||
loadMoreContent?: () => Promise<void>;
|
||||
selfScroll?: boolean;
|
||||
}
|
||||
|
||||
const {
|
||||
children,
|
||||
sidebarData,
|
||||
activeRepo = undefined,
|
||||
loadMoreContent = undefined,
|
||||
selfScroll = false,
|
||||
}: Props = $props();
|
||||
@@ -26,35 +18,22 @@
|
||||
let loadingContent = false;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="layout">
|
||||
<AppSidebar {sidebarData} {activeRepo} />
|
||||
{#if selfScroll}
|
||||
<div
|
||||
style="height: 100%; overflow: hidden; min-width: 0; background-color: var(--color-surface-canvas);">
|
||||
{@render children()}
|
||||
</div>
|
||||
{:else}
|
||||
<ScrollArea
|
||||
style="height: 100%; width: 100%; background-color: var(--color-surface-canvas);"
|
||||
onScrollHalf={loadMoreContent
|
||||
? () => {
|
||||
if (!loadingContent) {
|
||||
loadingContent = true;
|
||||
void loadMoreContent().finally(() => (loadingContent = false));
|
||||
}
|
||||
{#if selfScroll}
|
||||
<div
|
||||
style="height: 100%; overflow: hidden; min-width: 0; background-color: var(--color-surface-canvas);">
|
||||
{@render children()}
|
||||
</div>
|
||||
{:else}
|
||||
<ScrollArea
|
||||
style="height: 100%; width: 100%; background-color: var(--color-surface-canvas);"
|
||||
onScrollHalf={loadMoreContent
|
||||
? () => {
|
||||
if (!loadingContent) {
|
||||
loadingContent = true;
|
||||
void loadMoreContent().finally(() => (loadingContent = false));
|
||||
}
|
||||
: undefined}>
|
||||
{@render children()}
|
||||
</ScrollArea>
|
||||
{/if}
|
||||
</div>
|
||||
}
|
||||
: undefined}>
|
||||
{@render children()}
|
||||
</ScrollArea>
|
||||
{/if}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
import { nodeRunning } from "@app/lib/events";
|
||||
import { invoke } from "@app/lib/invoke";
|
||||
import * as router from "@app/lib/router";
|
||||
import type { SidebarData } from "@app/lib/router/definitions";
|
||||
import {
|
||||
didFromPublicKey,
|
||||
explorerUrl,
|
||||
@@ -46,7 +45,6 @@
|
||||
activity: Operation<Action>[];
|
||||
status: PatchStatus | undefined;
|
||||
review: Review | DraftReview | undefined;
|
||||
sidebarData: SidebarData;
|
||||
}
|
||||
|
||||
/* eslint-disable prefer-const */
|
||||
@@ -58,7 +56,6 @@
|
||||
status: initialStatus,
|
||||
activity,
|
||||
review,
|
||||
sidebarData,
|
||||
}: Props = $props();
|
||||
/* eslint-enable prefer-const */
|
||||
|
||||
@@ -249,7 +246,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {sidebarData} activeRepo={repo}>
|
||||
<Layout>
|
||||
{#if review}
|
||||
<ReviewComponent
|
||||
{config}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
updatePatchCounts,
|
||||
} from "@app/lib/patchCounts.svelte";
|
||||
import * as router from "@app/lib/router";
|
||||
import type { SidebarData } from "@app/lib/router/definitions";
|
||||
import { modifierKey } from "@app/lib/utils";
|
||||
|
||||
import CobCacheWarning from "@app/components/CobCacheWarning.svelte";
|
||||
@@ -33,10 +32,9 @@
|
||||
repo: RepoInfo;
|
||||
patches: PaginatedQuery<Patch[]>;
|
||||
status: PatchStatus | undefined;
|
||||
sidebarData: SidebarData;
|
||||
}
|
||||
|
||||
const { repo, patches, status, sidebarData }: Props = $props();
|
||||
const { repo, patches, status }: Props = $props();
|
||||
|
||||
let items = $state(patches.content);
|
||||
let cursor = patches.cursor;
|
||||
@@ -225,7 +223,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {sidebarData} activeRepo={repo} selfScroll>
|
||||
<Layout selfScroll>
|
||||
<div class="page">
|
||||
<div class="topbar">
|
||||
<span class="topbar-title">Patches</span>
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import { useOverlayScrollbars } from "overlayscrollbars-svelte";
|
||||
|
||||
import { invoke, InvokeError } from "@app/lib/invoke";
|
||||
import type { SidebarData } from "@app/lib/router/definitions";
|
||||
import { highlight } from "@app/lib/syntax";
|
||||
|
||||
import FileBlock from "@app/components/FileBlock.svelte";
|
||||
@@ -27,11 +26,10 @@
|
||||
tree: Tree;
|
||||
repo: RepoInfo;
|
||||
readme: Readme | null;
|
||||
sidebarData: SidebarData;
|
||||
}
|
||||
|
||||
/* eslint-disable prefer-const */
|
||||
let { tree, readme, repo, sidebarData }: Props = $props();
|
||||
let { tree, readme, repo }: Props = $props();
|
||||
/* eslint-enable prefer-const */
|
||||
|
||||
let currentPath = $state("");
|
||||
@@ -120,7 +118,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<Layout {sidebarData} activeRepo={repo} selfScroll>
|
||||
<Layout selfScroll>
|
||||
<div
|
||||
class="content"
|
||||
style:display="flex"
|
||||
|
||||
+11
-17
@@ -42,7 +42,6 @@ export interface LoadedRepoHomeRoute {
|
||||
repo: RepoInfo;
|
||||
sha?: string;
|
||||
tree: Tree;
|
||||
config: Config;
|
||||
readme: Readme | null;
|
||||
sidebarData: SidebarData;
|
||||
};
|
||||
@@ -72,7 +71,6 @@ export interface LoadedRepoIssuesRoute {
|
||||
resource: "repo.issues";
|
||||
params: {
|
||||
repo: RepoInfo;
|
||||
config: Config;
|
||||
issues: Issue[];
|
||||
status: IssueStatus;
|
||||
sidebarData: SidebarData;
|
||||
@@ -114,7 +112,6 @@ export interface LoadedRepoPatchesRoute {
|
||||
resource: "repo.patches";
|
||||
params: {
|
||||
repo: RepoInfo;
|
||||
config: Config;
|
||||
patches: PaginatedQuery<Patch[]>;
|
||||
status: PatchStatus | undefined;
|
||||
sidebarData: SidebarData;
|
||||
@@ -137,10 +134,9 @@ export type LoadedRepoRoute =
|
||||
export async function loadPatch(
|
||||
route: RepoPatchRoute,
|
||||
): Promise<LoadedRepoPatchRoute> {
|
||||
const [sidebarData, config, repo, patches, patch, revisions, activity] =
|
||||
const [sidebarData, repo, patches, patch, revisions, activity] =
|
||||
await Promise.all([
|
||||
loadSidebarData(),
|
||||
invoke<Config>("config"),
|
||||
invoke<RepoInfo>("repo_by_id", {
|
||||
rid: route.rid,
|
||||
}),
|
||||
@@ -163,6 +159,8 @@ export async function loadPatch(
|
||||
}),
|
||||
]);
|
||||
|
||||
const config = sidebarData.config;
|
||||
|
||||
const draftReview =
|
||||
route.reviewId !== undefined &&
|
||||
draftReviewStorage.get(route.reviewId, {
|
||||
@@ -195,9 +193,8 @@ export async function loadPatch(
|
||||
export async function loadPatches(
|
||||
route: RepoPatchesRoute,
|
||||
): Promise<LoadedRepoPatchesRoute> {
|
||||
const [sidebarData, config, repo, patches] = await Promise.all([
|
||||
const [sidebarData, repo, patches] = await Promise.all([
|
||||
loadSidebarData(),
|
||||
invoke<Config>("config"),
|
||||
invoke<RepoInfo>("repo_by_id", {
|
||||
rid: route.rid,
|
||||
}),
|
||||
@@ -210,16 +207,15 @@ export async function loadPatches(
|
||||
|
||||
return {
|
||||
resource: "repo.patches",
|
||||
params: { sidebarData, repo, config, patches, status: route.status },
|
||||
params: { sidebarData, repo, patches, status: route.status },
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadRepoHome(
|
||||
route: RepoHomeRoute,
|
||||
): Promise<LoadedRepoHomeRoute> {
|
||||
const [sidebarData, config, repo, readme, tree] = await Promise.all([
|
||||
const [sidebarData, repo, readme, tree] = await Promise.all([
|
||||
loadSidebarData(),
|
||||
invoke<Config>("config"),
|
||||
invoke<RepoInfo>("repo_by_id", {
|
||||
rid: route.rid,
|
||||
}),
|
||||
@@ -235,17 +231,16 @@ export async function loadRepoHome(
|
||||
|
||||
return {
|
||||
resource: "repo.home",
|
||||
params: { sidebarData, repo, sha: route.sha, config, readme, tree },
|
||||
params: { sidebarData, repo, sha: route.sha, readme, tree },
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadIssue(
|
||||
route: RepoIssueRoute,
|
||||
): Promise<LoadedRepoIssueRoute> {
|
||||
const [sidebarData, config, repo, issue, activity, issues, threads] =
|
||||
const [sidebarData, repo, issue, activity, issues, threads] =
|
||||
await Promise.all([
|
||||
loadSidebarData(),
|
||||
invoke<Config>("config"),
|
||||
invoke<RepoInfo>("repo_by_id", {
|
||||
rid: route.rid,
|
||||
}),
|
||||
@@ -272,7 +267,7 @@ export async function loadIssue(
|
||||
params: {
|
||||
sidebarData,
|
||||
repo,
|
||||
config,
|
||||
config: sidebarData.config,
|
||||
issue,
|
||||
activity,
|
||||
issues,
|
||||
@@ -285,9 +280,8 @@ export async function loadIssue(
|
||||
export async function loadIssues(
|
||||
route: RepoIssuesRoute,
|
||||
): Promise<LoadedRepoIssuesRoute> {
|
||||
const [sidebarData, config, repo, issues] = await Promise.all([
|
||||
const [sidebarData, repo, issues] = await Promise.all([
|
||||
loadSidebarData(),
|
||||
invoke<Config>("config"),
|
||||
invoke<RepoInfo>("repo_by_id", {
|
||||
rid: route.rid,
|
||||
}),
|
||||
@@ -299,7 +293,7 @@ export async function loadIssues(
|
||||
|
||||
return {
|
||||
resource: "repo.issues",
|
||||
params: { sidebarData, repo, config, issues, status: route.status },
|
||||
params: { sidebarData, repo, issues, status: route.status },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user