Skip to content

Commit 13515b2

Browse files
committed
Refactor patch loading and increase responsiveness while loading/selecting patches
1 parent 7ae99dc commit 13515b2

File tree

4 files changed

+203
-107
lines changed

4 files changed

+203
-107
lines changed

web/src/lib/diff-viewer-multi-file.svelte.ts

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { fetchGithubCommitDiff, fetchGithubComparison, fetchGithubPRComparison, type FileStatus, getGithubToken, type GithubDiff } from "./github.svelte";
1+
import {
2+
fetchGithubCommitDiff,
3+
fetchGithubComparison,
4+
fetchGithubPRComparison,
5+
type FileStatus,
6+
getGithubToken,
7+
type GithubDiff,
8+
type GithubDiffResult,
9+
splitMultiFilePatchGithub,
10+
} from "./github.svelte";
211
import { type StructuredPatch } from "diff";
312
import {
413
ConciseDiffViewCachedState,
@@ -11,8 +20,8 @@ import {
1120
import type { BundledTheme } from "shiki";
1221
import { browser } from "$app/environment";
1322
import { getEffectiveGlobalTheme } from "$lib/theme.svelte";
14-
import { countOccurrences, type FileTreeNodeData, makeFileTree, type LazyPromise, lazyPromise, watchLocalStorage } from "$lib/util";
15-
import { onDestroy } from "svelte";
23+
import { countOccurrences, type FileTreeNodeData, makeFileTree, type LazyPromise, lazyPromise, watchLocalStorage, animationFramePromise } from "$lib/util";
24+
import { onDestroy, tick } from "svelte";
1625
import { type TreeNode, TreeState } from "$lib/components/tree/index.svelte";
1726
import { VList } from "virtua/svelte";
1827
import { Context, Debounced } from "runed";
@@ -319,6 +328,7 @@ export class MultiFileDiffViewerState {
319328

320329
fileTreeFilter: string = $state("");
321330
searchQuery: string = $state("");
331+
// TODO remove parallel arrays to fix order-dependency issues
322332
collapsed: boolean[] = $state([]);
323333
checked: boolean[] = $state([]);
324334
fileDetails: FileDetails[] = $state([]);
@@ -455,26 +465,72 @@ export class MultiFileDiffViewerState {
455465
}
456466
}
457467

458-
loadPatches(patches: FileDetails[], meta: DiffMetadata | null) {
468+
private clear(clearMeta: boolean = true) {
459469
// Reset state
460470
this.collapsed = [];
461471
this.checked = [];
462-
this.diffMetadata = null;
472+
if (clearMeta) {
473+
this.diffMetadata = null;
474+
}
463475
this.fileDetails = [];
464476
this.clearImages();
465477
this.vlist?.scrollToIndex(0, { align: "start" });
478+
}
479+
480+
async loadPatches(meta: () => Promise<DiffMetadata>, patches: () => Promise<AsyncGenerator<FileDetails, void>>) {
481+
try {
482+
this.progressBar.setSpinning();
483+
await tick();
484+
await animationFramePromise();
485+
486+
this.diffMetadata = await meta();
487+
await tick();
488+
await animationFramePromise();
489+
490+
this.clear(false);
491+
await tick();
492+
await animationFramePromise();
466493

467-
// Load new state
468-
this.diffMetadata = meta;
469-
patches.sort(compareFileDetails);
470-
this.fileDetails.push(...patches);
494+
const generator = await patches();
471495

472-
// in case the caller didn't close the progress
473-
if (!this.progressBar.isDone()) {
474-
this.progressBar.setProgress(100, 100);
496+
const tempDetails: FileDetails[] = [];
497+
for await (const details of generator) {
498+
// Pushing directly to the main array causes too many signals to update (lag)
499+
tempDetails.push(details);
500+
}
501+
tempDetails.sort(compareFileDetails);
502+
this.fileDetails.push(...tempDetails);
503+
return true;
504+
} catch (e) {
505+
this.clear(); // Clear any partially loaded state
506+
console.error("Failed to load patches:", e);
507+
alert("Failed to load patches: " + e);
508+
return false;
509+
} finally {
510+
if (!this.progressBar.isDone()) {
511+
this.progressBar.setProgress(100, 100);
512+
}
475513
}
476514
}
477515

516+
private async loadPatchesGithub(resultPromise: Promise<GithubDiffResult>) {
517+
return await this.loadPatches(
518+
async () => {
519+
return { type: "github", details: (await resultPromise).info };
520+
},
521+
async () => {
522+
const result = await resultPromise;
523+
const split = splitMultiFilePatchGithub(result.info, result.response);
524+
async function* generatePatches() {
525+
for (const patch of split) {
526+
yield patch;
527+
}
528+
}
529+
return generatePatches();
530+
},
531+
);
532+
}
533+
478534
// TODO fails for initial commit?
479535
// handle matched github url
480536
async loadFromGithubApi(match: Array<string>): Promise<boolean> {
@@ -483,15 +539,9 @@ export class MultiFileDiffViewerState {
483539

484540
try {
485541
if (type === "commit") {
486-
this.progressBar.setSpinning();
487-
const { info, files } = await fetchGithubCommitDiff(token, owner, repo, id.split("/")[0]);
488-
this.loadPatches(files, { type: "github", details: info });
489-
return true;
542+
return await this.loadPatchesGithub(fetchGithubCommitDiff(token, owner, repo, id.split("/")[0]));
490543
} else if (type === "pull") {
491-
this.progressBar.setSpinning();
492-
const { info, files } = await fetchGithubPRComparison(token, owner, repo, id.split("/")[0]);
493-
this.loadPatches(files, { type: "github", details: info });
494-
return true;
544+
return await this.loadPatchesGithub(fetchGithubPRComparison(token, owner, repo, id.split("/")[0]));
495545
} else if (type === "compare") {
496546
let refs = id.split("...");
497547
if (refs.length !== 2) {
@@ -501,12 +551,9 @@ export class MultiFileDiffViewerState {
501551
return false;
502552
}
503553
}
504-
this.progressBar.setSpinning();
505554
const base = refs[0];
506555
const head = refs[1];
507-
const { info, files } = await fetchGithubComparison(token, owner, repo, base, head);
508-
this.loadPatches(files, { type: "github", details: info });
509-
return true;
556+
return await this.loadPatchesGithub(fetchGithubComparison(token, owner, repo, base, head));
510557
}
511558
} catch (error) {
512559
console.error(error);

web/src/lib/github.svelte.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { browser } from "$app/environment";
22
import type { components } from "@octokit/openapi-types";
33
import { splitMultiFilePatch, trimCommitHash } from "$lib/util";
4-
import { type FileDetails, makeImageDetails } from "$lib/diff-viewer-multi-file.svelte";
4+
import { makeImageDetails } from "$lib/diff-viewer-multi-file.svelte";
55
import { PUBLIC_GITHUB_APP_NAME, PUBLIC_GITHUB_CLIENT_ID } from "$env/static/public";
66

77
export const GITHUB_USERNAME_KEY = "github_username";
@@ -22,7 +22,7 @@ export type GithubDiff = {
2222

2323
export type GithubDiffResult = {
2424
info: GithubDiff;
25-
files: FileDetails[];
25+
response: string;
2626
};
2727

2828
if (browser) {
@@ -139,7 +139,7 @@ export async function fetchGithubPRInfo(token: string | null, owner: string, rep
139139
}
140140
}
141141

142-
function splitMultiFilePatchGithub(details: GithubDiff, patch: string) {
142+
export function splitMultiFilePatchGithub(details: GithubDiff, patch: string) {
143143
return splitMultiFilePatch(patch, (from, to, status) => {
144144
const token = getGithubToken();
145145
return makeImageDetails(
@@ -176,7 +176,7 @@ export async function fetchGithubComparison(
176176
url = `https://github.com/${owner}/${repo}/compare/${base}...${head}`;
177177
}
178178
const info = { owner, repo, base, head, description, backlink: url };
179-
return { files: splitMultiFilePatchGithub(info, await response.text()), info };
179+
return { response: await response.text(), info };
180180
} else {
181181
throw Error(`Failed to retrieve comparison (${response.status}): ${await response.text()}`);
182182
}
@@ -207,7 +207,7 @@ export async function fetchGithubCommitDiff(token: string | null, owner: string,
207207
const description = `${meta.commit.message.split("\n")[0]} (${trimCommitHash(commit)})`;
208208
const info = { owner, repo, base: firstParent, head: commit, description, backlink: meta.html_url };
209209
return {
210-
files: splitMultiFilePatchGithub(info, await response.text()),
210+
response: await response.text(),
211211
info,
212212
};
213213
} else {

web/src/lib/util.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,12 @@ export const resizeObserver: Action<HTMLElement, ResizeObserverCallback> = (node
455455
};
456456
};
457457

458+
export function animationFramePromise() {
459+
return new Promise((resolve) => {
460+
requestAnimationFrame(resolve);
461+
});
462+
}
463+
458464
// from bits-ui internals
459465
export type ReadableBoxedValues<T> = {
460466
[K in keyof T]: ReadableBox<T[K]>;

0 commit comments

Comments
 (0)