Fossil SCM
Removed some dead code. Internal cleanups and reorg in prep for upcoming changes.
Commit
bc5dc16e55134b035d23f3df60026d8e40537cc0749302670e930662c02c78bb
Parent
65ae15e0088cf3f…
1 file changed
+95
-92
+95
-92
| --- src/fossil.diff.js | ||
| +++ src/fossil.diff.js | ||
| @@ -204,30 +204,27 @@ | ||
| 204 | 204 | } |
| 205 | 205 | }); |
| 206 | 206 | }; |
| 207 | 207 | |
| 208 | 208 | /** |
| 209 | - Installs chunk-loading controls into TR element tr. isSplit is true | |
| 210 | - if the parent table is a split diff, else false. | |
| 209 | + Installs chunk-loading controls into TR.diffskip element tr. | |
| 210 | + Each instance corresponds to a single TR.diffskip element. | |
| 211 | 211 | |
| 212 | - The goal is to base these controls closely on github's, a good example | |
| 213 | - of which, for use as a model, is: | |
| 212 | + The goal is to base these controls roughly on github's, a good | |
| 213 | + example of which, for use as a model, is: | |
| 214 | 214 | |
| 215 | 215 | https://github.com/msteveb/autosetup/commit/235925e914a52a542 |
| 216 | - | |
| 217 | - Each instance corresponds to a single TR.diffskip element. | |
| 218 | 216 | */ |
| 219 | - Diff.ChunkLoadControls = function(isSplit, tr){ | |
| 220 | - this.isSplit = isSplit; | |
| 217 | + Diff.ChunkLoadControls = function(tr){ | |
| 221 | 218 | this.e = {/*DOM elements*/ |
| 222 | 219 | tr: tr, |
| 223 | 220 | table: tr.parentElement/*TBODY*/.parentElement |
| 224 | 221 | }; |
| 222 | + this.isSplit = this.e.table.classList.contains('splitdiff')/*else udiff*/; | |
| 225 | 223 | this.fileHash = this.e.table.dataset.lefthash; |
| 226 | 224 | tr.$chunker = this /* keep GC from reaping this */; |
| 227 | 225 | this.pos = { |
| 228 | - //hash: F.hashDigits(this.fileHash), | |
| 229 | 226 | /* These line numbers correspond to the LHS file. Because the |
| 230 | 227 | contents are common to both sides, we have the same number |
| 231 | 228 | for the RHS, but need to extract those line numbers from the |
| 232 | 229 | neighboring TR blocks */ |
| 233 | 230 | startLhs: +tr.dataset.startln, |
| @@ -234,11 +231,11 @@ | ||
| 234 | 231 | endLhs: +tr.dataset.endln |
| 235 | 232 | }; |
| 236 | 233 | D.clearElement(tr); |
| 237 | 234 | this.e.td = D.addClass( |
| 238 | 235 | /* Holder for our UI controls */ |
| 239 | - D.attr(D.td(tr), 'colspan', isSplit ? 5 : 4), | |
| 236 | + D.attr(D.td(tr), 'colspan', this.isSplit ? 5 : 4), | |
| 240 | 237 | 'chunkctrl' |
| 241 | 238 | ); |
| 242 | 239 | this.e.btnWrapper = D.div(); |
| 243 | 240 | D.append(this.e.td, this.e.btnWrapper); |
| 244 | 241 | /** |
| @@ -252,18 +249,18 @@ | ||
| 252 | 249 | |
| 253 | 250 | - A single button to load the final chunk incrementally |
| 254 | 251 | */ |
| 255 | 252 | if(tr.nextElementSibling){ |
| 256 | 253 | this.pos.next = { |
| 257 | - startLhs: extractLineNo(true, true, tr.nextElementSibling, isSplit), | |
| 258 | - startRhs: extractLineNo(false, true, tr.nextElementSibling, isSplit) | |
| 254 | + startLhs: extractLineNo(true, true, tr.nextElementSibling, this.isSplit), | |
| 255 | + startRhs: extractLineNo(false, true, tr.nextElementSibling, this.isSplit) | |
| 259 | 256 | }; |
| 260 | 257 | } |
| 261 | 258 | if(tr.previousElementSibling){ |
| 262 | 259 | this.pos.prev = { |
| 263 | - endLhs: extractLineNo(true, false, tr.previousElementSibling, isSplit), | |
| 264 | - endRhs: extractLineNo(false, false, tr.previousElementSibling, isSplit) | |
| 260 | + endLhs: extractLineNo(true, false, tr.previousElementSibling, this.isSplit), | |
| 261 | + endRhs: extractLineNo(false, false, tr.previousElementSibling, this.isSplit) | |
| 265 | 262 | }; |
| 266 | 263 | } |
| 267 | 264 | let btnUp = false, btnDown = false; |
| 268 | 265 | /** |
| 269 | 266 | this.pos.next refers to the line numbers in the next TR's chunk. |
| @@ -275,39 +272,22 @@ | ||
| 275 | 272 | && ((this.pos.next.startLhs - this.pos.prev.endLhs) |
| 276 | 273 | <= Diff.config.chunkLoadLines))){ |
| 277 | 274 | /* Place a single button to load the whole block, rather |
| 278 | 275 | than separate up/down buttons. */ |
| 279 | 276 | btnDown = false; |
| 280 | - btnUp = D.append( | |
| 281 | - D.addClass(D.span(), 'button', 'up', 'down'), | |
| 282 | - D.span(/*glyph holder*/) | |
| 283 | - ); | |
| 277 | + btnUp = this.createButton(0); | |
| 284 | 278 | }else{ |
| 285 | 279 | /* Figure out which chunk-load buttons to add... */ |
| 286 | 280 | if(this.pos.prev){ |
| 287 | - btnDown = D.append( | |
| 288 | - D.addClass(D.span(), 'button', 'down'), | |
| 289 | - D.span(/*glyph holder*/) | |
| 290 | - ); | |
| 281 | + btnDown = this.createButton(1); | |
| 291 | 282 | } |
| 292 | 283 | if(this.pos.next){ |
| 293 | - btnUp = D.append( | |
| 294 | - D.addClass(D.span(), 'button', 'up'), | |
| 295 | - D.span(/*glyph holder*/) | |
| 296 | - ); | |
| 297 | - } | |
| 298 | - } | |
| 299 | - if(btnDown){ | |
| 300 | - D.append(this.e.btnWrapper, btnDown); | |
| 301 | - btnDown.addEventListener('click', ()=>this.fetchChunk(1),false); | |
| 302 | - } | |
| 303 | - if(btnUp){ | |
| 304 | - D.append(this.e.btnWrapper, btnUp); | |
| 305 | - btnUp.addEventListener( | |
| 306 | - 'click', ()=>this.fetchChunk(btnUp.classList.contains('down') ? 0 : -1), | |
| 307 | - false); | |
| 308 | - } | |
| 284 | + btnUp = this.createButton(-1); | |
| 285 | + } | |
| 286 | + } | |
| 287 | + if(btnDown) D.append(this.e.btnWrapper, btnDown); | |
| 288 | + if(btnUp) D.append(this.e.btnWrapper, btnUp); | |
| 309 | 289 | /* For debugging only... */ |
| 310 | 290 | this.e.posState = D.span(); |
| 311 | 291 | D.append(this.e.btnWrapper, this.e.posState); |
| 312 | 292 | this.updatePosDebug(); |
| 313 | 293 | }; |
| @@ -317,56 +297,59 @@ | ||
| 317 | 297 | /* |
| 318 | 298 | glyphUp: '⇡', //'&#uarr;', |
| 319 | 299 | glyphDown: '⇣' //'&#darr;' |
| 320 | 300 | */ |
| 321 | 301 | }, |
| 302 | + | |
| 303 | + /** | |
| 304 | + Creates and returns a button element for fetching a chunk in | |
| 305 | + the given direction (as documented for fetchChunk()). | |
| 306 | + */ | |
| 307 | + createButton: function(direction){ | |
| 308 | + let b; | |
| 309 | + switch(direction){ | |
| 310 | + case 1: | |
| 311 | + b = D.append( | |
| 312 | + D.addClass(D.span(), 'button', 'down'), | |
| 313 | + D.span(/*glyph holder*/) | |
| 314 | + ); | |
| 315 | + break; | |
| 316 | + case 0: | |
| 317 | + b = D.append( | |
| 318 | + D.addClass(D.span(), 'button', 'up', 'down'), | |
| 319 | + D.span(/*glyph holder*/) | |
| 320 | + ); | |
| 321 | + break; | |
| 322 | + case -1: | |
| 323 | + b = D.append( | |
| 324 | + D.addClass(D.span(), 'button', 'up'), | |
| 325 | + D.span(/*glyph holder*/) | |
| 326 | + ); | |
| 327 | + break; | |
| 328 | + default: | |
| 329 | + throw new Error("Internal API misuse: unexpected direction value "+direction); | |
| 330 | + } | |
| 331 | + b.addEventListener('click', ()=>this.fetchChunk(direction),false); | |
| 332 | + return b; | |
| 333 | + }, | |
| 334 | + | |
| 322 | 335 | updatePosDebug: function(){ |
| 323 | 336 | if(this.e.posState){ |
| 324 | 337 | D.append(D.clearElement(this.e.posState), JSON.stringify(this.pos)); |
| 325 | 338 | } |
| 326 | 339 | return this; |
| 327 | 340 | }, |
| 328 | - | |
| 341 | + | |
| 342 | + /* Attempt to clean up resources and remove some circular references to | |
| 343 | + that GC can do the right thing. */ | |
| 329 | 344 | destroy: function(){ |
| 330 | 345 | D.remove(this.e.tr); |
| 331 | 346 | delete this.e.tr.$chunker; |
| 332 | 347 | delete this.e.tr; |
| 333 | 348 | delete this.e; |
| 334 | 349 | delete this.pos; |
| 335 | 350 | }, |
| 336 | - /** | |
| 337 | - Creates a new TR element, including its TD elements (depending | |
| 338 | - on this.isSplit), but does not fill it with any information nor | |
| 339 | - inject it into the table (it doesn't know where to do | |
| 340 | - so). Returns an object containing the TR element and various TD | |
| 341 | - elements which will likely be needed by the routine which | |
| 342 | - called this. See this code for details. | |
| 343 | - */ | |
| 344 | - newTR: function(){ | |
| 345 | - const tr = D.tr(), rc = { | |
| 346 | - tr, | |
| 347 | - preLnL: D.pre(), | |
| 348 | - preLnR: D.pre(), | |
| 349 | - preSep: D.pre() | |
| 350 | - }; | |
| 351 | - if(this.isSplit){ | |
| 352 | - D.append(D.addClass( D.td(tr), 'diffln', 'difflnl' ), rc.preLnL); | |
| 353 | - rc.preTxtL = D.pre(); | |
| 354 | - D.append(D.addClass( D.td(tr), 'difftxt', 'difftxtl' ), rc.preTxtL); | |
| 355 | - D.append(D.addClass( D.td(tr), 'diffsep' ), rc.preSep); | |
| 356 | - D.append(D.addClass( D.td(tr), 'diffln', 'difflnr' ), rc.preLnR); | |
| 357 | - rc.preTxtR = D.pre(); | |
| 358 | - D.append(D.addClass( D.td(tr), 'difftxt', 'difftxtr' ), rc.preTxtR); | |
| 359 | - }else{ | |
| 360 | - D.append(D.addClass( D.td(tr), 'diffln', 'difflnl' ), rc.preLnL); | |
| 361 | - D.append(D.addClass( D.td(tr), 'diffln', 'difflnr' ), rc.preLnR); | |
| 362 | - D.append(D.addClass( D.td(tr), 'diffsep' ), rc.preSep); | |
| 363 | - rc.preTxtU = D.pre(); | |
| 364 | - D.append(D.addClass( D.td(tr), 'difftxt', 'difftxtu' ), rc.preTxtU); | |
| 365 | - } | |
| 366 | - return rc; | |
| 367 | - }, | |
| 368 | 351 | |
| 369 | 352 | injectResponse: function(direction/*as for fetchChunk()*/, |
| 370 | 353 | urlParam/*from fetchChunk()*/, |
| 371 | 354 | lines/*response lines*/){ |
| 372 | 355 | console.debug("Loading line range ",urlParam.from,"-",urlParam.to); |
| @@ -464,45 +447,66 @@ | ||
| 464 | 447 | console.debug("TODO: handle load of partial next/prev"); |
| 465 | 448 | this.updatePosDebug(); |
| 466 | 449 | } |
| 467 | 450 | }, |
| 468 | 451 | |
| 469 | - fetchChunk: function(direction/*-1=down from prev chunk, | |
| 470 | - 1=up from next chunk, | |
| 471 | - 0=full-gap filler for any neighoring | |
| 472 | - chunk(s)*/){ | |
| 452 | + /** | |
| 453 | + Fetches and inserts a line chunk. direction is: | |
| 454 | + | |
| 455 | + -1 = upwards from next chunk (this.pos.next) | |
| 456 | + | |
| 457 | + 0 = the whole gap between this.pos.prev and this.pos.next, or | |
| 458 | + the whole gap before/after the initial/final chunk in the diff. | |
| 459 | + | |
| 460 | + 1 = downwards from the previous chunk (this.pos.prev) | |
| 461 | + | |
| 462 | + Those values are set at the time this object is initialized but | |
| 463 | + one instance of this class may have 2 buttons, one each for | |
| 464 | + directions -1 and 1. | |
| 465 | + | |
| 466 | + This is an async operation. While it is in transit, any calls | |
| 467 | + to this function will have no effect except (possibly) to emit | |
| 468 | + a warning. Returns this object. | |
| 469 | + */ | |
| 470 | + fetchChunk: function(direction){ | |
| 473 | 471 | /* Forewarning, this is a bit confusing: when fetching the |
| 474 | 472 | previous lines, we're doing so on behalf of the *next* diff |
| 475 | 473 | chunk (this.pos.next), and vice versa. */ |
| 476 | 474 | if(this.$isFetching){ |
| 477 | - console.debug("Cannot load chunk while a load is pending."); | |
| 475 | + F.toast.warning("Cannot load chunk while a load is pending."); | |
| 478 | 476 | return this; |
| 479 | 477 | } |
| 480 | - if(direction<0/*prev chunk*/ && !this.pos.next){ | |
| 481 | - console.error("Attempt to fetch previous diff lines but don't have any."); | |
| 482 | - return this; | |
| 483 | - }else if(direction>0/*next chunk*/ && !this.pos.prev){ | |
| 484 | - console.error("Attempt to fetch next diff lines but don't have any."); | |
| 478 | + if(direction<0 && !this.pos.next | |
| 479 | + || direction>0 && !this.pos.prev){ | |
| 480 | + console.error("Attempt to fetch diff lines but don't have any."); | |
| 485 | 481 | return this; |
| 486 | 482 | } |
| 487 | 483 | const fOpt = { |
| 488 | 484 | urlParams:{ |
| 489 | 485 | name: this.fileHash, from: 0, to: 0 |
| 490 | 486 | }, |
| 491 | - aftersend: ()=>delete this.$isFetching | |
| 487 | + aftersend: ()=>delete this.$isFetching, | |
| 488 | + onload: (list)=>this.injectResponse(direction,up,list) | |
| 492 | 489 | }; |
| 493 | - const self = this; | |
| 494 | - if(direction!=0){ | |
| 495 | - console.debug("Skipping partial fetch for now."); | |
| 490 | + const up = fOpt.urlParams; | |
| 491 | + if(direction===0){ | |
| 492 | + up.from = this.pos.startLhs; | |
| 493 | + up.to = this.pos.endLhs; | |
| 494 | + }else if(1===direction){ | |
| 495 | + /* Expand previous TR downwards. */ | |
| 496 | + if(!this.pos.prev){ | |
| 497 | + console.error("Attempt to fetch next diff lines but don't have any."); | |
| 498 | + return this; | |
| 499 | + } | |
| 500 | + console.debug("fetchChunk(",direction,")"); | |
| 496 | 501 | return this; |
| 497 | 502 | }else{ |
| 498 | - fOpt.urlParams.from = this.pos.startLhs; | |
| 499 | - fOpt.urlParams.to = this.pos.endLhs; | |
| 500 | - fOpt.onload = function(list){ | |
| 501 | - self.injectResponse(direction,fOpt.urlParams,list); | |
| 502 | - }; | |
| 503 | + /* Expand next TR upwards */ | |
| 504 | + console.debug("fetchChunk(",direction,")"); | |
| 505 | + return this; | |
| 503 | 506 | } |
| 507 | + | |
| 504 | 508 | this.$isFetching = true; |
| 505 | 509 | Diff.fetchArtifactChunk(fOpt); |
| 506 | 510 | return this; |
| 507 | 511 | } |
| 508 | 512 | }; |
| @@ -512,13 +516,12 @@ | ||
| 512 | 516 | /* Potential performance-related TODO: instead of installing all |
| 513 | 517 | of these at once, install them as the corresponding TR is |
| 514 | 518 | scrolled into view. */ |
| 515 | 519 | tables.forEach(function(table){ |
| 516 | 520 | D.addClass(table, 'diffskipped'/*avoid processing these more than once */); |
| 517 | - const isSplit = table.classList.contains('splitdiff')/*else udiff*/; | |
| 518 | 521 | table.querySelectorAll('tr.diffskip[data-startln]').forEach(function(tr){ |
| 519 | - new Diff.ChunkLoadControls(isSplit, D.addClass(tr, 'jchunk')); | |
| 522 | + new Diff.ChunkLoadControls(D.addClass(tr, 'jchunk')); | |
| 520 | 523 | }); |
| 521 | 524 | }); |
| 522 | 525 | return F; |
| 523 | 526 | }; |
| 524 | 527 | Diff.addDiffSkipHandlers(); |
| 525 | 528 |
| --- src/fossil.diff.js | |
| +++ src/fossil.diff.js | |
| @@ -204,30 +204,27 @@ | |
| 204 | } |
| 205 | }); |
| 206 | }; |
| 207 | |
| 208 | /** |
| 209 | Installs chunk-loading controls into TR element tr. isSplit is true |
| 210 | if the parent table is a split diff, else false. |
| 211 | |
| 212 | The goal is to base these controls closely on github's, a good example |
| 213 | of which, for use as a model, is: |
| 214 | |
| 215 | https://github.com/msteveb/autosetup/commit/235925e914a52a542 |
| 216 | |
| 217 | Each instance corresponds to a single TR.diffskip element. |
| 218 | */ |
| 219 | Diff.ChunkLoadControls = function(isSplit, tr){ |
| 220 | this.isSplit = isSplit; |
| 221 | this.e = {/*DOM elements*/ |
| 222 | tr: tr, |
| 223 | table: tr.parentElement/*TBODY*/.parentElement |
| 224 | }; |
| 225 | this.fileHash = this.e.table.dataset.lefthash; |
| 226 | tr.$chunker = this /* keep GC from reaping this */; |
| 227 | this.pos = { |
| 228 | //hash: F.hashDigits(this.fileHash), |
| 229 | /* These line numbers correspond to the LHS file. Because the |
| 230 | contents are common to both sides, we have the same number |
| 231 | for the RHS, but need to extract those line numbers from the |
| 232 | neighboring TR blocks */ |
| 233 | startLhs: +tr.dataset.startln, |
| @@ -234,11 +231,11 @@ | |
| 234 | endLhs: +tr.dataset.endln |
| 235 | }; |
| 236 | D.clearElement(tr); |
| 237 | this.e.td = D.addClass( |
| 238 | /* Holder for our UI controls */ |
| 239 | D.attr(D.td(tr), 'colspan', isSplit ? 5 : 4), |
| 240 | 'chunkctrl' |
| 241 | ); |
| 242 | this.e.btnWrapper = D.div(); |
| 243 | D.append(this.e.td, this.e.btnWrapper); |
| 244 | /** |
| @@ -252,18 +249,18 @@ | |
| 252 | |
| 253 | - A single button to load the final chunk incrementally |
| 254 | */ |
| 255 | if(tr.nextElementSibling){ |
| 256 | this.pos.next = { |
| 257 | startLhs: extractLineNo(true, true, tr.nextElementSibling, isSplit), |
| 258 | startRhs: extractLineNo(false, true, tr.nextElementSibling, isSplit) |
| 259 | }; |
| 260 | } |
| 261 | if(tr.previousElementSibling){ |
| 262 | this.pos.prev = { |
| 263 | endLhs: extractLineNo(true, false, tr.previousElementSibling, isSplit), |
| 264 | endRhs: extractLineNo(false, false, tr.previousElementSibling, isSplit) |
| 265 | }; |
| 266 | } |
| 267 | let btnUp = false, btnDown = false; |
| 268 | /** |
| 269 | this.pos.next refers to the line numbers in the next TR's chunk. |
| @@ -275,39 +272,22 @@ | |
| 275 | && ((this.pos.next.startLhs - this.pos.prev.endLhs) |
| 276 | <= Diff.config.chunkLoadLines))){ |
| 277 | /* Place a single button to load the whole block, rather |
| 278 | than separate up/down buttons. */ |
| 279 | btnDown = false; |
| 280 | btnUp = D.append( |
| 281 | D.addClass(D.span(), 'button', 'up', 'down'), |
| 282 | D.span(/*glyph holder*/) |
| 283 | ); |
| 284 | }else{ |
| 285 | /* Figure out which chunk-load buttons to add... */ |
| 286 | if(this.pos.prev){ |
| 287 | btnDown = D.append( |
| 288 | D.addClass(D.span(), 'button', 'down'), |
| 289 | D.span(/*glyph holder*/) |
| 290 | ); |
| 291 | } |
| 292 | if(this.pos.next){ |
| 293 | btnUp = D.append( |
| 294 | D.addClass(D.span(), 'button', 'up'), |
| 295 | D.span(/*glyph holder*/) |
| 296 | ); |
| 297 | } |
| 298 | } |
| 299 | if(btnDown){ |
| 300 | D.append(this.e.btnWrapper, btnDown); |
| 301 | btnDown.addEventListener('click', ()=>this.fetchChunk(1),false); |
| 302 | } |
| 303 | if(btnUp){ |
| 304 | D.append(this.e.btnWrapper, btnUp); |
| 305 | btnUp.addEventListener( |
| 306 | 'click', ()=>this.fetchChunk(btnUp.classList.contains('down') ? 0 : -1), |
| 307 | false); |
| 308 | } |
| 309 | /* For debugging only... */ |
| 310 | this.e.posState = D.span(); |
| 311 | D.append(this.e.btnWrapper, this.e.posState); |
| 312 | this.updatePosDebug(); |
| 313 | }; |
| @@ -317,56 +297,59 @@ | |
| 317 | /* |
| 318 | glyphUp: '⇡', //'&#uarr;', |
| 319 | glyphDown: '⇣' //'&#darr;' |
| 320 | */ |
| 321 | }, |
| 322 | updatePosDebug: function(){ |
| 323 | if(this.e.posState){ |
| 324 | D.append(D.clearElement(this.e.posState), JSON.stringify(this.pos)); |
| 325 | } |
| 326 | return this; |
| 327 | }, |
| 328 | |
| 329 | destroy: function(){ |
| 330 | D.remove(this.e.tr); |
| 331 | delete this.e.tr.$chunker; |
| 332 | delete this.e.tr; |
| 333 | delete this.e; |
| 334 | delete this.pos; |
| 335 | }, |
| 336 | /** |
| 337 | Creates a new TR element, including its TD elements (depending |
| 338 | on this.isSplit), but does not fill it with any information nor |
| 339 | inject it into the table (it doesn't know where to do |
| 340 | so). Returns an object containing the TR element and various TD |
| 341 | elements which will likely be needed by the routine which |
| 342 | called this. See this code for details. |
| 343 | */ |
| 344 | newTR: function(){ |
| 345 | const tr = D.tr(), rc = { |
| 346 | tr, |
| 347 | preLnL: D.pre(), |
| 348 | preLnR: D.pre(), |
| 349 | preSep: D.pre() |
| 350 | }; |
| 351 | if(this.isSplit){ |
| 352 | D.append(D.addClass( D.td(tr), 'diffln', 'difflnl' ), rc.preLnL); |
| 353 | rc.preTxtL = D.pre(); |
| 354 | D.append(D.addClass( D.td(tr), 'difftxt', 'difftxtl' ), rc.preTxtL); |
| 355 | D.append(D.addClass( D.td(tr), 'diffsep' ), rc.preSep); |
| 356 | D.append(D.addClass( D.td(tr), 'diffln', 'difflnr' ), rc.preLnR); |
| 357 | rc.preTxtR = D.pre(); |
| 358 | D.append(D.addClass( D.td(tr), 'difftxt', 'difftxtr' ), rc.preTxtR); |
| 359 | }else{ |
| 360 | D.append(D.addClass( D.td(tr), 'diffln', 'difflnl' ), rc.preLnL); |
| 361 | D.append(D.addClass( D.td(tr), 'diffln', 'difflnr' ), rc.preLnR); |
| 362 | D.append(D.addClass( D.td(tr), 'diffsep' ), rc.preSep); |
| 363 | rc.preTxtU = D.pre(); |
| 364 | D.append(D.addClass( D.td(tr), 'difftxt', 'difftxtu' ), rc.preTxtU); |
| 365 | } |
| 366 | return rc; |
| 367 | }, |
| 368 | |
| 369 | injectResponse: function(direction/*as for fetchChunk()*/, |
| 370 | urlParam/*from fetchChunk()*/, |
| 371 | lines/*response lines*/){ |
| 372 | console.debug("Loading line range ",urlParam.from,"-",urlParam.to); |
| @@ -464,45 +447,66 @@ | |
| 464 | console.debug("TODO: handle load of partial next/prev"); |
| 465 | this.updatePosDebug(); |
| 466 | } |
| 467 | }, |
| 468 | |
| 469 | fetchChunk: function(direction/*-1=down from prev chunk, |
| 470 | 1=up from next chunk, |
| 471 | 0=full-gap filler for any neighoring |
| 472 | chunk(s)*/){ |
| 473 | /* Forewarning, this is a bit confusing: when fetching the |
| 474 | previous lines, we're doing so on behalf of the *next* diff |
| 475 | chunk (this.pos.next), and vice versa. */ |
| 476 | if(this.$isFetching){ |
| 477 | console.debug("Cannot load chunk while a load is pending."); |
| 478 | return this; |
| 479 | } |
| 480 | if(direction<0/*prev chunk*/ && !this.pos.next){ |
| 481 | console.error("Attempt to fetch previous diff lines but don't have any."); |
| 482 | return this; |
| 483 | }else if(direction>0/*next chunk*/ && !this.pos.prev){ |
| 484 | console.error("Attempt to fetch next diff lines but don't have any."); |
| 485 | return this; |
| 486 | } |
| 487 | const fOpt = { |
| 488 | urlParams:{ |
| 489 | name: this.fileHash, from: 0, to: 0 |
| 490 | }, |
| 491 | aftersend: ()=>delete this.$isFetching |
| 492 | }; |
| 493 | const self = this; |
| 494 | if(direction!=0){ |
| 495 | console.debug("Skipping partial fetch for now."); |
| 496 | return this; |
| 497 | }else{ |
| 498 | fOpt.urlParams.from = this.pos.startLhs; |
| 499 | fOpt.urlParams.to = this.pos.endLhs; |
| 500 | fOpt.onload = function(list){ |
| 501 | self.injectResponse(direction,fOpt.urlParams,list); |
| 502 | }; |
| 503 | } |
| 504 | this.$isFetching = true; |
| 505 | Diff.fetchArtifactChunk(fOpt); |
| 506 | return this; |
| 507 | } |
| 508 | }; |
| @@ -512,13 +516,12 @@ | |
| 512 | /* Potential performance-related TODO: instead of installing all |
| 513 | of these at once, install them as the corresponding TR is |
| 514 | scrolled into view. */ |
| 515 | tables.forEach(function(table){ |
| 516 | D.addClass(table, 'diffskipped'/*avoid processing these more than once */); |
| 517 | const isSplit = table.classList.contains('splitdiff')/*else udiff*/; |
| 518 | table.querySelectorAll('tr.diffskip[data-startln]').forEach(function(tr){ |
| 519 | new Diff.ChunkLoadControls(isSplit, D.addClass(tr, 'jchunk')); |
| 520 | }); |
| 521 | }); |
| 522 | return F; |
| 523 | }; |
| 524 | Diff.addDiffSkipHandlers(); |
| 525 |
| --- src/fossil.diff.js | |
| +++ src/fossil.diff.js | |
| @@ -204,30 +204,27 @@ | |
| 204 | } |
| 205 | }); |
| 206 | }; |
| 207 | |
| 208 | /** |
| 209 | Installs chunk-loading controls into TR.diffskip element tr. |
| 210 | Each instance corresponds to a single TR.diffskip element. |
| 211 | |
| 212 | The goal is to base these controls roughly on github's, a good |
| 213 | example of which, for use as a model, is: |
| 214 | |
| 215 | https://github.com/msteveb/autosetup/commit/235925e914a52a542 |
| 216 | */ |
| 217 | Diff.ChunkLoadControls = function(tr){ |
| 218 | this.e = {/*DOM elements*/ |
| 219 | tr: tr, |
| 220 | table: tr.parentElement/*TBODY*/.parentElement |
| 221 | }; |
| 222 | this.isSplit = this.e.table.classList.contains('splitdiff')/*else udiff*/; |
| 223 | this.fileHash = this.e.table.dataset.lefthash; |
| 224 | tr.$chunker = this /* keep GC from reaping this */; |
| 225 | this.pos = { |
| 226 | /* These line numbers correspond to the LHS file. Because the |
| 227 | contents are common to both sides, we have the same number |
| 228 | for the RHS, but need to extract those line numbers from the |
| 229 | neighboring TR blocks */ |
| 230 | startLhs: +tr.dataset.startln, |
| @@ -234,11 +231,11 @@ | |
| 231 | endLhs: +tr.dataset.endln |
| 232 | }; |
| 233 | D.clearElement(tr); |
| 234 | this.e.td = D.addClass( |
| 235 | /* Holder for our UI controls */ |
| 236 | D.attr(D.td(tr), 'colspan', this.isSplit ? 5 : 4), |
| 237 | 'chunkctrl' |
| 238 | ); |
| 239 | this.e.btnWrapper = D.div(); |
| 240 | D.append(this.e.td, this.e.btnWrapper); |
| 241 | /** |
| @@ -252,18 +249,18 @@ | |
| 249 | |
| 250 | - A single button to load the final chunk incrementally |
| 251 | */ |
| 252 | if(tr.nextElementSibling){ |
| 253 | this.pos.next = { |
| 254 | startLhs: extractLineNo(true, true, tr.nextElementSibling, this.isSplit), |
| 255 | startRhs: extractLineNo(false, true, tr.nextElementSibling, this.isSplit) |
| 256 | }; |
| 257 | } |
| 258 | if(tr.previousElementSibling){ |
| 259 | this.pos.prev = { |
| 260 | endLhs: extractLineNo(true, false, tr.previousElementSibling, this.isSplit), |
| 261 | endRhs: extractLineNo(false, false, tr.previousElementSibling, this.isSplit) |
| 262 | }; |
| 263 | } |
| 264 | let btnUp = false, btnDown = false; |
| 265 | /** |
| 266 | this.pos.next refers to the line numbers in the next TR's chunk. |
| @@ -275,39 +272,22 @@ | |
| 272 | && ((this.pos.next.startLhs - this.pos.prev.endLhs) |
| 273 | <= Diff.config.chunkLoadLines))){ |
| 274 | /* Place a single button to load the whole block, rather |
| 275 | than separate up/down buttons. */ |
| 276 | btnDown = false; |
| 277 | btnUp = this.createButton(0); |
| 278 | }else{ |
| 279 | /* Figure out which chunk-load buttons to add... */ |
| 280 | if(this.pos.prev){ |
| 281 | btnDown = this.createButton(1); |
| 282 | } |
| 283 | if(this.pos.next){ |
| 284 | btnUp = this.createButton(-1); |
| 285 | } |
| 286 | } |
| 287 | if(btnDown) D.append(this.e.btnWrapper, btnDown); |
| 288 | if(btnUp) D.append(this.e.btnWrapper, btnUp); |
| 289 | /* For debugging only... */ |
| 290 | this.e.posState = D.span(); |
| 291 | D.append(this.e.btnWrapper, this.e.posState); |
| 292 | this.updatePosDebug(); |
| 293 | }; |
| @@ -317,56 +297,59 @@ | |
| 297 | /* |
| 298 | glyphUp: '⇡', //'&#uarr;', |
| 299 | glyphDown: '⇣' //'&#darr;' |
| 300 | */ |
| 301 | }, |
| 302 | |
| 303 | /** |
| 304 | Creates and returns a button element for fetching a chunk in |
| 305 | the given direction (as documented for fetchChunk()). |
| 306 | */ |
| 307 | createButton: function(direction){ |
| 308 | let b; |
| 309 | switch(direction){ |
| 310 | case 1: |
| 311 | b = D.append( |
| 312 | D.addClass(D.span(), 'button', 'down'), |
| 313 | D.span(/*glyph holder*/) |
| 314 | ); |
| 315 | break; |
| 316 | case 0: |
| 317 | b = D.append( |
| 318 | D.addClass(D.span(), 'button', 'up', 'down'), |
| 319 | D.span(/*glyph holder*/) |
| 320 | ); |
| 321 | break; |
| 322 | case -1: |
| 323 | b = D.append( |
| 324 | D.addClass(D.span(), 'button', 'up'), |
| 325 | D.span(/*glyph holder*/) |
| 326 | ); |
| 327 | break; |
| 328 | default: |
| 329 | throw new Error("Internal API misuse: unexpected direction value "+direction); |
| 330 | } |
| 331 | b.addEventListener('click', ()=>this.fetchChunk(direction),false); |
| 332 | return b; |
| 333 | }, |
| 334 | |
| 335 | updatePosDebug: function(){ |
| 336 | if(this.e.posState){ |
| 337 | D.append(D.clearElement(this.e.posState), JSON.stringify(this.pos)); |
| 338 | } |
| 339 | return this; |
| 340 | }, |
| 341 | |
| 342 | /* Attempt to clean up resources and remove some circular references to |
| 343 | that GC can do the right thing. */ |
| 344 | destroy: function(){ |
| 345 | D.remove(this.e.tr); |
| 346 | delete this.e.tr.$chunker; |
| 347 | delete this.e.tr; |
| 348 | delete this.e; |
| 349 | delete this.pos; |
| 350 | }, |
| 351 | |
| 352 | injectResponse: function(direction/*as for fetchChunk()*/, |
| 353 | urlParam/*from fetchChunk()*/, |
| 354 | lines/*response lines*/){ |
| 355 | console.debug("Loading line range ",urlParam.from,"-",urlParam.to); |
| @@ -464,45 +447,66 @@ | |
| 447 | console.debug("TODO: handle load of partial next/prev"); |
| 448 | this.updatePosDebug(); |
| 449 | } |
| 450 | }, |
| 451 | |
| 452 | /** |
| 453 | Fetches and inserts a line chunk. direction is: |
| 454 | |
| 455 | -1 = upwards from next chunk (this.pos.next) |
| 456 | |
| 457 | 0 = the whole gap between this.pos.prev and this.pos.next, or |
| 458 | the whole gap before/after the initial/final chunk in the diff. |
| 459 | |
| 460 | 1 = downwards from the previous chunk (this.pos.prev) |
| 461 | |
| 462 | Those values are set at the time this object is initialized but |
| 463 | one instance of this class may have 2 buttons, one each for |
| 464 | directions -1 and 1. |
| 465 | |
| 466 | This is an async operation. While it is in transit, any calls |
| 467 | to this function will have no effect except (possibly) to emit |
| 468 | a warning. Returns this object. |
| 469 | */ |
| 470 | fetchChunk: function(direction){ |
| 471 | /* Forewarning, this is a bit confusing: when fetching the |
| 472 | previous lines, we're doing so on behalf of the *next* diff |
| 473 | chunk (this.pos.next), and vice versa. */ |
| 474 | if(this.$isFetching){ |
| 475 | F.toast.warning("Cannot load chunk while a load is pending."); |
| 476 | return this; |
| 477 | } |
| 478 | if(direction<0 && !this.pos.next |
| 479 | || direction>0 && !this.pos.prev){ |
| 480 | console.error("Attempt to fetch diff lines but don't have any."); |
| 481 | return this; |
| 482 | } |
| 483 | const fOpt = { |
| 484 | urlParams:{ |
| 485 | name: this.fileHash, from: 0, to: 0 |
| 486 | }, |
| 487 | aftersend: ()=>delete this.$isFetching, |
| 488 | onload: (list)=>this.injectResponse(direction,up,list) |
| 489 | }; |
| 490 | const up = fOpt.urlParams; |
| 491 | if(direction===0){ |
| 492 | up.from = this.pos.startLhs; |
| 493 | up.to = this.pos.endLhs; |
| 494 | }else if(1===direction){ |
| 495 | /* Expand previous TR downwards. */ |
| 496 | if(!this.pos.prev){ |
| 497 | console.error("Attempt to fetch next diff lines but don't have any."); |
| 498 | return this; |
| 499 | } |
| 500 | console.debug("fetchChunk(",direction,")"); |
| 501 | return this; |
| 502 | }else{ |
| 503 | /* Expand next TR upwards */ |
| 504 | console.debug("fetchChunk(",direction,")"); |
| 505 | return this; |
| 506 | } |
| 507 | |
| 508 | this.$isFetching = true; |
| 509 | Diff.fetchArtifactChunk(fOpt); |
| 510 | return this; |
| 511 | } |
| 512 | }; |
| @@ -512,13 +516,12 @@ | |
| 516 | /* Potential performance-related TODO: instead of installing all |
| 517 | of these at once, install them as the corresponding TR is |
| 518 | scrolled into view. */ |
| 519 | tables.forEach(function(table){ |
| 520 | D.addClass(table, 'diffskipped'/*avoid processing these more than once */); |
| 521 | table.querySelectorAll('tr.diffskip[data-startln]').forEach(function(tr){ |
| 522 | new Diff.ChunkLoadControls(D.addClass(tr, 'jchunk')); |
| 523 | }); |
| 524 | }); |
| 525 | return F; |
| 526 | }; |
| 527 | Diff.addDiffSkipHandlers(); |
| 528 |