| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Overview - Web Platform Test</title> | |
| <link rel="stylesheet" href="css/bulma-0.7.5/bulma.min.css" /> | |
| <link rel="stylesheet" href="css/fontawesome-5.7.2.min.css" /> | |
| <script src="lib/utils.js"></script> | |
| <script src="lib/wave-service.js"></script> | |
| <script src="lib/ui.js"></script> | |
| <style> | |
| .site-logo { | |
| max-width: 300px; | |
| margin: 0 0 30px -15px; | |
| } | |
| .disabled-row { | |
| color: gray; | |
| background: lightgray; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <script> | |
| window.onload = () => { | |
| const query = utils.parseQuery(location.search); | |
| if (query.token) { | |
| location.href = WEB_ROOT + "results.html" + location.search; | |
| } | |
| resultsUi.render(); | |
| resultsUi.loadData(); | |
| }; | |
| var sortDetail = {}; | |
| const defaultSortDetail = { sortColumn: "dateStarted", ascending: true }; | |
| sortDetail["recentSessions"] = defaultSortDetail; | |
| sortDetail["pinnedSessions"] = defaultSortDetail; | |
| sortDetail["publicSessions"] = defaultSortDetail; | |
| const resultsUi = { | |
| state: { | |
| comparison: [], | |
| recentSessions: null, | |
| importResultsEnabled: false, | |
| filterLabels: [] | |
| }, | |
| loadData() { | |
| const pinnedSessions = WaveService.getPinnedSessions().filter( | |
| token => !!token | |
| ); | |
| const recentSessions = WaveService.getRecentSessions().filter( | |
| token => !!token | |
| ); | |
| pinnedSessions.forEach(token => { | |
| const index = recentSessions.indexOf(token); | |
| if (index !== -1) recentSessions.splice(index, 1); | |
| }); | |
| WaveService.setRecentSessions(recentSessions); | |
| let allSessions = []; | |
| allSessions = allSessions.concat(pinnedSessions); | |
| allSessions = allSessions.concat(recentSessions); | |
| WaveService.readPublicSessions(publicSessions => { | |
| publicSessions.forEach(token => { | |
| const index = recentSessions.indexOf(token); | |
| if (index !== -1) recentSessions.splice(index, 1); | |
| }); | |
| WaveService.setRecentSessions(recentSessions); | |
| allSessions = allSessions.concat(publicSessions); | |
| WaveService.readMultipleSessions(allSessions, configurations => | |
| WaveService.readMultipleSessionStatuses(allSessions, statuses => { | |
| configurations.forEach(configuration => { | |
| const status = statuses.find( | |
| status => status.token === configuration.token | |
| ); | |
| configuration.dateStarted = status.dateStarted; | |
| configuration.dateFinished = status.dateFinished; | |
| configuration.status = status.status; | |
| }); | |
| configurations = configurations.filter( | |
| configuration => !!configuration | |
| ); | |
| allSessions | |
| .filter( | |
| token => | |
| !configurations.some( | |
| configuration => configuration.token === token | |
| ) | |
| ) | |
| .forEach(token => { | |
| WaveService.removePinnedSession(token); | |
| WaveService.removeRecentSession(token); | |
| }); | |
| resultsUi.state.publicSessions = publicSessions; | |
| resultsUi.state.pinnedSessions = WaveService.getPinnedSessions(); | |
| resultsUi.state.recentSessions = WaveService.getRecentSessions(); | |
| const sessions = {}; | |
| configurations.forEach( | |
| configuration => | |
| (sessions[configuration.token] = configuration) | |
| ); | |
| resultsUi.state.sessions = sessions; | |
| const referenceTokens = []; | |
| const loadedSessionsTokens = Object.keys(sessions); | |
| configurations.forEach(configuration => | |
| configuration.referenceTokens | |
| .filter(token => loadedSessionsTokens.indexOf(token) === -1) | |
| .forEach(token => referenceTokens.push(token)) | |
| ); | |
| WaveService.readMultipleSessions( | |
| referenceTokens, | |
| configurations => { | |
| const { sessions } = resultsUi.state; | |
| configurations.forEach( | |
| configuration => | |
| (sessions[configuration.token] = configuration) | |
| ); | |
| resultsUi.renderPublicSessions(); | |
| resultsUi.renderPinnedSessions(); | |
| resultsUi.renderRecentSessions(); | |
| } | |
| ); | |
| }) | |
| ); | |
| }); | |
| WaveService.readStatus(function(config) { | |
| resultsUi.state.importResultsEnabled = config.importResultsEnabled; | |
| resultsUi.state.reportsEnabled = config.reportsEnabled; | |
| resultsUi.renderManageSessions(); | |
| }); | |
| }, | |
| findSession(fragment, callback) { | |
| if (!fragment || fragment.length < 8) return; | |
| WaveService.findToken( | |
| fragment, | |
| token => { | |
| WaveService.readSession(token, session => { | |
| WaveService.readSessionStatus(token, status => { | |
| session.status = status.status; | |
| session.dateStarted = status.dateStarted; | |
| session.dateFinished = status.dateFinished; | |
| callback(session); | |
| }); | |
| }); | |
| }, | |
| () => callback(null) | |
| ); | |
| }, | |
| addSession(session) { | |
| const token = session.token; | |
| if (resultsUi.state.sessions[token]) return; | |
| resultsUi.state.sessions[token] = session; | |
| resultsUi.pinSession(token); | |
| }, | |
| removeSession(token) { | |
| delete resultsUi.state.sessions[token]; | |
| WaveService.removeRecentSession(token); | |
| WaveService.removePinnedSession(token); | |
| resultsUi.updateSessionState(); | |
| }, | |
| showAddSessionError() { | |
| const errorBox = UI.getElement("find-error"); | |
| errorBox.setAttribute("style", "display: block"); | |
| }, | |
| hideAddSessionError() { | |
| const errorBox = UI.getElement("find-error"); | |
| errorBox.setAttribute("style", "display: none"); | |
| }, | |
| pinSession(token) { | |
| WaveService.addPinnedSession(token); | |
| WaveService.removeRecentSession(token); | |
| resultsUi.updateSessionState(); | |
| }, | |
| unpinSession(token) { | |
| WaveService.removePinnedSession(token); | |
| WaveService.addRecentSession(token); | |
| resultsUi.updateSessionState(); | |
| }, | |
| updateSessionState() { | |
| resultsUi.state.pinnedSessions = WaveService.getPinnedSessions(); | |
| resultsUi.state.recentSessions = WaveService.getRecentSessions(); | |
| resultsUi.renderPinnedSessions(); | |
| resultsUi.renderRecentSessions(); | |
| }, | |
| openSessionResult(token) { | |
| location.href = `${WEB_ROOT}results.html?token=${token}`; | |
| }, | |
| sortSessions(tableType, column) { | |
| if (tableType in sortDetail) { | |
| if (sortDetail[tableType].sortColumn == column) { | |
| sortDetail[tableType].ascending = !sortDetail[tableType] | |
| .ascending; | |
| } else { | |
| sortDetail[tableType].sortColumn = column; | |
| sortDetail[tableType].ascending = true; | |
| } | |
| switch (tableType) { | |
| case "recentSessions": | |
| resultsUi.renderRecentSessions(); | |
| break; | |
| case "pinnedSessions": | |
| resultsUi.renderPinnedSessions(); | |
| break; | |
| case "publicSessions": | |
| resultsUi.renderPublicSessions(); | |
| break; | |
| } | |
| } | |
| }, | |
| sortSessionsByColumn(sessions, recentSessions, column, ascending) { | |
| var resultArray = recentSessions | |
| .map(token => sessions[token]) | |
| .sort(function(sessionA, sessionB) { | |
| let columnA = sessionA[column]; | |
| if (column === "browser") | |
| columnA = sessionA[column].name + sessionA[column].version; | |
| if (column === "dateStarted" && !columnA) { | |
| columnA = Date.now(); | |
| } | |
| let columnB = sessionB[column]; | |
| if (column === "browser") | |
| columnB = sessionB[column].name + sessionA[column].version; | |
| if (column === "dateStarted" && !columnB) { | |
| columnB = Date.now(); | |
| } | |
| if (columnA < columnB) { | |
| return -1; | |
| } | |
| if (columnA > columnB) { | |
| return 1; | |
| } | |
| return 0; | |
| }); | |
| if (ascending) { | |
| resultArray.reverse(); | |
| } | |
| return resultArray; | |
| }, | |
| compareSessions(reftokens) { | |
| if (!resultsUi.isComparisonValid()) return; | |
| const tokens = resultsUi.state.comparison; | |
| if (!tokens || tokens.length === 0) return; | |
| const refQuery = reftokens ? `&reftokens=${reftokens}` : ""; | |
| location.href = `${WEB_ROOT}comparison.html?tokens=${tokens.join( | |
| "," | |
| )}${refQuery}`; | |
| }, | |
| isComparisonValid() { | |
| const { comparison, sessions } = resultsUi.state; | |
| if (!comparison) return false; | |
| if (comparison.length <= 1) return false; | |
| const comparingSessions = comparison.map(token => sessions[token]); | |
| const referenceTokens = comparingSessions[0].referenceTokens; | |
| for (let comparingSession of comparingSessions) { | |
| const comparingReferenceTokens = comparingSession.referenceTokens; | |
| if (referenceTokens.length !== comparingReferenceTokens.length) | |
| return false; | |
| for (let token of comparingReferenceTokens) { | |
| if (referenceTokens.indexOf(token) === -1) return false; | |
| } | |
| } | |
| return true; | |
| }, | |
| isSessionValidForComparison(session) { | |
| if (!session) return false; | |
| if (session.status !== "completed" && session.status !== "aborted") | |
| return false; | |
| const sessionRefTokens = session.reference_tokens; | |
| const comparisonSession = | |
| resultsUi.state.sessions[resultsUi.state.comparison[0]]; | |
| if (!comparisonSession) return true; | |
| const comparisonRefTokens = comparisonSession.reference_tokens; | |
| if (!comparisonRefTokens) return true; | |
| if (sessionRefTokens.length !== comparisonRefTokens.length) | |
| return false; | |
| if ( | |
| sessionRefTokens.some( | |
| token => comparisonRefTokens.indexOf(token) === -1 | |
| ) | |
| ) | |
| return false; | |
| return true; | |
| }, | |
| isSessionSelectedForComparison(session) { | |
| return resultsUi.state.comparison.indexOf(session.token) !== -1; | |
| }, | |
| isSessionDisabled(session) { | |
| return ( | |
| resultsUi.state.comparison.length > 0 && | |
| !resultsUi.isSessionValidForComparison(session) | |
| ); | |
| }, | |
| addSessionToComparison(token) { | |
| if (resultsUi.state.comparison.indexOf(token) !== -1) return; | |
| resultsUi.state.comparison.push(token); | |
| resultsUi.updateCompareButton(); | |
| resultsUi.renderSessions(); | |
| }, | |
| removeSessionFromComparison(token) { | |
| const index = resultsUi.state.comparison.indexOf(token); | |
| if (index === -1) return; | |
| resultsUi.state.comparison.splice(index, 1); | |
| resultsUi.updateCompareButton(); | |
| resultsUi.renderSessions(); | |
| }, | |
| handleAddSession() { | |
| const tokenFragmentInput = UI.getElement("token-fragment"); | |
| const fragment = tokenFragmentInput.value; | |
| resultsUi.findSession(fragment, session => { | |
| if (!session) { | |
| resultsUi.showAddSessionError(); | |
| return; | |
| } | |
| tokenFragmentInput.value = ""; | |
| resultsUi.hideAddSessionError(); | |
| resultsUi.addSession(session); | |
| }); | |
| }, | |
| handleImportSession() { | |
| resultsUi.state.importError = null; | |
| resultsUi.state.importInProgress = true; | |
| resultsUi.renderManageSessions(); | |
| const { importSessionFile: file } = resultsUi.state; | |
| const reader = new FileReader(); | |
| reader.readAsArrayBuffer(file); | |
| reader.onload = () => { | |
| const data = reader.result; | |
| WaveService.importResults( | |
| data, | |
| function(token) { | |
| location.href = WEB_ROOT + "results.html?token=" + token; | |
| }, | |
| function(error) { | |
| resultsUi.state.importError = error; | |
| resultsUi.state.importInProgress = false; | |
| resultsUi.renderManageSessions(); | |
| } | |
| ); | |
| }; | |
| }, | |
| handleImportSessionSelection() { | |
| const file = UI.getElement("import-session-file").files[0]; | |
| resultsUi.state.importSessionFile = file; | |
| resultsUi.renderManageSessions(); | |
| }, | |
| addFilterLabel() { | |
| const label = UI.getElement("filter-label-input").value; | |
| if (!label) return; | |
| const { filterLabels } = resultsUi.state; | |
| if (filterLabels.indexOf(label) !== -1) return; | |
| filterLabels.push(label); | |
| resultsUi.renderSessions(); | |
| UI.getElement("filter-label-input").focus(); | |
| }, | |
| removeFilterLabel(index) { | |
| resultsUi.state.filterLabels.splice(index, 1); | |
| resultsUi.renderSessions(); | |
| }, | |
| showAddFilterLabel() { | |
| resultsUi.state.addFilterLabelVisible = true; | |
| resultsUi.renderSessions(); | |
| UI.getElement("filter-label-input").focus(); | |
| }, | |
| hideAddFilterLabel() { | |
| resultsUi.state.addFilterLabelVisible = false; | |
| resultsUi.renderSessions(); | |
| }, | |
| render() { | |
| const { getRoot, createElement, getElement } = UI; | |
| const resultsView = UI.createElement({ | |
| className: "section", | |
| children: [ | |
| { | |
| className: "container", | |
| style: "margin-bottom: 2em", | |
| children: [ | |
| { | |
| element: "img", | |
| src: "res/wavelogo_2016.jpg", | |
| className: "site-logo" | |
| }, | |
| { text: "Results Overview", className: "title" } | |
| ] | |
| }, | |
| { | |
| id: "manage-sessions", | |
| className: "container", | |
| style: "margin-bottom: 2em" | |
| }, | |
| { id: "sessions", className: "container" } | |
| ] | |
| }); | |
| const root = UI.getRoot(); | |
| root.innerHTML = ""; | |
| root.appendChild(resultsView); | |
| resultsUi.renderManageSessions(); | |
| resultsUi.renderSessions(); | |
| }, | |
| renderManageSessions() { | |
| const manageSessionsView = UI.getElement("manage-sessions"); | |
| manageSessionsView.innerHTML = ""; | |
| const heading = { text: "Manage Sessions", className: "title is-4" }; | |
| const addCompareSessions = { | |
| className: "columns", | |
| children: [ | |
| { | |
| className: "column", | |
| children: [ | |
| { text: "Add Sessions", className: "title is-5" }, | |
| { | |
| element: "article", | |
| className: "message is-danger", | |
| id: "find-error", | |
| children: [ | |
| { | |
| text: | |
| "Could not find any sessions! Try adding more characters of the token.", | |
| className: "message-body" | |
| } | |
| ], | |
| style: "display: none" | |
| }, | |
| { | |
| className: "field", | |
| children: [ | |
| { | |
| className: "label has-text-weight-normal", | |
| text: "Session token:" | |
| }, | |
| { | |
| className: "field-body", | |
| children: { | |
| className: "field", | |
| children: { | |
| className: "control", | |
| children: { | |
| style: "display: flex; margin-bottom: 10px;", | |
| children: [ | |
| { | |
| element: "input", | |
| inputType: "text", | |
| className: "input is-family-monospace", | |
| id: "token-fragment", | |
| placeholder: | |
| "First 8 characters or more of session token", | |
| onKeyDown: event => | |
| event.key === "Enter" | |
| ? resultsUi.handleAddSession() | |
| : null | |
| } | |
| ] | |
| } | |
| } | |
| } | |
| }, | |
| { | |
| className: "field is-grouped is-grouped-right", | |
| children: { | |
| className: "control", | |
| children: { | |
| className: "button is-dark is-outlined", | |
| children: [ | |
| { | |
| element: "span", | |
| className: "icon", | |
| children: [ | |
| { | |
| element: "i", | |
| className: "fas fa-plus" | |
| } | |
| ] | |
| }, | |
| { text: "Add Session", element: "span" } | |
| ], | |
| onclick: resultsUi.handleAddSession | |
| } | |
| } | |
| } | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| className: "column", | |
| children: [ | |
| { text: "Compare Sessions", className: "title is-5" }, | |
| { | |
| element: "label", | |
| text: | |
| "Compare sessions by selecting them in the list below. " + | |
| "Only sessions with the same set of reference sessions can be compared. " + | |
| "Sessions have to be finished." | |
| }, | |
| { | |
| style: "text-align: right", | |
| children: [ | |
| { | |
| className: "button is-dark is-outlined", | |
| disabled: true, | |
| id: "compare-button", | |
| children: [ | |
| { | |
| element: "span", | |
| className: "icon", | |
| children: [ | |
| { | |
| element: "i", | |
| className: "fas fa-balance-scale" | |
| } | |
| ] | |
| }, | |
| { text: "Compare Selected", element: "span" } | |
| ], | |
| onClick: () => resultsUi.compareSessions() | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| ] | |
| }; | |
| const { | |
| importSessionFile, | |
| importError, | |
| importInProgress | |
| } = resultsUi.state; | |
| const importSessions = { | |
| className: "columns", | |
| style: "margin-bottom: 2em", | |
| children: [ | |
| { | |
| className: "column is-half", | |
| children: [ | |
| { text: "Import Sessions", className: "title is-5" }, | |
| { | |
| element: "article", | |
| className: "message is-danger", | |
| children: [ | |
| { | |
| className: "message-body", | |
| text: "Could not import session: " + importError | |
| } | |
| ], | |
| style: importError ? "" : "display: none" | |
| }, | |
| { | |
| className: "field file has-name", | |
| children: [ | |
| { | |
| element: "label", | |
| className: "file-label", | |
| style: "width: 100%", | |
| children: [ | |
| { | |
| element: "input", | |
| className: "file-input", | |
| type: "file", | |
| accept: ".zip", | |
| id: "import-session-file", | |
| onChange: resultsUi.handleImportSessionSelection | |
| }, | |
| { | |
| element: "span", | |
| className: "file-cta", | |
| children: [ | |
| { | |
| element: "span", | |
| className: "file-icon", | |
| children: [ | |
| { | |
| element: "i", | |
| className: "fas fa-upload" | |
| } | |
| ] | |
| }, | |
| { | |
| element: "span", | |
| className: "file-label", | |
| text: "Choose ZIP file" | |
| } | |
| ] | |
| }, | |
| { | |
| element: "span", | |
| className: "file-name", | |
| style: "width: 100%; max-width: unset", | |
| text: importSessionFile | |
| ? importSessionFile.name | |
| : "" | |
| } | |
| ] | |
| } | |
| ] | |
| }, | |
| { | |
| className: "field is-grouped is-grouped-right", | |
| children: { | |
| className: "control", | |
| children: { | |
| className: "button is-dark is-outlined", | |
| disabled: !importSessionFile, | |
| children: [ | |
| { | |
| element: "span", | |
| className: "icon", | |
| children: [ | |
| { | |
| element: "i", | |
| className: importInProgress | |
| ? "fas fa-spinner fa-pulse" | |
| : "fas fa-plus" | |
| } | |
| ] | |
| }, | |
| { text: "Import Session", element: "span" } | |
| ], | |
| onclick: resultsUi.handleImportSession | |
| } | |
| } | |
| } | |
| ] | |
| }, | |
| { | |
| className: "column", | |
| children: [] | |
| } | |
| ] | |
| }; | |
| const { importResultsEnabled } = resultsUi.state; | |
| manageSessionsView.appendChild(UI.createElement(heading)); | |
| manageSessionsView.appendChild(UI.createElement(addCompareSessions)); | |
| if (!importResultsEnabled) return; | |
| manageSessionsView.appendChild(UI.createElement(importSessions)); | |
| }, | |
| renderSessions() { | |
| const sessionsView = UI.getElement("sessions"); | |
| sessionsView.innerHTML = ""; | |
| sessionsView.appendChild( | |
| UI.createElement({ text: "Sessions", className: "title is-4" }) | |
| ); | |
| const sessionFilters = resultsUi.createSessionFilters(); | |
| sessionsView.appendChild(sessionFilters); | |
| sessionsView.appendChild(UI.createElement({ id: "public-sessions" })); | |
| sessionsView.appendChild(UI.createElement({ id: "pinned-sessions" })); | |
| sessionsView.appendChild(UI.createElement({ id: "recent-sessions" })); | |
| sessionsView.appendChild(UI.createElement({ id: "session-status" })); | |
| resultsUi.renderPublicSessions(); | |
| resultsUi.renderPinnedSessions(); | |
| resultsUi.renderRecentSessions(); | |
| }, | |
| renderPublicSessions() { | |
| resultsUi.renderSessionStatus(); | |
| const { sessions, publicSessions, filterLabels } = resultsUi.state; | |
| UI.saveScrollPosition("public-sessions-overflow"); | |
| const publicSessionsView = UI.getElement("public-sessions"); | |
| publicSessionsView.innerHTML = ""; | |
| if (!publicSessions || publicSessions.length === 0) return; | |
| const sortedPublicSessions = resultsUi.sortSessionsByColumn( | |
| sessions, | |
| publicSessions, | |
| sortDetail["publicSessions"].sortColumn, | |
| sortDetail["publicSessions"].ascending | |
| ); | |
| const filteredPublicSessions = sortedPublicSessions.filter( | |
| session => | |
| filterLabels.length === 0 || | |
| filterLabels.reduce( | |
| (match, label) => | |
| match && | |
| session.labels | |
| .map(label => label.toLowerCase()) | |
| .indexOf(label.toLowerCase()) !== -1, | |
| true | |
| ) | |
| ); | |
| if (filteredPublicSessions.length === 0) return; | |
| publicSessionsView.appendChild( | |
| UI.createElement({ | |
| text: "Reference Browsers", | |
| className: "title is-5" | |
| }) | |
| ); | |
| const sessionsTable = UI.createElement({ | |
| style: "overflow-x: auto", | |
| id: "public-sessions-overflow", | |
| children: resultsUi.createSessionsTable( | |
| "publicSessions", | |
| filteredPublicSessions, | |
| { static: true } | |
| ) | |
| }); | |
| publicSessionsView.appendChild(sessionsTable); | |
| publicSessionsView.appendChild( | |
| UI.createElement({ style: "content: ''; margin-bottom: 40px" }) | |
| ); | |
| UI.loadScrollPosition("public-sessions-overflow") | |
| }, | |
| renderPinnedSessions() { | |
| resultsUi.renderSessionStatus(); | |
| const { sessions, pinnedSessions, filterLabels } = resultsUi.state; | |
| UI.saveScrollPosition("pinned-sessions-overflow"); | |
| const pinnedSessionsView = UI.getElement("pinned-sessions"); | |
| pinnedSessionsView.innerHTML = ""; | |
| if (!pinnedSessions || pinnedSessions.length === 0) return; | |
| const sortedPinnedSessions = resultsUi.sortSessionsByColumn( | |
| sessions, | |
| pinnedSessions, | |
| sortDetail["pinnedSessions"].sortColumn, | |
| sortDetail["pinnedSessions"].ascending | |
| ); | |
| const filteredPinnedSessions = sortedPinnedSessions.filter( | |
| session => | |
| filterLabels.length === 0 || | |
| filterLabels.reduce( | |
| (match, label) => | |
| match && | |
| session.labels | |
| .map(label => label.toLowerCase()) | |
| .indexOf(label.toLowerCase()) !== -1, | |
| true | |
| ) | |
| ); | |
| if (filteredPinnedSessions.length === 0) return; | |
| pinnedSessionsView.appendChild( | |
| UI.createElement({ text: "Pinned", className: "title is-5" }) | |
| ); | |
| const sessionsTable = UI.createElement({ | |
| style: "overflow-x: auto", | |
| id: "pinned-sessions-overflow", | |
| children: resultsUi.createSessionsTable( | |
| "pinnedSessions", | |
| filteredPinnedSessions, | |
| { pinned: true } | |
| ) | |
| }); | |
| pinnedSessionsView.appendChild(sessionsTable); | |
| pinnedSessionsView.appendChild( | |
| UI.createElement({ style: "content: ''; margin-bottom: 40px" }) | |
| ); | |
| UI.loadScrollPosition("pinned-sessions-overflow"); | |
| }, | |
| renderRecentSessions() { | |
| resultsUi.renderSessionStatus(); | |
| const { | |
| sessions, | |
| recentSessions, | |
| pinnedSessions, | |
| filterLabels | |
| } = resultsUi.state; | |
| UI.saveScrollPosition("recent-sessions-overflow"); | |
| const recentSessionsView = UI.getElement("recent-sessions"); | |
| recentSessionsView.innerHTML = ""; | |
| if (!recentSessions || recentSessions.length === 0) return; | |
| const sortedRecentSessions = resultsUi.sortSessionsByColumn( | |
| sessions, | |
| recentSessions, | |
| sortDetail["recentSessions"].sortColumn, | |
| sortDetail["recentSessions"].ascending | |
| ); | |
| const filteredRecentSessions = sortedRecentSessions.filter( | |
| session => | |
| filterLabels.length === 0 || | |
| filterLabels.reduce( | |
| (match, label) => | |
| match && | |
| session.labels | |
| .map(label => label.toLowerCase()) | |
| .indexOf(label.toLowerCase()) !== -1, | |
| true | |
| ) | |
| ); | |
| if (filteredRecentSessions.length === 0) return; | |
| recentSessionsView.appendChild( | |
| UI.createElement({ text: "Recent", className: "title is-5" }) | |
| ); | |
| const sessionsTable = UI.createElement({ | |
| style: "overflow-x: auto", | |
| id: "recent-sessions-overflow", | |
| children: resultsUi.createSessionsTable( | |
| "recentSessions", | |
| filteredRecentSessions, | |
| { pinned: false } | |
| ) | |
| }); | |
| recentSessionsView.appendChild(sessionsTable); | |
| recentSessionsView.appendChild( | |
| UI.createElement({ style: "content: ''; margin-bottom: 40px" }) | |
| ); | |
| UI.loadScrollPosition("recent-sessions-overflow"); | |
| }, | |
| renderSessionStatus() { | |
| const { | |
| recentSessions, | |
| pinnedSessions, | |
| publicSessions | |
| } = resultsUi.state; | |
| const sessionStatusView = UI.getElement("session-status"); | |
| sessionStatusView.innerHTML = ""; | |
| if (!recentSessions && !pinnedSessions && !publicSessions) { | |
| sessionStatusView.appendChild( | |
| UI.createElement({ | |
| className: "level", | |
| children: { | |
| element: "span", | |
| className: "level-item", | |
| children: [ | |
| { | |
| element: "i", | |
| className: "fas fa-spinner fa-pulse" | |
| }, | |
| { | |
| style: "margin-left: 0.4em;", | |
| text: "Loading sessions ..." | |
| } | |
| ] | |
| } | |
| }) | |
| ); | |
| return; | |
| } else if ( | |
| (!recentSessions || recentSessions.length === 0) && | |
| (!pinnedSessions || pinnedSessions.length === 0) && | |
| (!publicSessions || publicSessions.length === 0) | |
| ) { | |
| sessionStatusView.appendChild( | |
| UI.createElement({ | |
| className: "level", | |
| children: { | |
| element: "span", | |
| className: "level-item", | |
| text: "No sessions available." | |
| } | |
| }) | |
| ); | |
| return; | |
| } | |
| }, | |
| createSessionFilters() { | |
| const { filterLabels, addFilterLabelVisible } = resultsUi.state; | |
| const filters = UI.createElement({ | |
| className: "field is-horizontal", | |
| style: "margin-bottom: 2em", | |
| children: [ | |
| { | |
| className: "field-label", | |
| style: "flex: unset", | |
| children: { | |
| className: "label has-text-weight-normal", | |
| text: "Filter by labels:" | |
| } | |
| }, | |
| { | |
| className: "field-body", | |
| children: { | |
| className: "control", | |
| children: { | |
| className: "field is-grouped is-grouped-multiline", | |
| children: filterLabels | |
| .map((label, index) => ({ | |
| className: "control", | |
| children: { | |
| className: "tags has-addons", | |
| children: [ | |
| { | |
| element: "span", | |
| className: "tag is-info", | |
| text: label | |
| }, | |
| { | |
| element: "a", | |
| className: "tag is-delete", | |
| onClick: () => resultsUi.removeFilterLabel(index) | |
| } | |
| ] | |
| } | |
| })) | |
| .concat( | |
| addFilterLabelVisible | |
| ? [ | |
| { | |
| className: "control field is-grouped", | |
| children: [ | |
| { | |
| element: "input", | |
| className: "input is-small control", | |
| style: "width: 10rem", | |
| id: "filter-label-input", | |
| type: "text", | |
| onKeyUp: event => | |
| event.keyCode === 13 | |
| ? resultsUi.addFilterLabel() | |
| : null | |
| }, | |
| { | |
| className: | |
| "button is-dark is-outlined is-small is-rounded control", | |
| text: "save", | |
| onClick: resultsUi.addFilterLabel | |
| }, | |
| { | |
| className: | |
| "button is-dark is-outlined is-small is-rounded control", | |
| text: "cancel", | |
| onClick: resultsUi.hideAddFilterLabel | |
| } | |
| ] | |
| } | |
| ] | |
| : [ | |
| { | |
| className: "button is-rounded is-small", | |
| text: "Add", | |
| onClick: resultsUi.showAddFilterLabel | |
| } | |
| ] | |
| ) | |
| } | |
| } | |
| } | |
| ] | |
| }); | |
| return filters; | |
| }, | |
| createSessionsTable( | |
| tableType, | |
| sessions, | |
| { pinned = false, static = false } = {} | |
| ) { | |
| const getTagStyle = status => { | |
| switch (status) { | |
| case "completed": | |
| return "is-success"; | |
| case "running": | |
| return "is-info"; | |
| case "aborted": | |
| return "is-danger"; | |
| case "paused": | |
| return "is-warning"; | |
| case "pending": | |
| return "is-primary"; | |
| } | |
| }; | |
| var sortIcon = null; | |
| if (tableType in sortDetail) { | |
| sortIcon = sortDetail[tableType].ascending | |
| ? "fas fa-sort-down" | |
| : "fas fa-sort-up"; | |
| } | |
| return UI.createElement({ | |
| element: "table", | |
| className: "table is-bordered is-hoverable is-fullwidth", | |
| children: [ | |
| { | |
| element: "thead", | |
| children: { | |
| element: "tr", | |
| children: [ | |
| { | |
| element: "td", | |
| style: "text-decoration: underline dotted;", | |
| text: "Cp", | |
| className: "is-narrow", | |
| title: "Select for comparison" | |
| }, | |
| { | |
| element: "td", | |
| text: "Token", | |
| className: "is-narrow", | |
| onclick: () => resultsUi.sortSessions(tableType, "token"), | |
| style: "cursor: pointer;", | |
| children: [ | |
| { | |
| element: "i", | |
| className: sortIcon, | |
| style: | |
| "padding-left: 20px; visibility:" + | |
| (sortIcon && | |
| sortDetail[tableType].sortColumn == "token" | |
| ? "visible;" | |
| : "hidden;") | |
| } | |
| ] | |
| }, | |
| { | |
| element: "td", | |
| text: "Browser", | |
| onclick: () => | |
| resultsUi.sortSessions(tableType, "browser"), | |
| style: "cursor: pointer;", | |
| className: "is-narrow", | |
| children: [ | |
| { | |
| element: "i", | |
| className: sortIcon, | |
| style: | |
| "padding-left: 20px; visibility:" + | |
| (sortIcon && | |
| sortDetail[tableType].sortColumn == "browser" | |
| ? "visible;" | |
| : "hidden;") | |
| } | |
| ] | |
| }, | |
| { | |
| element: "td", | |
| text: "Status", | |
| onclick: () => | |
| resultsUi.sortSessions(tableType, "status"), | |
| style: "cursor: pointer", | |
| className: "is-narrow", | |
| children: [ | |
| { | |
| element: "i", | |
| className: sortIcon, | |
| style: | |
| "padding-left: 20px; visibility:" + | |
| (sortIcon && | |
| sortDetail[tableType].sortColumn == "status" | |
| ? "visible;" | |
| : "hidden;") | |
| } | |
| ] | |
| }, | |
| { | |
| element: "td", | |
| text: "Date Started", | |
| onclick: () => | |
| resultsUi.sortSessions(tableType, "dateStarted"), | |
| style: "cursor: pointer;", | |
| className: "is-narrow", | |
| children: [ | |
| { | |
| element: "i", | |
| className: sortIcon, | |
| style: | |
| "padding-left: 20px; visibility:" + | |
| (sortIcon && | |
| sortDetail[tableType].sortColumn == "dateStarted" | |
| ? "visible;" | |
| : "hidden;") | |
| } | |
| ] | |
| }, | |
| { | |
| element: "td", | |
| text: "Labels", | |
| style: "cursor: pointer; width: 18rem" | |
| }, | |
| static | |
| ? null | |
| : { | |
| element: "td", | |
| text: "RefS", | |
| title: "Reference Sessions", | |
| style: "text-decoration: underline dotted;", | |
| className: "is-narrow" | |
| }, | |
| static | |
| ? null | |
| : { | |
| element: "td", | |
| colspan: 2, | |
| text: "Options", | |
| className: "is-narrow" | |
| } | |
| ] | |
| } | |
| }, | |
| { | |
| element: "tbody", | |
| children: sessions.map(session => ({ | |
| element: "tr", | |
| className: resultsUi.isSessionDisabled(session) | |
| ? "disabled-row" | |
| : "", | |
| style: "cursor: pointer", | |
| onclick: () => resultsUi.openSessionResult(session.token), | |
| children: [ | |
| { | |
| element: "td", | |
| onclick: event => event.stopPropagation(), | |
| style: "vertical-align: middle;", | |
| children: [ | |
| { | |
| element: "input", | |
| className: "checkbox", | |
| style: | |
| "width: 18px; height: 18px; margin-top: 0.55em", | |
| type: "checkbox", | |
| disabled: !resultsUi.isSessionValidForComparison( | |
| session | |
| ), | |
| checked: resultsUi.isSessionSelectedForComparison( | |
| session | |
| ), | |
| onchange: event => | |
| event.target.checked | |
| ? resultsUi.addSessionToComparison(session.token) | |
| : resultsUi.removeSessionFromComparison( | |
| session.token | |
| ) | |
| } | |
| ] | |
| }, | |
| { | |
| element: "td", | |
| className: "is-family-monospace", | |
| style: "vertical-align: middle;", | |
| text: session.token.split("-").shift() | |
| }, | |
| { | |
| element: "td", | |
| style: "vertical-align: middle; white-space: nowrap", | |
| text: session.browser.name + " " + session.browser.version | |
| }, | |
| { | |
| element: "td", | |
| style: "vertical-align: middle; text-align: center", | |
| children: [ | |
| { | |
| className: `tag ${getTagStyle(session.status)}`, | |
| text: session.status | |
| } | |
| ] | |
| }, | |
| { | |
| element: "td", | |
| style: "vertical-align: middle; white-space: nowrap", | |
| text: session.dateStarted | |
| ? new Date(session.dateStarted).toLocaleString() | |
| : "not started" | |
| }, | |
| { | |
| element: "td", | |
| children: { | |
| className: "tags field is-grouped isgrouped-multiline", | |
| style: "min-width: 10em", | |
| children: session.labels.map(label => ({ | |
| className: "control", | |
| children: { | |
| element: "span", | |
| className: "tag is-info", | |
| text: label | |
| } | |
| })) | |
| } | |
| }, | |
| static | |
| ? null | |
| : { | |
| element: "td", | |
| title: session.referenceTokens | |
| .map(token => token.split("-").shift()) | |
| .sort((tokenA, tokenB) => tokenA - tokenB) | |
| .join("\n"), | |
| style: "white-space:nowrap", | |
| children: (() => { | |
| const tokens = session.referenceTokens.slice(); | |
| let overflow = 0; | |
| if (tokens.length > 3) { | |
| overflow = tokens.length - 2; | |
| } | |
| if (overflow > 0) tokens.splice(2, overflow + 2); | |
| const children = tokens.map(token => { | |
| let icon = ""; | |
| const session = resultsUi.state.sessions[token]; | |
| switch (session.browser.name.toLowerCase()) { | |
| case "firefox": | |
| icon = "fab fa-firefox"; | |
| break; | |
| case "edge": | |
| icon = "fab fa-edge"; | |
| break; | |
| case "chrome": | |
| case "chromium": | |
| icon = "fab fa-chrome"; | |
| break; | |
| case "safari": | |
| case "webkit": | |
| icon = "fab fa-safari"; | |
| break; | |
| } | |
| return { | |
| element: "span", | |
| style: | |
| "margin-right: 5px; vertical-align: middle;", | |
| children: { element: "i", className: icon } | |
| }; | |
| }); | |
| if (overflow > 0) | |
| children.push({ | |
| element: "span", | |
| style: "vertical-align: middle", | |
| className: "is-size-7", | |
| text: `+${overflow}` | |
| }); | |
| return children; | |
| })() | |
| }, | |
| static | |
| ? null | |
| : { | |
| element: "td", | |
| style: "vertical-align: middle; text-align: center", | |
| className: "is-paddingless", | |
| children: [ | |
| { | |
| className: "button is-dark is-outlined is-small", | |
| title: pinned ? "Unpin session" : "Pin session", | |
| style: "margin: 5px", | |
| children: [ | |
| { | |
| element: "span", | |
| className: "icon", | |
| children: [ | |
| { | |
| element: "i", | |
| className: "fas fa-thumbtack", | |
| style: pinned | |
| ? "" | |
| : "transform: rotate(45deg)" | |
| } | |
| ] | |
| } | |
| ], | |
| onclick: event => { | |
| event.stopPropagation(); | |
| if (pinned) { | |
| resultsUi.unpinSession(session.token); | |
| } else { | |
| resultsUi.pinSession(session.token); | |
| } | |
| } | |
| } | |
| ] | |
| }, | |
| static | |
| ? null | |
| : { | |
| element: "td", | |
| style: "vertical-align: middle; text-align: center", | |
| className: "is-paddingless", | |
| children: [ | |
| { | |
| className: "button is-dark is-outlined is-small", | |
| title: "Remove session from list", | |
| style: "margin: 5px", | |
| children: [ | |
| { | |
| element: "span", | |
| className: "icon", | |
| children: [ | |
| { | |
| element: "i", | |
| className: "fas fa-trash-alt" | |
| } | |
| ] | |
| } | |
| ], | |
| onclick: event => { | |
| event.stopPropagation(); | |
| resultsUi.removeSession(session.token); | |
| } | |
| } | |
| ] | |
| } | |
| ] | |
| })) | |
| } | |
| ] | |
| }); | |
| }, | |
| updateCompareButton: () => { | |
| const compareButton = UI.getElement("compare-button"); | |
| if (resultsUi.isComparisonValid()) { | |
| compareButton.removeAttribute("disabled"); | |
| } else { | |
| compareButton.setAttribute("disabled", true); | |
| } | |
| } | |
| }; | |
| </script> | |
| </body> | |
| </html> |