overview.html (51596B)
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1" /> 6 <title>Overview - Web Platform Test</title> 7 <link rel="stylesheet" href="css/bulma-0.7.5/bulma.min.css" /> 8 <link rel="stylesheet" href="css/fontawesome-5.7.2.min.css" /> 9 <script src="lib/utils.js"></script> 10 <script src="lib/wave-service.js"></script> 11 <script src="lib/ui.js"></script> 12 <style> 13 .site-logo { 14 max-width: 300px; 15 margin: 0 0 30px -15px; 16 } 17 18 .disabled-row { 19 color: gray; 20 background: lightgray; 21 } 22 </style> 23 </head> 24 <body> 25 <script> 26 window.onload = () => { 27 const query = utils.parseQuery(location.search); 28 if (query.token) { 29 location.href = WEB_ROOT + "results.html" + location.search; 30 } 31 resultsUi.render(); 32 resultsUi.loadData(); 33 }; 34 var sortDetail = {}; 35 const defaultSortDetail = { sortColumn: "dateStarted", ascending: true }; 36 sortDetail["recentSessions"] = defaultSortDetail; 37 sortDetail["pinnedSessions"] = defaultSortDetail; 38 sortDetail["publicSessions"] = defaultSortDetail; 39 40 const resultsUi = { 41 state: { 42 comparison: [], 43 recentSessions: null, 44 importResultsEnabled: false, 45 filterLabels: [] 46 }, 47 loadData() { 48 const pinnedSessions = WaveService.getPinnedSessions().filter( 49 token => !!token 50 ); 51 const recentSessions = WaveService.getRecentSessions().filter( 52 token => !!token 53 ); 54 55 pinnedSessions.forEach(token => { 56 const index = recentSessions.indexOf(token); 57 if (index !== -1) recentSessions.splice(index, 1); 58 }); 59 WaveService.setRecentSessions(recentSessions); 60 61 let allSessions = []; 62 allSessions = allSessions.concat(pinnedSessions); 63 allSessions = allSessions.concat(recentSessions); 64 65 WaveService.readPublicSessions(publicSessions => { 66 publicSessions.forEach(token => { 67 const index = recentSessions.indexOf(token); 68 if (index !== -1) recentSessions.splice(index, 1); 69 }); 70 WaveService.setRecentSessions(recentSessions); 71 allSessions = allSessions.concat(publicSessions); 72 WaveService.readMultipleSessions(allSessions, configurations => 73 WaveService.readMultipleSessionStatuses(allSessions, statuses => { 74 configurations.forEach(configuration => { 75 const status = statuses.find( 76 status => status.token === configuration.token 77 ); 78 configuration.dateStarted = status.dateStarted; 79 configuration.dateFinished = status.dateFinished; 80 configuration.status = status.status; 81 }); 82 83 configurations = configurations.filter( 84 configuration => !!configuration 85 ); 86 allSessions 87 .filter( 88 token => 89 !configurations.some( 90 configuration => configuration.token === token 91 ) 92 ) 93 .forEach(token => { 94 WaveService.removePinnedSession(token); 95 WaveService.removeRecentSession(token); 96 }); 97 resultsUi.state.publicSessions = publicSessions; 98 resultsUi.state.pinnedSessions = WaveService.getPinnedSessions(); 99 resultsUi.state.recentSessions = WaveService.getRecentSessions(); 100 101 const sessions = {}; 102 configurations.forEach( 103 configuration => 104 (sessions[configuration.token] = configuration) 105 ); 106 resultsUi.state.sessions = sessions; 107 108 const referenceTokens = []; 109 const loadedSessionsTokens = Object.keys(sessions); 110 configurations.forEach(configuration => 111 configuration.referenceTokens 112 .filter(token => loadedSessionsTokens.indexOf(token) === -1) 113 .forEach(token => referenceTokens.push(token)) 114 ); 115 WaveService.readMultipleSessions( 116 referenceTokens, 117 configurations => { 118 const { sessions } = resultsUi.state; 119 configurations.forEach( 120 configuration => 121 (sessions[configuration.token] = configuration) 122 ); 123 resultsUi.renderPublicSessions(); 124 resultsUi.renderPinnedSessions(); 125 resultsUi.renderRecentSessions(); 126 } 127 ); 128 }) 129 ); 130 }); 131 WaveService.readStatus(function(config) { 132 resultsUi.state.importResultsEnabled = config.importResultsEnabled; 133 resultsUi.state.reportsEnabled = config.reportsEnabled; 134 resultsUi.renderManageSessions(); 135 }); 136 }, 137 findSession(fragment, callback) { 138 if (!fragment || fragment.length < 8) return; 139 WaveService.findToken( 140 fragment, 141 token => { 142 WaveService.readSession(token, session => { 143 WaveService.readSessionStatus(token, status => { 144 session.status = status.status; 145 session.dateStarted = status.dateStarted; 146 session.dateFinished = status.dateFinished; 147 callback(session); 148 }); 149 }); 150 }, 151 () => callback(null) 152 ); 153 }, 154 addSession(session) { 155 const token = session.token; 156 if (resultsUi.state.sessions[token]) return; 157 resultsUi.state.sessions[token] = session; 158 resultsUi.pinSession(token); 159 }, 160 removeSession(token) { 161 delete resultsUi.state.sessions[token]; 162 WaveService.removeRecentSession(token); 163 WaveService.removePinnedSession(token); 164 resultsUi.updateSessionState(); 165 }, 166 showAddSessionError() { 167 const errorBox = UI.getElement("find-error"); 168 errorBox.setAttribute("style", "display: block"); 169 }, 170 hideAddSessionError() { 171 const errorBox = UI.getElement("find-error"); 172 errorBox.setAttribute("style", "display: none"); 173 }, 174 pinSession(token) { 175 WaveService.addPinnedSession(token); 176 WaveService.removeRecentSession(token); 177 resultsUi.updateSessionState(); 178 }, 179 unpinSession(token) { 180 WaveService.removePinnedSession(token); 181 WaveService.addRecentSession(token); 182 resultsUi.updateSessionState(); 183 }, 184 updateSessionState() { 185 resultsUi.state.pinnedSessions = WaveService.getPinnedSessions(); 186 resultsUi.state.recentSessions = WaveService.getRecentSessions(); 187 resultsUi.renderPinnedSessions(); 188 resultsUi.renderRecentSessions(); 189 }, 190 openSessionResult(token) { 191 location.href = `${WEB_ROOT}results.html?token=${token}`; 192 }, 193 sortSessions(tableType, column) { 194 if (tableType in sortDetail) { 195 if (sortDetail[tableType].sortColumn == column) { 196 sortDetail[tableType].ascending = !sortDetail[tableType] 197 .ascending; 198 } else { 199 sortDetail[tableType].sortColumn = column; 200 sortDetail[tableType].ascending = true; 201 } 202 switch (tableType) { 203 case "recentSessions": 204 resultsUi.renderRecentSessions(); 205 break; 206 case "pinnedSessions": 207 resultsUi.renderPinnedSessions(); 208 break; 209 case "publicSessions": 210 resultsUi.renderPublicSessions(); 211 break; 212 } 213 } 214 }, 215 sortSessionsByColumn(sessions, recentSessions, column, ascending) { 216 var resultArray = recentSessions 217 .map(token => sessions[token]) 218 .sort(function(sessionA, sessionB) { 219 let columnA = sessionA[column]; 220 if (column === "browser") 221 columnA = sessionA[column].name + sessionA[column].version; 222 if (column === "dateStarted" && !columnA) { 223 columnA = Date.now(); 224 } 225 let columnB = sessionB[column]; 226 if (column === "browser") 227 columnB = sessionB[column].name + sessionA[column].version; 228 if (column === "dateStarted" && !columnB) { 229 columnB = Date.now(); 230 } 231 if (columnA < columnB) { 232 return -1; 233 } 234 if (columnA > columnB) { 235 return 1; 236 } 237 return 0; 238 }); 239 if (ascending) { 240 resultArray.reverse(); 241 } 242 return resultArray; 243 }, 244 compareSessions(reftokens) { 245 if (!resultsUi.isComparisonValid()) return; 246 const tokens = resultsUi.state.comparison; 247 if (!tokens || tokens.length === 0) return; 248 const refQuery = reftokens ? `&reftokens=${reftokens}` : ""; 249 location.href = `${WEB_ROOT}comparison.html?tokens=${tokens.join( 250 "," 251 )}${refQuery}`; 252 }, 253 isComparisonValid() { 254 const { comparison, sessions } = resultsUi.state; 255 if (!comparison) return false; 256 if (comparison.length <= 1) return false; 257 const comparingSessions = comparison.map(token => sessions[token]); 258 const referenceTokens = comparingSessions[0].referenceTokens; 259 for (let comparingSession of comparingSessions) { 260 const comparingReferenceTokens = comparingSession.referenceTokens; 261 if (referenceTokens.length !== comparingReferenceTokens.length) 262 return false; 263 for (let token of comparingReferenceTokens) { 264 if (referenceTokens.indexOf(token) === -1) return false; 265 } 266 } 267 return true; 268 }, 269 isSessionValidForComparison(session) { 270 if (!session) return false; 271 if (session.status !== "completed" && session.status !== "aborted") 272 return false; 273 const sessionRefTokens = session.reference_tokens; 274 const comparisonSession = 275 resultsUi.state.sessions[resultsUi.state.comparison[0]]; 276 if (!comparisonSession) return true; 277 const comparisonRefTokens = comparisonSession.reference_tokens; 278 if (!comparisonRefTokens) return true; 279 if (sessionRefTokens.length !== comparisonRefTokens.length) 280 return false; 281 if ( 282 sessionRefTokens.some( 283 token => comparisonRefTokens.indexOf(token) === -1 284 ) 285 ) 286 return false; 287 return true; 288 }, 289 isSessionSelectedForComparison(session) { 290 return resultsUi.state.comparison.indexOf(session.token) !== -1; 291 }, 292 isSessionDisabled(session) { 293 return ( 294 resultsUi.state.comparison.length > 0 && 295 !resultsUi.isSessionValidForComparison(session) 296 ); 297 }, 298 addSessionToComparison(token) { 299 if (resultsUi.state.comparison.indexOf(token) !== -1) return; 300 resultsUi.state.comparison.push(token); 301 resultsUi.updateCompareButton(); 302 resultsUi.renderSessions(); 303 }, 304 removeSessionFromComparison(token) { 305 const index = resultsUi.state.comparison.indexOf(token); 306 if (index === -1) return; 307 resultsUi.state.comparison.splice(index, 1); 308 resultsUi.updateCompareButton(); 309 resultsUi.renderSessions(); 310 }, 311 handleAddSession() { 312 const tokenFragmentInput = UI.getElement("token-fragment"); 313 const fragment = tokenFragmentInput.value; 314 resultsUi.findSession(fragment, session => { 315 if (!session) { 316 resultsUi.showAddSessionError(); 317 return; 318 } 319 tokenFragmentInput.value = ""; 320 resultsUi.hideAddSessionError(); 321 resultsUi.addSession(session); 322 }); 323 }, 324 handleImportSession() { 325 resultsUi.state.importError = null; 326 resultsUi.state.importInProgress = true; 327 resultsUi.renderManageSessions(); 328 const { importSessionFile: file } = resultsUi.state; 329 const reader = new FileReader(); 330 reader.readAsArrayBuffer(file); 331 reader.onload = () => { 332 const data = reader.result; 333 WaveService.importResults( 334 data, 335 function(token) { 336 location.href = WEB_ROOT + "results.html?token=" + token; 337 }, 338 function(error) { 339 resultsUi.state.importError = error; 340 resultsUi.state.importInProgress = false; 341 resultsUi.renderManageSessions(); 342 } 343 ); 344 }; 345 }, 346 handleImportSessionSelection() { 347 const file = UI.getElement("import-session-file").files[0]; 348 resultsUi.state.importSessionFile = file; 349 resultsUi.renderManageSessions(); 350 }, 351 addFilterLabel() { 352 const label = UI.getElement("filter-label-input").value; 353 if (!label) return; 354 const { filterLabels } = resultsUi.state; 355 if (filterLabels.indexOf(label) !== -1) return; 356 filterLabels.push(label); 357 resultsUi.renderSessions(); 358 UI.getElement("filter-label-input").focus(); 359 }, 360 removeFilterLabel(index) { 361 resultsUi.state.filterLabels.splice(index, 1); 362 resultsUi.renderSessions(); 363 }, 364 showAddFilterLabel() { 365 resultsUi.state.addFilterLabelVisible = true; 366 resultsUi.renderSessions(); 367 UI.getElement("filter-label-input").focus(); 368 }, 369 hideAddFilterLabel() { 370 resultsUi.state.addFilterLabelVisible = false; 371 resultsUi.renderSessions(); 372 }, 373 render() { 374 const { getRoot, createElement, getElement } = UI; 375 const resultsView = UI.createElement({ 376 className: "section", 377 children: [ 378 { 379 className: "container", 380 style: "margin-bottom: 2em", 381 children: [ 382 { 383 element: "img", 384 src: "res/wavelogo_2016.jpg", 385 className: "site-logo" 386 }, 387 { text: "Results Overview", className: "title" } 388 ] 389 }, 390 { 391 id: "manage-sessions", 392 className: "container", 393 style: "margin-bottom: 2em" 394 }, 395 { id: "sessions", className: "container" } 396 ] 397 }); 398 399 const root = UI.getRoot(); 400 root.innerHTML = ""; 401 root.appendChild(resultsView); 402 403 resultsUi.renderManageSessions(); 404 resultsUi.renderSessions(); 405 }, 406 renderManageSessions() { 407 const manageSessionsView = UI.getElement("manage-sessions"); 408 manageSessionsView.innerHTML = ""; 409 const heading = { text: "Manage Sessions", className: "title is-4" }; 410 const addCompareSessions = { 411 className: "columns", 412 children: [ 413 { 414 className: "column", 415 children: [ 416 { text: "Add Sessions", className: "title is-5" }, 417 { 418 element: "article", 419 className: "message is-danger", 420 id: "find-error", 421 children: [ 422 { 423 text: 424 "Could not find any sessions! Try adding more characters of the token.", 425 className: "message-body" 426 } 427 ], 428 style: "display: none" 429 }, 430 { 431 className: "field", 432 children: [ 433 { 434 className: "label has-text-weight-normal", 435 text: "Session token:" 436 }, 437 { 438 className: "field-body", 439 children: { 440 className: "field", 441 children: { 442 className: "control", 443 children: { 444 style: "display: flex; margin-bottom: 10px;", 445 children: [ 446 { 447 element: "input", 448 inputType: "text", 449 className: "input is-family-monospace", 450 id: "token-fragment", 451 placeholder: 452 "First 8 characters or more of session token", 453 onKeyDown: event => 454 event.key === "Enter" 455 ? resultsUi.handleAddSession() 456 : null 457 } 458 ] 459 } 460 } 461 } 462 }, 463 { 464 className: "field is-grouped is-grouped-right", 465 children: { 466 className: "control", 467 children: { 468 className: "button is-dark is-outlined", 469 children: [ 470 { 471 element: "span", 472 className: "icon", 473 children: [ 474 { 475 element: "i", 476 className: "fas fa-plus" 477 } 478 ] 479 }, 480 { text: "Add Session", element: "span" } 481 ], 482 onclick: resultsUi.handleAddSession 483 } 484 } 485 } 486 ] 487 } 488 ] 489 }, 490 { 491 className: "column", 492 children: [ 493 { text: "Compare Sessions", className: "title is-5" }, 494 { 495 element: "label", 496 text: 497 "Compare sessions by selecting them in the list below. " + 498 "Only sessions with the same set of reference sessions can be compared. " + 499 "Sessions have to be finished." 500 }, 501 { 502 style: "text-align: right", 503 children: [ 504 { 505 className: "button is-dark is-outlined", 506 disabled: true, 507 id: "compare-button", 508 children: [ 509 { 510 element: "span", 511 className: "icon", 512 children: [ 513 { 514 element: "i", 515 className: "fas fa-balance-scale" 516 } 517 ] 518 }, 519 { text: "Compare Selected", element: "span" } 520 ], 521 onClick: () => resultsUi.compareSessions() 522 } 523 ] 524 } 525 ] 526 } 527 ] 528 }; 529 const { 530 importSessionFile, 531 importError, 532 importInProgress 533 } = resultsUi.state; 534 const importSessions = { 535 className: "columns", 536 style: "margin-bottom: 2em", 537 children: [ 538 { 539 className: "column is-half", 540 children: [ 541 { text: "Import Sessions", className: "title is-5" }, 542 { 543 element: "article", 544 className: "message is-danger", 545 children: [ 546 { 547 className: "message-body", 548 text: "Could not import session: " + importError 549 } 550 ], 551 style: importError ? "" : "display: none" 552 }, 553 { 554 className: "field file has-name", 555 children: [ 556 { 557 element: "label", 558 className: "file-label", 559 style: "width: 100%", 560 children: [ 561 { 562 element: "input", 563 className: "file-input", 564 type: "file", 565 accept: ".zip", 566 id: "import-session-file", 567 onChange: resultsUi.handleImportSessionSelection 568 }, 569 { 570 element: "span", 571 className: "file-cta", 572 children: [ 573 { 574 element: "span", 575 className: "file-icon", 576 children: [ 577 { 578 element: "i", 579 className: "fas fa-upload" 580 } 581 ] 582 }, 583 { 584 element: "span", 585 className: "file-label", 586 text: "Choose ZIP file" 587 } 588 ] 589 }, 590 { 591 element: "span", 592 className: "file-name", 593 style: "width: 100%; max-width: unset", 594 text: importSessionFile 595 ? importSessionFile.name 596 : "" 597 } 598 ] 599 } 600 ] 601 }, 602 { 603 className: "field is-grouped is-grouped-right", 604 children: { 605 className: "control", 606 children: { 607 className: "button is-dark is-outlined", 608 disabled: !importSessionFile, 609 children: [ 610 { 611 element: "span", 612 className: "icon", 613 children: [ 614 { 615 element: "i", 616 className: importInProgress 617 ? "fas fa-spinner fa-pulse" 618 : "fas fa-plus" 619 } 620 ] 621 }, 622 { text: "Import Session", element: "span" } 623 ], 624 onclick: resultsUi.handleImportSession 625 } 626 } 627 } 628 ] 629 }, 630 { 631 className: "column", 632 children: [] 633 } 634 ] 635 }; 636 const { importResultsEnabled } = resultsUi.state; 637 manageSessionsView.appendChild(UI.createElement(heading)); 638 manageSessionsView.appendChild(UI.createElement(addCompareSessions)); 639 if (!importResultsEnabled) return; 640 manageSessionsView.appendChild(UI.createElement(importSessions)); 641 }, 642 renderSessions() { 643 const sessionsView = UI.getElement("sessions"); 644 sessionsView.innerHTML = ""; 645 sessionsView.appendChild( 646 UI.createElement({ text: "Sessions", className: "title is-4" }) 647 ); 648 649 const sessionFilters = resultsUi.createSessionFilters(); 650 sessionsView.appendChild(sessionFilters); 651 652 sessionsView.appendChild(UI.createElement({ id: "public-sessions" })); 653 sessionsView.appendChild(UI.createElement({ id: "pinned-sessions" })); 654 sessionsView.appendChild(UI.createElement({ id: "recent-sessions" })); 655 sessionsView.appendChild(UI.createElement({ id: "session-status" })); 656 resultsUi.renderPublicSessions(); 657 resultsUi.renderPinnedSessions(); 658 resultsUi.renderRecentSessions(); 659 }, 660 renderPublicSessions() { 661 resultsUi.renderSessionStatus(); 662 const { sessions, publicSessions, filterLabels } = resultsUi.state; 663 664 UI.saveScrollPosition("public-sessions-overflow"); 665 666 const publicSessionsView = UI.getElement("public-sessions"); 667 publicSessionsView.innerHTML = ""; 668 669 if (!publicSessions || publicSessions.length === 0) return; 670 const sortedPublicSessions = resultsUi.sortSessionsByColumn( 671 sessions, 672 publicSessions, 673 sortDetail["publicSessions"].sortColumn, 674 sortDetail["publicSessions"].ascending 675 ); 676 677 const filteredPublicSessions = sortedPublicSessions.filter( 678 session => 679 filterLabels.length === 0 || 680 filterLabels.reduce( 681 (match, label) => 682 match && 683 session.labels 684 .map(label => label.toLowerCase()) 685 .indexOf(label.toLowerCase()) !== -1, 686 true 687 ) 688 ); 689 690 if (filteredPublicSessions.length === 0) return; 691 692 publicSessionsView.appendChild( 693 UI.createElement({ 694 text: "Reference Browsers", 695 className: "title is-5" 696 }) 697 ); 698 699 const sessionsTable = UI.createElement({ 700 style: "overflow-x: auto", 701 id: "public-sessions-overflow", 702 children: resultsUi.createSessionsTable( 703 "publicSessions", 704 filteredPublicSessions, 705 { static: true } 706 ) 707 }); 708 publicSessionsView.appendChild(sessionsTable); 709 710 publicSessionsView.appendChild( 711 UI.createElement({ style: "content: ''; margin-bottom: 40px" }) 712 ); 713 714 UI.loadScrollPosition("public-sessions-overflow") 715 }, 716 renderPinnedSessions() { 717 resultsUi.renderSessionStatus(); 718 const { sessions, pinnedSessions, filterLabels } = resultsUi.state; 719 720 UI.saveScrollPosition("pinned-sessions-overflow"); 721 const pinnedSessionsView = UI.getElement("pinned-sessions"); 722 pinnedSessionsView.innerHTML = ""; 723 if (!pinnedSessions || pinnedSessions.length === 0) return; 724 const sortedPinnedSessions = resultsUi.sortSessionsByColumn( 725 sessions, 726 pinnedSessions, 727 sortDetail["pinnedSessions"].sortColumn, 728 sortDetail["pinnedSessions"].ascending 729 ); 730 const filteredPinnedSessions = sortedPinnedSessions.filter( 731 session => 732 filterLabels.length === 0 || 733 filterLabels.reduce( 734 (match, label) => 735 match && 736 session.labels 737 .map(label => label.toLowerCase()) 738 .indexOf(label.toLowerCase()) !== -1, 739 true 740 ) 741 ); 742 743 if (filteredPinnedSessions.length === 0) return; 744 745 pinnedSessionsView.appendChild( 746 UI.createElement({ text: "Pinned", className: "title is-5" }) 747 ); 748 749 const sessionsTable = UI.createElement({ 750 style: "overflow-x: auto", 751 id: "pinned-sessions-overflow", 752 children: resultsUi.createSessionsTable( 753 "pinnedSessions", 754 filteredPinnedSessions, 755 { pinned: true } 756 ) 757 }); 758 pinnedSessionsView.appendChild(sessionsTable); 759 760 pinnedSessionsView.appendChild( 761 UI.createElement({ style: "content: ''; margin-bottom: 40px" }) 762 ); 763 UI.loadScrollPosition("pinned-sessions-overflow"); 764 }, 765 renderRecentSessions() { 766 resultsUi.renderSessionStatus(); 767 const { 768 sessions, 769 recentSessions, 770 pinnedSessions, 771 filterLabels 772 } = resultsUi.state; 773 UI.saveScrollPosition("recent-sessions-overflow"); 774 const recentSessionsView = UI.getElement("recent-sessions"); 775 recentSessionsView.innerHTML = ""; 776 if (!recentSessions || recentSessions.length === 0) return; 777 778 const sortedRecentSessions = resultsUi.sortSessionsByColumn( 779 sessions, 780 recentSessions, 781 sortDetail["recentSessions"].sortColumn, 782 sortDetail["recentSessions"].ascending 783 ); 784 const filteredRecentSessions = sortedRecentSessions.filter( 785 session => 786 filterLabels.length === 0 || 787 filterLabels.reduce( 788 (match, label) => 789 match && 790 session.labels 791 .map(label => label.toLowerCase()) 792 .indexOf(label.toLowerCase()) !== -1, 793 true 794 ) 795 ); 796 797 if (filteredRecentSessions.length === 0) return; 798 799 recentSessionsView.appendChild( 800 UI.createElement({ text: "Recent", className: "title is-5" }) 801 ); 802 803 const sessionsTable = UI.createElement({ 804 style: "overflow-x: auto", 805 id: "recent-sessions-overflow", 806 children: resultsUi.createSessionsTable( 807 "recentSessions", 808 filteredRecentSessions, 809 { pinned: false } 810 ) 811 }); 812 recentSessionsView.appendChild(sessionsTable); 813 814 recentSessionsView.appendChild( 815 UI.createElement({ style: "content: ''; margin-bottom: 40px" }) 816 ); 817 UI.loadScrollPosition("recent-sessions-overflow"); 818 }, 819 renderSessionStatus() { 820 const { 821 recentSessions, 822 pinnedSessions, 823 publicSessions 824 } = resultsUi.state; 825 const sessionStatusView = UI.getElement("session-status"); 826 sessionStatusView.innerHTML = ""; 827 if (!recentSessions && !pinnedSessions && !publicSessions) { 828 sessionStatusView.appendChild( 829 UI.createElement({ 830 className: "level", 831 children: { 832 element: "span", 833 className: "level-item", 834 children: [ 835 { 836 element: "i", 837 className: "fas fa-spinner fa-pulse" 838 }, 839 { 840 style: "margin-left: 0.4em;", 841 text: "Loading sessions ..." 842 } 843 ] 844 } 845 }) 846 ); 847 return; 848 } else if ( 849 (!recentSessions || recentSessions.length === 0) && 850 (!pinnedSessions || pinnedSessions.length === 0) && 851 (!publicSessions || publicSessions.length === 0) 852 ) { 853 sessionStatusView.appendChild( 854 UI.createElement({ 855 className: "level", 856 children: { 857 element: "span", 858 className: "level-item", 859 text: "No sessions available." 860 } 861 }) 862 ); 863 return; 864 } 865 }, 866 createSessionFilters() { 867 const { filterLabels, addFilterLabelVisible } = resultsUi.state; 868 869 const filters = UI.createElement({ 870 className: "field is-horizontal", 871 style: "margin-bottom: 2em", 872 children: [ 873 { 874 className: "field-label", 875 style: "flex: unset", 876 children: { 877 className: "label has-text-weight-normal", 878 text: "Filter by labels:" 879 } 880 }, 881 { 882 className: "field-body", 883 children: { 884 className: "control", 885 children: { 886 className: "field is-grouped is-grouped-multiline", 887 children: filterLabels 888 .map((label, index) => ({ 889 className: "control", 890 children: { 891 className: "tags has-addons", 892 children: [ 893 { 894 element: "span", 895 className: "tag is-info", 896 text: label 897 }, 898 { 899 element: "a", 900 className: "tag is-delete", 901 onClick: () => resultsUi.removeFilterLabel(index) 902 } 903 ] 904 } 905 })) 906 .concat( 907 addFilterLabelVisible 908 ? [ 909 { 910 className: "control field is-grouped", 911 children: [ 912 { 913 element: "input", 914 className: "input is-small control", 915 style: "width: 10rem", 916 id: "filter-label-input", 917 type: "text", 918 onKeyUp: event => 919 event.keyCode === 13 920 ? resultsUi.addFilterLabel() 921 : null 922 }, 923 { 924 className: 925 "button is-dark is-outlined is-small is-rounded control", 926 text: "save", 927 onClick: resultsUi.addFilterLabel 928 }, 929 { 930 className: 931 "button is-dark is-outlined is-small is-rounded control", 932 text: "cancel", 933 onClick: resultsUi.hideAddFilterLabel 934 } 935 ] 936 } 937 ] 938 : [ 939 { 940 className: "button is-rounded is-small", 941 text: "Add", 942 onClick: resultsUi.showAddFilterLabel 943 } 944 ] 945 ) 946 } 947 } 948 } 949 ] 950 }); 951 return filters; 952 }, 953 createSessionsTable( 954 tableType, 955 sessions, 956 { pinned = false, static = false } = {} 957 ) { 958 const getTagStyle = status => { 959 switch (status) { 960 case "completed": 961 return "is-success"; 962 case "running": 963 return "is-info"; 964 case "aborted": 965 return "is-danger"; 966 case "paused": 967 return "is-warning"; 968 case "pending": 969 return "is-primary"; 970 } 971 }; 972 var sortIcon = null; 973 if (tableType in sortDetail) { 974 sortIcon = sortDetail[tableType].ascending 975 ? "fas fa-sort-down" 976 : "fas fa-sort-up"; 977 } 978 return UI.createElement({ 979 element: "table", 980 className: "table is-bordered is-hoverable is-fullwidth", 981 children: [ 982 { 983 element: "thead", 984 children: { 985 element: "tr", 986 children: [ 987 { 988 element: "td", 989 style: "text-decoration: underline dotted;", 990 text: "Cp", 991 className: "is-narrow", 992 title: "Select for comparison" 993 }, 994 { 995 element: "td", 996 text: "Token", 997 className: "is-narrow", 998 onclick: () => resultsUi.sortSessions(tableType, "token"), 999 style: "cursor: pointer;", 1000 children: [ 1001 { 1002 element: "i", 1003 className: sortIcon, 1004 style: 1005 "padding-left: 20px; visibility:" + 1006 (sortIcon && 1007 sortDetail[tableType].sortColumn == "token" 1008 ? "visible;" 1009 : "hidden;") 1010 } 1011 ] 1012 }, 1013 { 1014 element: "td", 1015 text: "Browser", 1016 onclick: () => 1017 resultsUi.sortSessions(tableType, "browser"), 1018 style: "cursor: pointer;", 1019 className: "is-narrow", 1020 children: [ 1021 { 1022 element: "i", 1023 className: sortIcon, 1024 style: 1025 "padding-left: 20px; visibility:" + 1026 (sortIcon && 1027 sortDetail[tableType].sortColumn == "browser" 1028 ? "visible;" 1029 : "hidden;") 1030 } 1031 ] 1032 }, 1033 { 1034 element: "td", 1035 text: "Status", 1036 onclick: () => 1037 resultsUi.sortSessions(tableType, "status"), 1038 style: "cursor: pointer", 1039 className: "is-narrow", 1040 children: [ 1041 { 1042 element: "i", 1043 className: sortIcon, 1044 style: 1045 "padding-left: 20px; visibility:" + 1046 (sortIcon && 1047 sortDetail[tableType].sortColumn == "status" 1048 ? "visible;" 1049 : "hidden;") 1050 } 1051 ] 1052 }, 1053 { 1054 element: "td", 1055 text: "Date Started", 1056 onclick: () => 1057 resultsUi.sortSessions(tableType, "dateStarted"), 1058 style: "cursor: pointer;", 1059 className: "is-narrow", 1060 children: [ 1061 { 1062 element: "i", 1063 className: sortIcon, 1064 style: 1065 "padding-left: 20px; visibility:" + 1066 (sortIcon && 1067 sortDetail[tableType].sortColumn == "dateStarted" 1068 ? "visible;" 1069 : "hidden;") 1070 } 1071 ] 1072 }, 1073 { 1074 element: "td", 1075 text: "Labels", 1076 style: "cursor: pointer; width: 18rem" 1077 }, 1078 static 1079 ? null 1080 : { 1081 element: "td", 1082 text: "RefS", 1083 title: "Reference Sessions", 1084 style: "text-decoration: underline dotted;", 1085 className: "is-narrow" 1086 }, 1087 static 1088 ? null 1089 : { 1090 element: "td", 1091 colspan: 2, 1092 text: "Options", 1093 className: "is-narrow" 1094 } 1095 ] 1096 } 1097 }, 1098 { 1099 element: "tbody", 1100 children: sessions.map(session => ({ 1101 element: "tr", 1102 className: resultsUi.isSessionDisabled(session) 1103 ? "disabled-row" 1104 : "", 1105 style: "cursor: pointer", 1106 onclick: () => resultsUi.openSessionResult(session.token), 1107 children: [ 1108 { 1109 element: "td", 1110 onclick: event => event.stopPropagation(), 1111 style: "vertical-align: middle;", 1112 children: [ 1113 { 1114 element: "input", 1115 className: "checkbox", 1116 style: 1117 "width: 18px; height: 18px; margin-top: 0.55em", 1118 type: "checkbox", 1119 disabled: !resultsUi.isSessionValidForComparison( 1120 session 1121 ), 1122 checked: resultsUi.isSessionSelectedForComparison( 1123 session 1124 ), 1125 onchange: event => 1126 event.target.checked 1127 ? resultsUi.addSessionToComparison(session.token) 1128 : resultsUi.removeSessionFromComparison( 1129 session.token 1130 ) 1131 } 1132 ] 1133 }, 1134 { 1135 element: "td", 1136 className: "is-family-monospace", 1137 style: "vertical-align: middle;", 1138 text: session.token.split("-").shift() 1139 }, 1140 { 1141 element: "td", 1142 style: "vertical-align: middle; white-space: nowrap", 1143 text: session.browser.name + " " + session.browser.version 1144 }, 1145 { 1146 element: "td", 1147 style: "vertical-align: middle; text-align: center", 1148 children: [ 1149 { 1150 className: `tag ${getTagStyle(session.status)}`, 1151 text: session.status 1152 } 1153 ] 1154 }, 1155 { 1156 element: "td", 1157 style: "vertical-align: middle; white-space: nowrap", 1158 text: session.dateStarted 1159 ? new Date(session.dateStarted).toLocaleString() 1160 : "not started" 1161 }, 1162 { 1163 element: "td", 1164 children: { 1165 className: "tags field is-grouped isgrouped-multiline", 1166 style: "min-width: 10em", 1167 children: session.labels.map(label => ({ 1168 className: "control", 1169 children: { 1170 element: "span", 1171 className: "tag is-info", 1172 text: label 1173 } 1174 })) 1175 } 1176 }, 1177 static 1178 ? null 1179 : { 1180 element: "td", 1181 title: session.referenceTokens 1182 .map(token => token.split("-").shift()) 1183 .sort((tokenA, tokenB) => tokenA - tokenB) 1184 .join("\n"), 1185 style: "white-space:nowrap", 1186 children: (() => { 1187 const tokens = session.referenceTokens.slice(); 1188 let overflow = 0; 1189 if (tokens.length > 3) { 1190 overflow = tokens.length - 2; 1191 } 1192 if (overflow > 0) tokens.splice(2, overflow + 2); 1193 const children = tokens.map(token => { 1194 let icon = ""; 1195 const session = resultsUi.state.sessions[token]; 1196 switch (session.browser.name.toLowerCase()) { 1197 case "firefox": 1198 icon = "fab fa-firefox"; 1199 break; 1200 case "edge": 1201 icon = "fab fa-edge"; 1202 break; 1203 case "chrome": 1204 case "chromium": 1205 icon = "fab fa-chrome"; 1206 break; 1207 case "safari": 1208 case "webkit": 1209 icon = "fab fa-safari"; 1210 break; 1211 } 1212 return { 1213 element: "span", 1214 style: 1215 "margin-right: 5px; vertical-align: middle;", 1216 children: { element: "i", className: icon } 1217 }; 1218 }); 1219 1220 if (overflow > 0) 1221 children.push({ 1222 element: "span", 1223 style: "vertical-align: middle", 1224 className: "is-size-7", 1225 text: `+${overflow}` 1226 }); 1227 1228 return children; 1229 })() 1230 }, 1231 static 1232 ? null 1233 : { 1234 element: "td", 1235 style: "vertical-align: middle; text-align: center", 1236 className: "is-paddingless", 1237 children: [ 1238 { 1239 className: "button is-dark is-outlined is-small", 1240 title: pinned ? "Unpin session" : "Pin session", 1241 style: "margin: 5px", 1242 children: [ 1243 { 1244 element: "span", 1245 className: "icon", 1246 children: [ 1247 { 1248 element: "i", 1249 className: "fas fa-thumbtack", 1250 style: pinned 1251 ? "" 1252 : "transform: rotate(45deg)" 1253 } 1254 ] 1255 } 1256 ], 1257 onclick: event => { 1258 event.stopPropagation(); 1259 if (pinned) { 1260 resultsUi.unpinSession(session.token); 1261 } else { 1262 resultsUi.pinSession(session.token); 1263 } 1264 } 1265 } 1266 ] 1267 }, 1268 static 1269 ? null 1270 : { 1271 element: "td", 1272 style: "vertical-align: middle; text-align: center", 1273 className: "is-paddingless", 1274 children: [ 1275 { 1276 className: "button is-dark is-outlined is-small", 1277 title: "Remove session from list", 1278 style: "margin: 5px", 1279 children: [ 1280 { 1281 element: "span", 1282 className: "icon", 1283 children: [ 1284 { 1285 element: "i", 1286 className: "fas fa-trash-alt" 1287 } 1288 ] 1289 } 1290 ], 1291 onclick: event => { 1292 event.stopPropagation(); 1293 resultsUi.removeSession(session.token); 1294 } 1295 } 1296 ] 1297 } 1298 ] 1299 })) 1300 } 1301 ] 1302 }); 1303 }, 1304 updateCompareButton: () => { 1305 const compareButton = UI.getElement("compare-button"); 1306 if (resultsUi.isComparisonValid()) { 1307 compareButton.removeAttribute("disabled"); 1308 } else { 1309 compareButton.setAttribute("disabled", true); 1310 } 1311 } 1312 }; 1313 </script> 1314 </body> 1315 </html>