-
Notifications
You must be signed in to change notification settings - Fork 62
Add an options page with past runs and some controls #116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
320093e
f2f68ed
11a1e2b
180d843
821fad8
4c496a8
bbffa91
da268dc
10270d1
b56dd3b
0a24440
baf1d66
524eb68
950dadb
79c1b77
6da8e0e
523a76f
10e939b
f1ae347
030d789
f03c4bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| /** | ||
| * @license | ||
| * Copyright Google LLC All Rights Reserved. | ||
| * | ||
| * Use of this source code is governed by an MIT-style license that can be | ||
| * found in the LICENSE file at https://github.com/material-components/material-components-web/blob/master/LICENSE | ||
| */ | ||
| :root { | ||
| --mdc-theme-primary: rgb(26, 115, 232); | ||
| --mdc-theme-secondary: rgba(0, 0, 0, 0.04); | ||
| --icon-margin-horizontal: 5px; | ||
| } | ||
|
|
||
| body { | ||
| font-family: Roboto, sans-serif; | ||
| color: #485656; | ||
| margin: 0; | ||
| } | ||
|
|
||
| a, | ||
| a:visited { | ||
| color: inherit; | ||
| text-decoration: none; | ||
| } | ||
|
|
||
| .d-block { | ||
| display: block; | ||
| } | ||
|
|
||
| .d-flex { | ||
| display: flex; | ||
| } | ||
|
|
||
| .d-none { | ||
| display: none; | ||
| } | ||
|
|
||
| .flex-align-center { | ||
| align-items: center; | ||
| } | ||
|
|
||
| .flex-justify-center { | ||
| justify-content: center; | ||
| } | ||
|
|
||
| .mdc-top-app-bar__row { | ||
| width: 90%; | ||
| margin-left: auto; | ||
| margin-right: auto; | ||
| } | ||
|
|
||
| .mdc-top-app-bar__row .mdc-top-app-bar__section { | ||
| padding-left: 0; | ||
| padding-right: 0; | ||
| } | ||
|
|
||
| .logo-container { | ||
| display: inline-flex; | ||
| align-items: center; | ||
| padding: 0; | ||
| } | ||
|
|
||
| .logo-container .logo { | ||
| margin-right: 10px; | ||
| } | ||
|
|
||
| .app-bar-link { | ||
| display: inline-flex; | ||
| align-items: center; | ||
| margin: 0 10px; | ||
| } | ||
|
|
||
| .app-bar-link .material-icons { | ||
| margin-right: var(--icon-margin-horizontal); | ||
| } | ||
|
|
||
| .tabDetails .tabSingle { | ||
| display: none; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| } | ||
|
|
||
| .tabDetails .tabSingle.tabActive { | ||
| display: flex; | ||
| } | ||
|
|
||
| #statusContainer .btns { | ||
| margin-top: 1rem; | ||
| } | ||
|
|
||
| #statusContainer.statusRunning .stopBtn, | ||
| #statusContainer:not(.statusRunning) .runBtn { | ||
| display: block; | ||
| } | ||
|
|
||
| #statusContainer:not(.statusRunning) .stopBtn, | ||
| #statusContainer.statusRunning .runBtn { | ||
| display: none; | ||
| } | ||
|
|
||
| #statusContainer .statusRow { | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| } | ||
|
|
||
| #statusContainer .statusRow .value { | ||
| text-transform: capitalize; | ||
| font-size: 1rem; | ||
| } | ||
|
|
||
| #progress { | ||
| width: 200px; | ||
| height: 200px; | ||
| position: relative; | ||
| } | ||
|
|
||
| #statusContainer:not(.statusRunning) .completedVal, | ||
| #statusContainer:not(.statusRunning) .totalVal { | ||
| display: none; | ||
| } | ||
|
|
||
| #progress .completedVal { | ||
| font-size: 2rem; | ||
| border-bottom: 2px solid var(--mdc-theme-primary); | ||
| position: absolute; | ||
| left: 50%; | ||
| top: 50%; | ||
| transform: translate(-50%, -100%); | ||
| } | ||
|
|
||
| #progress .totalVal { | ||
| font-size: 2rem; | ||
| position: absolute; | ||
| left: 50%; | ||
| top: 50%; | ||
| transform: translate(-50%, 0); | ||
| } | ||
|
|
||
| #progress .mdc-circular-progress { | ||
| width: 100%; | ||
| height: 100%; | ||
| display: none; | ||
| } | ||
|
|
||
| #statusContainer.statusRunning .mdc-circular-progress { | ||
| display: block; | ||
| } | ||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| window.onload = async function () { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. need a license header
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer addEventListener when registering an event listener. |
||
| initComponents(); | ||
| showCurrentRuns(); | ||
| updateCurrentRunningStatus(await getCurrentRunningStatus()); | ||
| }; | ||
|
|
||
| let currentProgress; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add a comment for a global variable. |
||
|
|
||
| /** | ||
| * Message received from other parts of the extension | ||
| */ | ||
| chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { | ||
| if (request.action === "sync_ping_status") { | ||
| updateCurrentRunningStatus(request.currentStatus); | ||
| } | ||
| }); | ||
|
|
||
| /** | ||
| * Loads and shows the latest runs in the options page | ||
| */ | ||
| async function showCurrentRuns() { | ||
| const runs = await getCurrentRuns(); | ||
| const container = document.getElementById("runsListContainer"); | ||
|
|
||
| runs.forEach(async (runStartTime) => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer |
||
| const row = document.createElement("tr"); | ||
| row.classList.add("mdc-data-table__header-row"); | ||
|
|
||
| const cell1 = document.createElement("td"); | ||
| cell1.setAttribute("data-run-id", runStartTime); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using more descriptive names for cell1 and cell2. Maybe startTimeCell and latencyCell or something like that? |
||
| cell1.innerText = getFormattedTime(new Date(runStartTime)); | ||
| cell1.classList.add("mdc-data-table__cell"); | ||
|
|
||
| const cell2 = document.createElement("td"); | ||
| const fastestRun = await getFastestPingInRun(runStartTime); | ||
| cell2.innerText = `${fastestRun.region} (${fastestRun.ping} ms)`; | ||
| cell2.setAttribute("data-run-id", runStartTime); | ||
| cell2.classList.add("mdc-data-table__cell"); | ||
|
|
||
| row.appendChild(cell1); | ||
| row.appendChild(cell2); | ||
|
|
||
| container.appendChild(row); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Returns the information for the fastest region for a saved run recognized by a runId | ||
| * @param {int} runId | ||
| * @return {Object} | ||
| */ | ||
| async function getFastestPingInRun(runId) { | ||
| const data = await getRunData(runId); | ||
| let fastestRegion = null; | ||
|
|
||
| // loop therough the results of the run to find the fastest region | ||
| for (const region of Object.keys(data.results)) { | ||
| if ( | ||
| fastestRegion === null || | ||
| data.results[fastestRegion] > data.results[region] | ||
| ) { | ||
| fastestRegion = region; | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| region: fastestRegion, | ||
| ping: data.results[fastestRegion], | ||
| runId: runId, | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Fetches the current stored runs from chrome.storage.local | ||
| * @return {Promise} | ||
| */ | ||
| async function getCurrentRuns() { | ||
| return new Promise((resolve, reject) => { | ||
| chrome.storage.local.get("runs", (result) => { | ||
| // return an empty array by default | ||
| resolve(result["runs"] ?? []); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Fetches the details of a single historical run from chrome.storage.local | ||
| * @param {object} runId | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this really an object? It seems to take a number. |
||
| */ | ||
| async function getRunData(runId) { | ||
| runId = "run-" + runId; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find this structure a bit confusing because the passed runId is a start time but also a run id, but you're reassigning the value with the "run-" prefix. Which format is the canonical run ID? |
||
| return new Promise((resolve, reject) => { | ||
| chrome.storage.local.get(runId, (result) => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. get returns a Promise. |
||
| // return an empty array by default | ||
| resolve(result[runId] ?? false); | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Handler for when a tab is clicked | ||
| * @param {int} selectedIndex | ||
| */ | ||
| function focusTab(selectedIndex) { | ||
| const tabs = document | ||
| .querySelector(".tabDetails") | ||
| .querySelectorAll(".tabSingle"); | ||
| [...tabs].forEach((tab, index) => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NodeList has forEach since Chrome 51 |
||
| if (index === selectedIndex) { | ||
| tab.classList.add("tabActive"); | ||
| } else { | ||
| tab.classList.remove("tabActive"); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Helper to initialize all the material components | ||
| */ | ||
| function initComponents() { | ||
| new mdc.tabBar.MDCTabBar(document.querySelector(".mdc-tab-bar")); | ||
|
|
||
| currentProgress = new mdc.circularProgress.MDCCircularProgress( | ||
| document.getElementById("currentProgress") | ||
| ); | ||
|
|
||
| currentProgress.progress = 0.8; | ||
|
|
||
| // Handle tab click | ||
| document | ||
| .querySelector(".mdc-tab-bar") | ||
| .addEventListener("MDCTabBar:activated", function (ev) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use () => instead of function |
||
| const tabIndex = ev?.detail?.index ?? 0; | ||
|
|
||
| focusTab(tabIndex); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Helper to return formatted time given a Date object | ||
| * @param {Date} dt | ||
| * @return {string} | ||
| */ | ||
| function getFormattedTime(dt) { | ||
| return ( | ||
| new Intl.DateTimeFormat("en-US", { dateStyle: "long" }).format(dt) + | ||
| ", " + | ||
| new Intl.DateTimeFormat("en-US", { timeStyle: "medium" }).format(dt) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a reason why you specify the en-US locale instead of the user default? |
||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Updates the current status for the ping test in the UI. | ||
| * @param {Object} currentStatus | ||
| */ | ||
| async function updateCurrentRunningStatus(currentStatus) { | ||
| const container = document.getElementById("statusContainer"); | ||
|
|
||
| container.querySelector(".statusRow").querySelector(".value").innerText = | ||
| currentStatus.status; | ||
|
|
||
| if (currentStatus.status === "running") { | ||
| container.classList.add("statusRunning"); | ||
| container.querySelector(".completedVal").innerText = | ||
| currentStatus.completed; | ||
| container.querySelector(".totalVal").innerText = currentStatus.total; | ||
|
|
||
| const ratio = currentStatus.completed / currentStatus.total; | ||
| currentProgress.progress = ratio; | ||
| } else { | ||
| container.classList.remove("statusRunning"); | ||
| currentProgress.progress = 0; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Fetches the current status of the ping test | ||
| */ | ||
| async function getCurrentRunningStatus() { | ||
| return new Promise((resolve) => { | ||
| chrome.runtime.sendMessage( | ||
| { action: "fetch_current_status" }, | ||
| function (response) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto, avoid function expressions. |
||
| resolve(response); | ||
| } | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Event handlers for button clicks | ||
| */ | ||
| document.querySelector("button.runBtn").addEventListener("click", function (e) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it ok to touch DOM here instead of inside of Load or DOMContentLoaded? |
||
| chrome.runtime.sendMessage({ action: "run_test" }, () => {}); | ||
| }); | ||
|
|
||
| document | ||
| .querySelector("button.stopBtn") | ||
| .addEventListener("click", function (e) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same, use arrow function. |
||
| chrome.runtime.sendMessage({ action: "stop_test" }, () => {}); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.