Fossil SCM
Experiment to implement a click-queue for the buttons dynamically loading diff context. See [forum:c8919e12dd | Forum Post c8919e12dd] for comments and potential TODOs.
Commit
c714f2515e06d0011e3fc06938bd48a6446fe175c8bd6487afd12799d885a9e5
Parent
55a5b7014d89072…
1 file changed
+30
-9
+30
-9
| --- src/fossil.diff.js | ||
| +++ src/fossil.diff.js | ||
| @@ -117,10 +117,11 @@ | ||
| 117 | 117 | example of which, for use as a model, is: |
| 118 | 118 | |
| 119 | 119 | https://github.com/msteveb/autosetup/commit/235925e914a52a542 |
| 120 | 120 | */ |
| 121 | 121 | const ChunkLoadControls = function(tr){ |
| 122 | + this.$fetchQueue = []; | |
| 122 | 123 | this.e = {/*DOM elements*/ |
| 123 | 124 | tr: tr, |
| 124 | 125 | table: tr.parentElement/*TBODY*/.parentElement |
| 125 | 126 | }; |
| 126 | 127 | this.isSplit = this.e.table.classList.contains('splitdiff')/*else udiff*/; |
| @@ -209,11 +210,13 @@ | ||
| 209 | 210 | /** Fill a complete gap between the previous/next diff chunks |
| 210 | 211 | or at the start of the next chunk or end of the previous |
| 211 | 212 | chunks. */ |
| 212 | 213 | FillGap: 0, |
| 213 | 214 | /** Prepend context to the start of the next diff chunk. */ |
| 214 | - NextUp: -1 | |
| 215 | + NextUp: -1, | |
| 216 | + /** Process the next queued action. */ | |
| 217 | + ProcessQueue: 0x7fffffff | |
| 215 | 218 | }, |
| 216 | 219 | config: { |
| 217 | 220 | /* |
| 218 | 221 | glyphUp: '⇡', //'&#uarr;', |
| 219 | 222 | glyphDown: '⇣' //'&#darr;' |
| @@ -262,10 +265,11 @@ | ||
| 262 | 265 | }, |
| 263 | 266 | |
| 264 | 267 | /* Attempt to clean up resources and remove some circular references to |
| 265 | 268 | that GC can do the right thing. */ |
| 266 | 269 | destroy: function(){ |
| 270 | + delete this.$fetchQueue; | |
| 267 | 271 | D.remove(this.e.tr); |
| 268 | 272 | delete this.e.tr.$chunker; |
| 269 | 273 | delete this.e.tr; |
| 270 | 274 | delete this.e; |
| 271 | 275 | delete this.pos; |
| @@ -282,10 +286,13 @@ | ||
| 282 | 286 | maybeReplaceButtons: function(){ |
| 283 | 287 | if(this.pos.next && this.pos.prev |
| 284 | 288 | && (this.pos.endLhs - this.pos.startLhs <= Diff.config.chunkLoadLines)){ |
| 285 | 289 | D.clearElement(this.e.btnWrapper); |
| 286 | 290 | D.append(this.e.btnWrapper, this.createButton(this.FetchType.FillGap)); |
| 291 | + if( this.$fetchQueue && this.$fetchQueue.length>0 ){ | |
| 292 | + this.$fetchQueue = [this.FetchType.FillGap]; | |
| 293 | + } | |
| 287 | 294 | } |
| 288 | 295 | return this; |
| 289 | 296 | }, |
| 290 | 297 | |
| 291 | 298 | /** |
| @@ -501,28 +508,40 @@ | ||
| 501 | 508 | This is an async operation. While it is in transit, any calls |
| 502 | 509 | to this function will have no effect except (possibly) to emit |
| 503 | 510 | a warning. Returns this object. |
| 504 | 511 | */ |
| 505 | 512 | fetchChunk: function(fetchType){ |
| 513 | + if( !this.$fetchQueue ) return this; // HACKHACK: are we destroyed? | |
| 514 | + if( fetchType==this.FetchType.ProcessQueue ){ | |
| 515 | + if( this.$fetchQueue.length==0 ) return this; | |
| 516 | + //console.log('fetchChunk: processing queue ...'); | |
| 517 | + } | |
| 518 | + else{ | |
| 519 | + this.$fetchQueue.push(fetchType); | |
| 520 | + if( this.$fetchQueue.length!=1 ) return this; | |
| 521 | + //console.log('fetchChunk: processing user input ...'); | |
| 522 | + } | |
| 523 | + fetchType = this.$fetchQueue[0]; | |
| 506 | 524 | /* Forewarning, this is a bit confusing: when fetching the |
| 507 | 525 | previous lines, we're doing so on behalf of the *next* diff |
| 508 | 526 | chunk (this.pos.next), and vice versa. */ |
| 509 | - if(this.$isFetching){ | |
| 510 | - return this.msg(true,"Cannot load chunk while a load is pending."); | |
| 511 | - } | |
| 512 | 527 | if(fetchType===this.FetchType.NextUp && !this.pos.next |
| 513 | 528 | || fetchType===this.FetchType.PrevDown && !this.pos.prev){ |
| 514 | 529 | console.error("Attempt to fetch diff lines but don't have any."); |
| 515 | 530 | return this; |
| 516 | 531 | } |
| 517 | - this.msg(false,"Fetching diff chunk..."); | |
| 532 | + this.msg(false); | |
| 518 | 533 | const fOpt = { |
| 519 | 534 | urlParams:{ |
| 520 | 535 | name: this.fileHash, from: 0, to: 0 |
| 521 | 536 | }, |
| 522 | - aftersend: ()=>delete this.$isFetching, | |
| 523 | - onload: (list)=>this.injectResponse(fetchType,up,list) | |
| 537 | + onload: function(list){ | |
| 538 | + this.injectResponse(fetchType,up,list); | |
| 539 | + if( !this.$fetchQueue || this.$fetchQueue.length==0 ) return; | |
| 540 | + this.$fetchQueue.shift(); | |
| 541 | + setTimeout(this.fetchChunk.bind(this,this.FetchType.ProcessQueue)); | |
| 542 | + }.bind(this) | |
| 524 | 543 | }; |
| 525 | 544 | const up = fOpt.urlParams; |
| 526 | 545 | if(fetchType===this.FetchType.FillGap){ |
| 527 | 546 | /* Easiest case: filling a whole gap. */ |
| 528 | 547 | up.from = this.pos.startLhs; |
| @@ -551,13 +570,15 @@ | ||
| 551 | 570 | if( this.pos.prev && this.pos.prev.endLhs >= up.from ){ |
| 552 | 571 | up.from = this.pos.prev.endLhs + 1; |
| 553 | 572 | fetchType = this.FetchType.FillGap; |
| 554 | 573 | } |
| 555 | 574 | } |
| 556 | - this.$isFetching = true; | |
| 557 | 575 | //console.debug("fetchChunk(",fetchType,")",up); |
| 558 | - fOpt.onerror = (err)=>this.msg(true,err.message); | |
| 576 | + fOpt.onerror = function(err){ | |
| 577 | + this.msg(true,err.message); | |
| 578 | + this.$fetchQueue = []; | |
| 579 | + }.bind(this); | |
| 559 | 580 | Diff.fetchArtifactChunk(fOpt); |
| 560 | 581 | return this; |
| 561 | 582 | } |
| 562 | 583 | }; |
| 563 | 584 | |
| 564 | 585 |
| --- src/fossil.diff.js | |
| +++ src/fossil.diff.js | |
| @@ -117,10 +117,11 @@ | |
| 117 | example of which, for use as a model, is: |
| 118 | |
| 119 | https://github.com/msteveb/autosetup/commit/235925e914a52a542 |
| 120 | */ |
| 121 | const ChunkLoadControls = function(tr){ |
| 122 | this.e = {/*DOM elements*/ |
| 123 | tr: tr, |
| 124 | table: tr.parentElement/*TBODY*/.parentElement |
| 125 | }; |
| 126 | this.isSplit = this.e.table.classList.contains('splitdiff')/*else udiff*/; |
| @@ -209,11 +210,13 @@ | |
| 209 | /** Fill a complete gap between the previous/next diff chunks |
| 210 | or at the start of the next chunk or end of the previous |
| 211 | chunks. */ |
| 212 | FillGap: 0, |
| 213 | /** Prepend context to the start of the next diff chunk. */ |
| 214 | NextUp: -1 |
| 215 | }, |
| 216 | config: { |
| 217 | /* |
| 218 | glyphUp: '⇡', //'&#uarr;', |
| 219 | glyphDown: '⇣' //'&#darr;' |
| @@ -262,10 +265,11 @@ | |
| 262 | }, |
| 263 | |
| 264 | /* Attempt to clean up resources and remove some circular references to |
| 265 | that GC can do the right thing. */ |
| 266 | destroy: function(){ |
| 267 | D.remove(this.e.tr); |
| 268 | delete this.e.tr.$chunker; |
| 269 | delete this.e.tr; |
| 270 | delete this.e; |
| 271 | delete this.pos; |
| @@ -282,10 +286,13 @@ | |
| 282 | maybeReplaceButtons: function(){ |
| 283 | if(this.pos.next && this.pos.prev |
| 284 | && (this.pos.endLhs - this.pos.startLhs <= Diff.config.chunkLoadLines)){ |
| 285 | D.clearElement(this.e.btnWrapper); |
| 286 | D.append(this.e.btnWrapper, this.createButton(this.FetchType.FillGap)); |
| 287 | } |
| 288 | return this; |
| 289 | }, |
| 290 | |
| 291 | /** |
| @@ -501,28 +508,40 @@ | |
| 501 | This is an async operation. While it is in transit, any calls |
| 502 | to this function will have no effect except (possibly) to emit |
| 503 | a warning. Returns this object. |
| 504 | */ |
| 505 | fetchChunk: function(fetchType){ |
| 506 | /* Forewarning, this is a bit confusing: when fetching the |
| 507 | previous lines, we're doing so on behalf of the *next* diff |
| 508 | chunk (this.pos.next), and vice versa. */ |
| 509 | if(this.$isFetching){ |
| 510 | return this.msg(true,"Cannot load chunk while a load is pending."); |
| 511 | } |
| 512 | if(fetchType===this.FetchType.NextUp && !this.pos.next |
| 513 | || fetchType===this.FetchType.PrevDown && !this.pos.prev){ |
| 514 | console.error("Attempt to fetch diff lines but don't have any."); |
| 515 | return this; |
| 516 | } |
| 517 | this.msg(false,"Fetching diff chunk..."); |
| 518 | const fOpt = { |
| 519 | urlParams:{ |
| 520 | name: this.fileHash, from: 0, to: 0 |
| 521 | }, |
| 522 | aftersend: ()=>delete this.$isFetching, |
| 523 | onload: (list)=>this.injectResponse(fetchType,up,list) |
| 524 | }; |
| 525 | const up = fOpt.urlParams; |
| 526 | if(fetchType===this.FetchType.FillGap){ |
| 527 | /* Easiest case: filling a whole gap. */ |
| 528 | up.from = this.pos.startLhs; |
| @@ -551,13 +570,15 @@ | |
| 551 | if( this.pos.prev && this.pos.prev.endLhs >= up.from ){ |
| 552 | up.from = this.pos.prev.endLhs + 1; |
| 553 | fetchType = this.FetchType.FillGap; |
| 554 | } |
| 555 | } |
| 556 | this.$isFetching = true; |
| 557 | //console.debug("fetchChunk(",fetchType,")",up); |
| 558 | fOpt.onerror = (err)=>this.msg(true,err.message); |
| 559 | Diff.fetchArtifactChunk(fOpt); |
| 560 | return this; |
| 561 | } |
| 562 | }; |
| 563 | |
| 564 |
| --- src/fossil.diff.js | |
| +++ src/fossil.diff.js | |
| @@ -117,10 +117,11 @@ | |
| 117 | example of which, for use as a model, is: |
| 118 | |
| 119 | https://github.com/msteveb/autosetup/commit/235925e914a52a542 |
| 120 | */ |
| 121 | const ChunkLoadControls = function(tr){ |
| 122 | this.$fetchQueue = []; |
| 123 | this.e = {/*DOM elements*/ |
| 124 | tr: tr, |
| 125 | table: tr.parentElement/*TBODY*/.parentElement |
| 126 | }; |
| 127 | this.isSplit = this.e.table.classList.contains('splitdiff')/*else udiff*/; |
| @@ -209,11 +210,13 @@ | |
| 210 | /** Fill a complete gap between the previous/next diff chunks |
| 211 | or at the start of the next chunk or end of the previous |
| 212 | chunks. */ |
| 213 | FillGap: 0, |
| 214 | /** Prepend context to the start of the next diff chunk. */ |
| 215 | NextUp: -1, |
| 216 | /** Process the next queued action. */ |
| 217 | ProcessQueue: 0x7fffffff |
| 218 | }, |
| 219 | config: { |
| 220 | /* |
| 221 | glyphUp: '⇡', //'&#uarr;', |
| 222 | glyphDown: '⇣' //'&#darr;' |
| @@ -262,10 +265,11 @@ | |
| 265 | }, |
| 266 | |
| 267 | /* Attempt to clean up resources and remove some circular references to |
| 268 | that GC can do the right thing. */ |
| 269 | destroy: function(){ |
| 270 | delete this.$fetchQueue; |
| 271 | D.remove(this.e.tr); |
| 272 | delete this.e.tr.$chunker; |
| 273 | delete this.e.tr; |
| 274 | delete this.e; |
| 275 | delete this.pos; |
| @@ -282,10 +286,13 @@ | |
| 286 | maybeReplaceButtons: function(){ |
| 287 | if(this.pos.next && this.pos.prev |
| 288 | && (this.pos.endLhs - this.pos.startLhs <= Diff.config.chunkLoadLines)){ |
| 289 | D.clearElement(this.e.btnWrapper); |
| 290 | D.append(this.e.btnWrapper, this.createButton(this.FetchType.FillGap)); |
| 291 | if( this.$fetchQueue && this.$fetchQueue.length>0 ){ |
| 292 | this.$fetchQueue = [this.FetchType.FillGap]; |
| 293 | } |
| 294 | } |
| 295 | return this; |
| 296 | }, |
| 297 | |
| 298 | /** |
| @@ -501,28 +508,40 @@ | |
| 508 | This is an async operation. While it is in transit, any calls |
| 509 | to this function will have no effect except (possibly) to emit |
| 510 | a warning. Returns this object. |
| 511 | */ |
| 512 | fetchChunk: function(fetchType){ |
| 513 | if( !this.$fetchQueue ) return this; // HACKHACK: are we destroyed? |
| 514 | if( fetchType==this.FetchType.ProcessQueue ){ |
| 515 | if( this.$fetchQueue.length==0 ) return this; |
| 516 | //console.log('fetchChunk: processing queue ...'); |
| 517 | } |
| 518 | else{ |
| 519 | this.$fetchQueue.push(fetchType); |
| 520 | if( this.$fetchQueue.length!=1 ) return this; |
| 521 | //console.log('fetchChunk: processing user input ...'); |
| 522 | } |
| 523 | fetchType = this.$fetchQueue[0]; |
| 524 | /* Forewarning, this is a bit confusing: when fetching the |
| 525 | previous lines, we're doing so on behalf of the *next* diff |
| 526 | chunk (this.pos.next), and vice versa. */ |
| 527 | if(fetchType===this.FetchType.NextUp && !this.pos.next |
| 528 | || fetchType===this.FetchType.PrevDown && !this.pos.prev){ |
| 529 | console.error("Attempt to fetch diff lines but don't have any."); |
| 530 | return this; |
| 531 | } |
| 532 | this.msg(false); |
| 533 | const fOpt = { |
| 534 | urlParams:{ |
| 535 | name: this.fileHash, from: 0, to: 0 |
| 536 | }, |
| 537 | onload: function(list){ |
| 538 | this.injectResponse(fetchType,up,list); |
| 539 | if( !this.$fetchQueue || this.$fetchQueue.length==0 ) return; |
| 540 | this.$fetchQueue.shift(); |
| 541 | setTimeout(this.fetchChunk.bind(this,this.FetchType.ProcessQueue)); |
| 542 | }.bind(this) |
| 543 | }; |
| 544 | const up = fOpt.urlParams; |
| 545 | if(fetchType===this.FetchType.FillGap){ |
| 546 | /* Easiest case: filling a whole gap. */ |
| 547 | up.from = this.pos.startLhs; |
| @@ -551,13 +570,15 @@ | |
| 570 | if( this.pos.prev && this.pos.prev.endLhs >= up.from ){ |
| 571 | up.from = this.pos.prev.endLhs + 1; |
| 572 | fetchType = this.FetchType.FillGap; |
| 573 | } |
| 574 | } |
| 575 | //console.debug("fetchChunk(",fetchType,")",up); |
| 576 | fOpt.onerror = function(err){ |
| 577 | this.msg(true,err.message); |
| 578 | this.$fetchQueue = []; |
| 579 | }.bind(this); |
| 580 | Diff.fetchArtifactChunk(fOpt); |
| 581 | return this; |
| 582 | } |
| 583 | }; |
| 584 | |
| 585 |