Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,117 @@
<title>Azure Speed Test 2.0</title>
<link rel="icon" type="image/png" href="/favicon.png" />
<style>
:root {
--bg-color: #ffffff;
--text-color: #212529;
--jumbotron-bg: #e9ecef;
--table-bg: #ffffff;
--table-hover-bg: #f8f9fa;
--table-header-bg: #e9ecef;
--table-border-color: #dee2e6;
--progress-bg: #e9ecef;
--badge-bg: #dc3545;
--sparkline-color: #B8BABC;
--row-gradient-color: #e9ecef;
}

[data-theme="dark"] {
--bg-color: #212529;
--text-color: #f8f9fa;
--jumbotron-bg: #343a40;
--table-bg: #2b3035;
--table-hover-bg: #343a40;
--table-header-bg: #24282d;
--table-border-color: #495057;
--progress-bg: #495057;
--badge-bg: #dc3545;
--sparkline-color: #6c757d;
--row-gradient-color: #343a40;
}

@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg-color: #212529;
--text-color: #f8f9fa;
--jumbotron-bg: #343a40;
--table-bg: #2b3035;
--table-hover-bg: #343a40;
--table-header-bg: #24282d;
--table-border-color: #495057;
--progress-bg: #495057;
--badge-bg: #dc3545;
--sparkline-color: #6c757d;
--row-gradient-color: #343a40;
}
}

body {
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}

.jumbotron {
background-color: var(--jumbotron-bg);
color: var(--text-color);
}

.table {
background-color: var(--table-bg);
color: var(--text-color);
}

.table thead th {
background-color: var(--table-header-bg);
color: var(--text-color);
border-color: var(--table-border-color);
}

.table .thead-light th {
background-color: var(--table-header-bg) !important;
color: var(--text-color) !important;
border-color: var(--table-border-color) !important;
}

.table td, .table th {
border-color: var(--table-border-color);
}

.table-hover tbody tr:hover {
background-color: var(--table-hover-bg);
color: var(--text-color);
}

.progress {
background-color: var(--progress-bg);
}

.theme-toggle {
position: absolute;
top: 20px;
right: 20px;
z-index: 1000;
background: transparent;
border: none;
border-radius: 50%;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
padding: 0;
color: var(--text-color);
}

.theme-toggle svg {
display: block;
}

.theme-toggle:hover {
transform: scale(1.1);
}

.icon {
width: 25px;
Expand Down Expand Up @@ -68,6 +179,20 @@ <h4>Initializing Azure Speed Test</h4>

<noscript>You need to enabled JavaScript for this web application to work.</noscript>

<script>
// Initialize theme before content loads to prevent flash
(function() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
} else {
// Check system preference
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
document.documentElement.setAttribute('data-theme', 'dark');
}
}
})();
</script>
<script>
// Suppress network error messages in console for cleaner output during latency testing
// Save original console.error for debugging purposes if needed
Expand Down
90 changes: 87 additions & 3 deletions index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ let progressState = {
isVisible: true
}

// Theme management
const getTheme = () => {
const saved = localStorage.getItem('theme')
if (saved) return saved
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark'
}
return 'light'
}

const setTheme = (theme) => {
localStorage.setItem('theme', theme)
document.documentElement.setAttribute('data-theme', theme)
}

// Record history
speedtest.on(history.record)

Expand All @@ -36,6 +51,9 @@ speedtest.on(() => {
}
lastRenderTime = now

// Clear CSS cache before re-render to pick up theme changes
cssVariablesCache = null

const scrollPosition = window.scrollY
render(<Table history={history.read()} blockList={globalBlockList} />)

Expand Down Expand Up @@ -95,6 +113,50 @@ function render(jsx) {
}
}

/**
* Theme toggle button component
* @returns {React.Element} Theme toggle button
*/
const ThemeToggle = () => {
const [theme, setThemeState] = React.useState(getTheme())

const handleToggle = () => {
const current = getTheme()
const next = current === 'dark' ? 'light' : 'dark'
setTheme(next)
setThemeState(next)
// Clear CSS cache to pick up new theme colors
cssVariablesCache = null
}

return (
<button
className="theme-toggle"
onClick={handleToggle}
title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
>
{theme === 'dark' ? (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1" opacity="0.6">
<circle cx="12" cy="12" r="5"/>
<line x1="12" y1="1" x2="12" y2="3"/>
<line x1="12" y1="21" x2="12" y2="23"/>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
<line x1="1" y1="12" x2="3" y2="12"/>
<line x1="21" y1="12" x2="23" y2="12"/>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
</svg>
) : (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1" opacity="0.6">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
</svg>
)}
</button>
)
}

/**
* Progress indicator component for warm-up phase
* @param {Object} props - Component props
Expand All @@ -105,7 +167,9 @@ const ProgressIndicator = ({ progress }) => {
const { completed, total, percentage, phase } = progress

return (
<div className="text-center mt-5">
<div>
<ThemeToggle />
<div className="text-center mt-5">
<div className="mb-4">
<div className="spinner-border text-primary mb-3" role="status">
<span className="sr-only">Loading...</span>
Expand Down Expand Up @@ -143,6 +207,7 @@ const ProgressIndicator = ({ progress }) => {
</small>
</div>
)}
</div>
</div>
)
}
Expand All @@ -164,15 +229,33 @@ const renderFlag = (item) => {
)
}

/**
* Get CSS variable values (cached per render cycle)
*/
let cssVariablesCache = null
const getCSSVariables = () => {
if (!cssVariablesCache) {
const styles = getComputedStyle(document.documentElement)
cssVariablesCache = {
gradientColor: styles.getPropertyValue('--row-gradient-color').trim(),
bgColor: styles.getPropertyValue('--table-bg').trim(),
sparklineColor: styles.getPropertyValue('--sparkline-color').trim()
}
}
return cssVariablesCache
}

/**
* Render a data row for active locations
* @param {Object} item - Location data with latency information
* @returns {React.Element} Table row element
*/
const renderRow = (item) => {
const percentage = Math.min(Math.round(item.percent || 0), 100)
const { gradientColor, bgColor, sparklineColor } = getCSSVariables()

const rowStyle = {
backgroundImage: `linear-gradient(to right, #e9ecef ${percentage}%, #ffffff ${percentage}%)`
backgroundImage: `linear-gradient(to right, ${gradientColor} ${percentage}%, ${bgColor} ${percentage}%)`
}

return (
Expand All @@ -194,7 +277,7 @@ const renderRow = (item) => {
margin={2}
>
<SparklinesLine
color="#B8BABC"
color={sparklineColor}
style={{ strokeWidth: 2 }}
/>
</Sparklines>
Expand Down Expand Up @@ -255,6 +338,7 @@ const Table = ({ history = [], blockList = [] }) => {

return (
<div>
<ThemeToggle />
<div className="mb-3">
<small className="text-muted">
Testing {history.length + blockList.length} Azure regions | {' '}
Expand Down
2 changes: 1 addition & 1 deletion index.min.js

Large diffs are not rendered by default.