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" ;
211import { type StructuredPatch } from "diff" ;
312import {
413 ConciseDiffViewCachedState ,
@@ -11,8 +20,8 @@ import {
1120import type { BundledTheme } from "shiki" ;
1221import { browser } from "$app/environment" ;
1322import { 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" ;
1625import { type TreeNode , TreeState } from "$lib/components/tree/index.svelte" ;
1726import { VList } from "virtua/svelte" ;
1827import { 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 ) ;
0 commit comments