feat: v0.2.0 — UI rebuilt to WordPress admin standards

v0.1.0 worked but felt like a third-party React widget bolted on
top of WordPress. v0.2.0 makes the player visually native to the
WP admin: postbox container, standard Play/Pause button with text
label, admin-colour-scheme aware accents, dashboard widget no
longer renders a card inside a card.

CHANGES
- inc/admin-page.php: main page now wraps the player in a
  .postbox > .postbox-header (with h2.hndle) > .inside structure.
  Custom rounded card / shadow stripped.
- inc/dashboard-widget.php: bare .radio-player content; WP already
  wraps dashboard widgets in a postbox, was double-card before.
- inc/about.php: version-history card promotes v0.2.0 to latest,
  demotes v0.1.0.
- assets/css/radio.css: rewrite. Strip custom shadows + oversized
  typography. Adopt WP body-text defaults. Use
  var(--wp-admin-theme-color, #2271b1) for volume-slider accent +
  link colours so the plugin picks up whichever admin colour
  scheme the user has chosen. About-page cards now use the
  postbox-style gray header + 1px border pattern.
- assets/js/radio.js: setPlayIcon() also flips the visible text
  label ("Play" ↔ "Pause"), not just the icon class. mirrorSelection()
  also updates the [data-radio-genre] element so the genre label
  stays in sync across surfaces.
- radio.php: Version: 0.1.0 -> 0.2.0; BUDDY_VERSION constant
  bumped likewise.
- CHANGELOG.md: new [0.2.0] entry explaining the visual overhaul.

NET EFFECT
- Same 44 stations, same audio path, same persistence, same
  updater, same AJAX endpoint. Pure visual change.
- The plugin now looks like part of WordPress admin instead of a
  guest widget.
- Closer to WP.org submission criteria — plugin reviewers look
  for native-styled plugins.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-26 10:03:51 +01:00
parent a22ddfb6d3
commit 3e6994461e
7 changed files with 245 additions and 146 deletions
+25
View File
@@ -9,6 +9,31 @@ Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) — versi
--- ---
## [0.2.0] — 2026-05-26
### Changed — UI rebuilt to WordPress admin standards
v0.1.0 worked but looked like a third-party React widget sitting on top of WordPress rather than part of it. v0.2.0 rebuilds the player UI to use WP-native patterns end-to-end.
- **Main page** now uses the standard **`.postbox` container** (gray header bar + `.inside` body) instead of a custom rounded-shadow card.
- **Play button** is now a standard **`.button .button-primary`** with both icon AND text label (Play / Pause), matching every other admin button. Replaces the giant blue circular icon-only button.
- **Now Playing** uses left-aligned default body text with `.description` muted-gray for the station tagline. Replaces the centered large-typography card.
- **Genre badge** moved to a small `.radio-player__station-genre` text label aligned right of the station name. Replaces the custom pill.
- **Volume slider** now uses `accent-color: var(--wp-admin-theme-color)` — adapts to whichever admin colour scheme the user has chosen (Default / Light / Modern / Blue / Coffee / Ectoplasm / Midnight / Ocean / Sunrise).
- **All link colours** likewise adapt to the user's admin theme via `var(--wp-admin-theme-color, #2271b1)`.
- **Dashboard widget** content sits bare inside its `.inside` — WordPress already wraps it in a postbox. v0.1.0 was rendering a card inside a card.
- **About page** cards now use postbox-style gray header bars + WP-standard 1px border + subtle shadow. Replaces the custom rounded grid.
- **Credit footer** uses `.description` class, smaller and more native.
### Net effect
The plugin feels like part of WordPress now, not bolted onto it. Picks up your admin colour scheme automatically. Closer to WP.org submission criteria — they look for native-styled plugins during plugin review.
### Not changed
- Functionality identical to v0.1.0 — same 44 stations, same audio path, same user_meta persistence, same updater, same AJAX endpoint.
- No behaviour change for end users; this is purely visual.
- About page version-history card promotes v0.2.0 to "latest", demotes v0.1.0.
---
## [0.1.0] — 2026-05-26 ## [0.1.0] — 2026-05-26
**Radio is born.** First release of a new standalone WordPress plugin extracted-and-rebuilt from the radio feature that lives inside RangerPlex. Radio stands on its own as a focused, friendly companion plugin for the WordPress dashboard — a tab of background music while you work. **Radio is born.** First release of a new standalone WordPress plugin extracted-and-rebuilt from the radio feature that lives inside RangerPlex. Radio stands on its own as a focused, friendly companion plugin for the WordPress dashboard — a tab of background music while you work.
+136 -88
View File
@@ -1,118 +1,109 @@
/* Radio — admin styles */ /* Radio — admin styles, WordPress-native */
/* ──────────────────────────────────────────────────────────────────
* Player layout (works inside .postbox .inside on main page,
* and bare on the dashboard widget where .inside is the parent)
* ─────────────────────────────────────────────────────────────── */
.radio-player { .radio-player {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 14px;
padding: 8px 0;
} }
/* Now Playing — left-aligned, body text size, no custom typography */
.radio-player__now { .radio-player__now {
text-align: center; display: flex;
padding-bottom: 6px; align-items: baseline;
border-bottom: 1px solid #e5e5e5; flex-wrap: wrap;
} gap: 8px;
padding-bottom: 10px;
.radio-player__now--large { border-bottom: 1px solid #dcdcde;
padding: 18px 0;
} }
.radio-player__label { .radio-player__label {
font-size: 11px; font-size: 11px;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.08em; letter-spacing: 0.06em;
color: #646970; color: #646970;
margin-bottom: 4px; font-weight: 600;
margin-right: 4px;
} }
.radio-player__station-name { .radio-player__station-name {
font-size: 16px; font-size: 14px;
font-weight: 600; font-weight: 600;
color: #1d2327; color: #1d2327;
} }
.radio-player__now--large .radio-player__station-name {
font-size: 24px;
}
.radio-player__station-desc { .radio-player__station-desc {
font-size: 13px; font-size: 13px;
color: #50575e; color: #50575e;
margin-top: 2px; flex: 1 1 100%;
margin: 0;
} }
.radio-player__station-genre { .radio-player__station-genre {
display: inline-block;
margin-top: 6px;
padding: 2px 10px;
background: #f0f0f1;
border-radius: 12px;
font-size: 11px; font-size: 11px;
color: #2c3338; color: #646970;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.04em; letter-spacing: 0.04em;
margin-left: auto;
} }
/* Controls — single row, native button + slider */
.radio-player__controls { .radio-player__controls {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
} flex-wrap: wrap;
.radio-player__controls--large {
justify-content: center;
gap: 20px;
padding: 8px 0;
} }
.radio-player__play { .radio-player__play {
display: inline-flex; /* native .button .button-primary styling; just ensure icon aligns */
display: inline-flex !important;
align-items: center; align-items: center;
justify-content: center; gap: 6px;
width: 40px;
height: 40px;
border-radius: 50% !important;
padding: 0 !important;
line-height: 1 !important;
} }
.radio-player__play .dashicons { .radio-player__play .dashicons {
font-size: 20px; font-size: 18px;
width: 20px; width: 18px;
height: 20px; height: 18px;
line-height: 1; line-height: 1;
} }
.radio-player__controls--large .radio-player__play {
width: 56px;
height: 56px;
}
.radio-player__controls--large .radio-player__play .dashicons {
font-size: 28px;
width: 28px;
height: 28px;
}
.radio-player__volume { .radio-player__volume {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 8px;
flex: 1; flex: 1;
min-width: 200px;
}
.radio-player__volume .dashicons {
color: #646970;
font-size: 16px;
width: 16px;
height: 16px;
} }
.radio-player__volume input[type="range"] { .radio-player__volume input[type="range"] {
flex: 1; flex: 1;
accent-color: #2271b1; margin: 0;
/* Use WP's admin theme colour for the slider thumb/track */
accent-color: var(--wp-admin-theme-color, #2271b1);
} }
.radio-player__volume-pct { .radio-player__volume-pct {
min-width: 36px; min-width: 36px;
font-size: 12px; font-size: 12px;
color: #50575e; color: #646970;
text-align: right; text-align: right;
font-variant-numeric: tabular-nums;
} }
/* Station selector — label-on-top, full-width select */
.radio-player__station-select { .radio-player__station-select {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -124,35 +115,38 @@
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.06em; letter-spacing: 0.06em;
color: #646970; color: #646970;
font-weight: 600;
} }
.radio-player__station-select select { .radio-player__station-select select {
width: 100%; width: 100%;
max-width: 480px; max-width: 100%;
} }
/* Error notice — uses WP notice styling */
.radio-player__error { .radio-player__error {
color: #b32d2e; color: #b32d2e;
font-size: 12px; font-size: 13px;
padding: 6px 10px; padding: 6px 10px;
background: #fcf0f1; background: #fcf0f1;
border-left: 3px solid #b32d2e; border-left: 4px solid #b32d2e;
border-radius: 2px; border-radius: 0;
}
.radio-player__credit {
font-size: 12px;
color: #646970;
text-align: center;
margin: 0; margin: 0;
} }
/* Credit footer */
.radio-player__credit {
margin: 0;
color: #646970;
font-size: 12px;
}
.radio-player__credit--main { .radio-player__credit--main {
margin-top: 18px; margin-top: 12px;
} }
.radio-player__credit a { .radio-player__credit a {
color: #2271b1; color: var(--wp-admin-theme-color, #2271b1);
text-decoration: none; text-decoration: none;
} }
@@ -160,54 +154,101 @@
text-decoration: underline; text-decoration: underline;
} }
/* Main page wraps the player at a comfortable max width. */ /* ──────────────────────────────────────────────────────────────────
.radio-wrap .radio-player--main { * Main admin page — wrap player in a postbox-like card
max-width: 640px; * ─────────────────────────────────────────────────────────────── */
padding: 20px 24px;
background: #fff; .radio-wrap .radio-player {
border: 1px solid #ccd0d4; margin-top: 4px;
border-radius: 6px;
margin-top: 18px;
} }
.radio-intro { .radio-intro {
max-width: 640px; margin: 0 0 16px;
color: #50575e; color: #50575e;
font-size: 13px;
max-width: 720px;
} }
/* About page layout */ /* ──────────────────────────────────────────────────────────────────
* Dashboard widget — no nested card; bare content inside .inside
* (WP renders the widget as a postbox already; don't double up)
* ─────────────────────────────────────────────────────────────── */
#radio_dashboard_widget .radio-player {
gap: 10px;
}
#radio_dashboard_widget .radio-player__now {
padding-bottom: 8px;
}
#radio_dashboard_widget .radio-player__station-name {
font-size: 14px;
}
#radio_dashboard_widget .radio-player__credit {
border-top: 1px solid #dcdcde;
padding-top: 8px;
margin-top: 4px;
text-align: center;
}
/* ──────────────────────────────────────────────────────────────────
* About page — postbox-style cards in a metabox column layout
* ─────────────────────────────────────────────────────────────── */
.radio-about-grid { .radio-about-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 18px; gap: 16px;
max-width: 1100px; max-width: 1100px;
margin-top: 16px;
} }
.radio-about-card { .radio-about-card {
background: #fff; background: #fff;
border: 1px solid #ccd0d4; border: 1px solid #c3c4c7;
border-radius: 6px; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
padding: 18px 20px;
} }
.radio-about-card h2 { .radio-about-card h2 {
margin-top: 0; margin: 0;
font-size: 16px; padding: 8px 12px;
font-size: 14px;
font-weight: 600;
background: #f6f7f7;
border-bottom: 1px solid #c3c4c7;
}
.radio-about-card > p,
.radio-about-card > ul,
.radio-about-card > a {
padding: 12px;
margin: 0;
}
.radio-about-card > p + p {
padding-top: 0;
} }
.radio-about-card--versions ul { .radio-about-card--versions ul {
list-style: none; list-style: none;
margin-left: 0; padding: 12px;
padding-left: 0; margin: 0;
} }
.radio-about-card--versions li { .radio-about-card--versions li {
margin-bottom: 12px; margin-bottom: 10px;
font-size: 13px;
}
.radio-about-card--versions li:last-child {
margin-bottom: 0;
} }
.radio-about-card--versions .ver { .radio-about-card--versions .ver {
font-weight: 600; font-weight: 600;
color: #2271b1; color: var(--wp-admin-theme-color, #2271b1);
} }
.radio-about-card--versions .latest { .radio-about-card--versions .latest {
@@ -219,10 +260,17 @@
border-radius: 9px; border-radius: 9px;
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
vertical-align: middle;
} }
.radio-about-changelog-link { .radio-about-changelog-link {
display: inline-block; display: inline-block;
margin-top: 8px; margin: 0 12px 12px;
font-size: 13px; font-size: 13px;
color: var(--wp-admin-theme-color, #2271b1);
text-decoration: none;
}
.radio-about-changelog-link:hover {
text-decoration: underline;
} }
+11 -2
View File
@@ -42,11 +42,16 @@
}).catch(function () { /* swallow — local UI already updated */ }); }).catch(function () { /* swallow — local UI already updated */ });
} }
/** Update play/pause button icon to reflect current audio state. */ /** Update play/pause button icon + label to reflect current audio state. */
function setPlayIcon(btn, playing) { function setPlayIcon(btn, playing) {
var icon = btn.querySelector('.dashicons'); var icon = btn.querySelector('.dashicons');
if (!icon) { return; } var label = btn.querySelector('[data-radio-play-label]');
if (icon) {
icon.className = 'dashicons ' + (playing ? 'dashicons-controls-pause' : 'dashicons-controls-play'); icon.className = 'dashicons ' + (playing ? 'dashicons-controls-pause' : 'dashicons-controls-play');
}
if (label) {
label.textContent = playing ? (cfg.strings.pause || 'Pause') : (cfg.strings.play || 'Play');
}
btn.setAttribute('title', playing ? (cfg.strings.pause || 'Pause') : (cfg.strings.play || 'Play')); btn.setAttribute('title', playing ? (cfg.strings.pause || 'Pause') : (cfg.strings.play || 'Play'));
} }
@@ -122,6 +127,8 @@
audio.load(); audio.load();
if (nameEl) { nameEl.textContent = s.name; } if (nameEl) { nameEl.textContent = s.name; }
if (descEl) { descEl.textContent = s.description || ''; } if (descEl) { descEl.textContent = s.description || ''; }
var genreEl = player.querySelector('[data-radio-genre]');
if (genreEl) { genreEl.textContent = s.genre || ''; }
// Mirror selection to any OTHER .radio-player surfaces on the page. // Mirror selection to any OTHER .radio-player surfaces on the page.
mirrorSelection(player, newId); mirrorSelection(player, newId);
saveState({ station_id: newId }); saveState({ station_id: newId });
@@ -157,8 +164,10 @@
if (s) { if (s) {
var nm = other.querySelector('[data-radio-name]'); var nm = other.querySelector('[data-radio-name]');
var dc = other.querySelector('[data-radio-desc]'); var dc = other.querySelector('[data-radio-desc]');
var gn = other.querySelector('[data-radio-genre]');
if (nm) { nm.textContent = s.name; } if (nm) { nm.textContent = s.name; }
if (dc) { dc.textContent = s.description || ''; } if (dc) { dc.textContent = s.description || ''; }
if (gn) { gn.textContent = s.genre || ''; }
} }
} }
}); });
+5 -1
View File
@@ -48,7 +48,11 @@ function radio_render_about_page() {
<h2><?php esc_html_e( 'Version history', 'radio' ); ?></h2> <h2><?php esc_html_e( 'Version history', 'radio' ); ?></h2>
<ul> <ul>
<li> <li>
<span class="ver">v0.1.0</span> &mdash; 26 May 2026 <span class="latest">latest</span><br> <span class="ver">v0.2.0</span> &mdash; 26 May 2026 <span class="latest">latest</span><br>
<?php esc_html_e( 'UI rebuilt to WordPress admin standards. Postbox container, native Play/Pause button with text label, picks up your admin colour scheme via var(--wp-admin-theme-color), genre badge moved inline, dashboard widget no longer renders a card-inside-a-card. Functionality identical to v0.1.0 — purely visual polish.', 'radio' ); ?>
</li>
<li>
<span class="ver">v0.1.0</span> &mdash; 26 May 2026<br>
<?php esc_html_e( 'First release. 44 SomaFM stations grouped by 10 genres, dashboard widget + dedicated admin page, per-user state in user_meta, self-hosted update checker against Gitea. Direct HTML5 audio playback — no proxy, no build step, no tracking.', 'radio' ); ?> <?php esc_html_e( 'First release. 44 SomaFM stations grouped by 10 genres, dashboard widget + dedicated admin page, per-user state in user_meta, self-hosted update checker against Gitea. Direct HTML5 audio playback — no proxy, no build step, no tracking.', 'radio' ); ?>
</li> </li>
</ul> </ul>
+25 -15
View File
@@ -2,9 +2,10 @@
/** /**
* Radio — main admin page (WP Admin → Radio → My Radio). * Radio — main admin page (WP Admin → Radio → My Radio).
* *
* Larger player with the same controls as the dashboard widget. Both * Uses WP-native postbox structure so the player feels like part of
* surfaces share the same JS (assets/js/radio.js binds to every * WordPress, not a third-party widget. The dashboard widget shares
* .radio-player on the page). * the same internal markup via assets/js/radio.js binding to every
* .radio-player on the page.
*/ */
if ( ! defined( 'ABSPATH' ) ) { exit; } if ( ! defined( 'ABSPATH' ) ) { exit; }
@@ -26,27 +27,34 @@ function radio_render_main_page() {
<?php <?php
printf( printf(
/* translators: %d = number of stations */ /* translators: %d = number of stations */
esc_html__( 'A friendly tab of background music for your WordPress admin. %d hand-curated SomaFM stations across 10 genres, all free, no ads.', 'radio' ), esc_html__( 'A tab of background music for your WordPress admin. %d hand-curated SomaFM stations across 10 genres free, no ads, no tracking.', 'radio' ),
(int) $count (int) $count
); );
?> ?>
</p> </p>
<div class="radio-player radio-player--main" data-radio-surface="main"> <div class="postbox">
<div class="radio-player__now radio-player__now--large"> <div class="postbox-header">
<div class="radio-player__label"><?php esc_html_e( 'Now Playing', 'radio' ); ?></div> <h2 class="hndle"><?php esc_html_e( 'Player', 'radio' ); ?></h2>
<div class="radio-player__station-name" data-radio-name><?php echo esc_html( $station['name'] ); ?></div> </div>
<div class="radio-player__station-desc" data-radio-desc><?php echo esc_html( $station['description'] ); ?></div> <div class="inside">
<div class="radio-player__station-genre"><?php echo esc_html( $station['genre'] ); ?></div> <div class="radio-player" data-radio-surface="main">
<div class="radio-player__now">
<span class="radio-player__label"><?php esc_html_e( 'Now Playing', 'radio' ); ?></span>
<span class="radio-player__station-name" data-radio-name><?php echo esc_html( $station['name'] ); ?></span>
<span class="radio-player__station-genre" data-radio-genre><?php echo esc_html( $station['genre'] ); ?></span>
<p class="radio-player__station-desc" data-radio-desc><?php echo esc_html( $station['description'] ); ?></p>
</div> </div>
<div class="radio-player__controls radio-player__controls--large"> <div class="radio-player__controls">
<button type="button" class="radio-player__play button button-primary button-hero" data-radio-play title="<?php esc_attr_e( 'Play', 'radio' ); ?>"> <button type="button" class="button button-primary radio-player__play" data-radio-play>
<span class="dashicons dashicons-controls-play"></span> <span class="dashicons dashicons-controls-play" aria-hidden="true"></span>
<span data-radio-play-label><?php esc_html_e( 'Play', 'radio' ); ?></span>
</button> </button>
<div class="radio-player__volume"> <div class="radio-player__volume">
<span class="dashicons dashicons-controls-volumeon"></span> <span class="dashicons dashicons-controls-volumeon" aria-hidden="true"></span>
<input type="range" min="0" max="100" value="<?php echo esc_attr( (int) round( $state['volume'] * 100 ) ); ?>" data-radio-volume aria-label="<?php esc_attr_e( 'Volume', 'radio' ); ?>"> <input type="range" min="0" max="100" value="<?php echo esc_attr( (int) round( $state['volume'] * 100 ) ); ?>" data-radio-volume aria-label="<?php esc_attr_e( 'Volume', 'radio' ); ?>">
<span class="radio-player__volume-pct" data-radio-volume-pct><?php echo esc_html( (int) round( $state['volume'] * 100 ) ); ?>%</span> <span class="radio-player__volume-pct" data-radio-volume-pct><?php echo esc_html( (int) round( $state['volume'] * 100 ) ); ?>%</span>
</div> </div>
@@ -73,8 +81,10 @@ function radio_render_main_page() {
<audio data-radio-audio preload="none"></audio> <audio data-radio-audio preload="none"></audio>
</div> </div>
</div>
</div>
<p class="radio-player__credit radio-player__credit--main"> <p class="radio-player__credit radio-player__credit--main description">
<?php <?php
printf( printf(
wp_kses( wp_kses(
+15 -12
View File
@@ -2,9 +2,9 @@
/** /**
* Radio — dashboard widget. * Radio — dashboard widget.
* *
* The compact mini-player on WP Admin → Dashboard. Same HTML structure * The compact mini-player on WP Admin → Dashboard. WordPress wraps
* as the main admin page but smaller, so the assets/js/radio.js can * dashboard widgets in a .postbox automatically, so we render bare
* bind to whichever surface the user opens first. * content here — no nested card styling.
*/ */
if ( ! defined( 'ABSPATH' ) ) { exit; } if ( ! defined( 'ABSPATH' ) ) { exit; }
@@ -31,21 +31,25 @@ function radio_render_dashboard_widget() {
$station = radio_find_station( $state['station_id'] ); $station = radio_find_station( $state['station_id'] );
$stations = radio_get_stations_grouped(); $stations = radio_get_stations_grouped();
?> ?>
<div class="radio-player radio-player--widget" data-radio-surface="widget"> <div class="radio-player" data-radio-surface="widget">
<div class="radio-player__now"> <div class="radio-player__now">
<div class="radio-player__label"><?php esc_html_e( 'Now Playing', 'radio' ); ?></div> <span class="radio-player__label"><?php esc_html_e( 'Now Playing', 'radio' ); ?></span>
<div class="radio-player__station-name" data-radio-name><?php echo esc_html( $station['name'] ); ?></div> <span class="radio-player__station-name" data-radio-name><?php echo esc_html( $station['name'] ); ?></span>
<div class="radio-player__station-desc" data-radio-desc><?php echo esc_html( $station['description'] ); ?></div> <span class="radio-player__station-genre" data-radio-genre><?php echo esc_html( $station['genre'] ); ?></span>
<p class="radio-player__station-desc" data-radio-desc><?php echo esc_html( $station['description'] ); ?></p>
</div> </div>
<div class="radio-player__controls"> <div class="radio-player__controls">
<button type="button" class="radio-player__play button button-primary" data-radio-play title="<?php esc_attr_e( 'Play', 'radio' ); ?>"> <button type="button" class="button button-primary radio-player__play" data-radio-play>
<span class="dashicons dashicons-controls-play"></span> <span class="dashicons dashicons-controls-play" aria-hidden="true"></span>
<span data-radio-play-label><?php esc_html_e( 'Play', 'radio' ); ?></span>
</button> </button>
<div class="radio-player__volume"> <div class="radio-player__volume">
<span class="dashicons dashicons-controls-volumeon"></span> <span class="dashicons dashicons-controls-volumeon" aria-hidden="true"></span>
<input type="range" min="0" max="100" value="<?php echo esc_attr( (int) round( $state['volume'] * 100 ) ); ?>" data-radio-volume aria-label="<?php esc_attr_e( 'Volume', 'radio' ); ?>"> <input type="range" min="0" max="100" value="<?php echo esc_attr( (int) round( $state['volume'] * 100 ) ); ?>" data-radio-volume aria-label="<?php esc_attr_e( 'Volume', 'radio' ); ?>">
<span class="radio-player__volume-pct" data-radio-volume-pct><?php echo esc_html( (int) round( $state['volume'] * 100 ) ); ?>%</span>
</div> </div>
</div> </div>
@@ -57,7 +61,7 @@ function radio_render_dashboard_widget() {
?> ?>
<optgroup label="<?php echo esc_attr( $genre ); ?>"> <optgroup label="<?php echo esc_attr( $genre ); ?>">
<?php foreach ( $entries as $entry ) : ?> <?php foreach ( $entries as $entry ) : ?>
<option value="<?php echo esc_attr( $entry['id'] ); ?>" data-url="<?php echo esc_attr( $entry['url'] ); ?>" data-desc="<?php echo esc_attr( $entry['description'] ); ?>" <?php selected( $entry['id'], $state['station_id'] ); ?>> <option value="<?php echo esc_attr( $entry['id'] ); ?>" data-url="<?php echo esc_attr( $entry['url'] ); ?>" data-desc="<?php echo esc_attr( $entry['description'] ); ?>" data-genre="<?php echo esc_attr( $entry['genre'] ); ?>" <?php selected( $entry['id'], $state['station_id'] ); ?>>
<?php echo esc_html( $entry['name'] ); ?> <?php echo esc_html( $entry['name'] ); ?>
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
@@ -72,7 +76,6 @@ function radio_render_dashboard_widget() {
<p class="radio-player__credit"> <p class="radio-player__credit">
<?php <?php
/* translators: %s = SomaFM link */
printf( printf(
wp_kses( wp_kses(
/* translators: %s = link to somafm.com */ /* translators: %s = link to somafm.com */
+2 -2
View File
@@ -5,7 +5,7 @@
* Plugin Name: Radio * Plugin Name: Radio
* Plugin URI: https://icanhelp.ie/radio * Plugin URI: https://icanhelp.ie/radio
* Description: A small, focused, free radio player for your WordPress admin. 44 SomaFM stations grouped by 10 genres — ambient, electronic, lounge, rock, metal, jazz, world, reggae, holiday, specials. Plays via HTML5 audio; volume + station choice persist per-user. * Description: A small, focused, free radio player for your WordPress admin. 44 SomaFM stations grouped by 10 genres — ambient, electronic, lounge, rock, metal, jazz, world, reggae, holiday, specials. Plays via HTML5 audio; volume + station choice persist per-user.
* Version: 0.1.0 * Version: 0.2.0
* Requires at least: 5.0 * Requires at least: 5.0
* Requires PHP: 7.4 * Requires PHP: 7.4
* Author: David Keane * Author: David Keane
@@ -20,7 +20,7 @@
if ( ! defined( 'ABSPATH' ) ) { exit; } if ( ! defined( 'ABSPATH' ) ) { exit; }
// Plugin coordinates. // Plugin coordinates.
if ( ! defined( 'RADIO_VERSION' ) ) { define( 'RADIO_VERSION', '0.1.0' ); } if ( ! defined( 'RADIO_VERSION' ) ) { define( 'RADIO_VERSION', '0.2.0' ); }
if ( ! defined( 'RADIO_FILE' ) ) { define( 'RADIO_FILE', __FILE__ ); } if ( ! defined( 'RADIO_FILE' ) ) { define( 'RADIO_FILE', __FILE__ ); }
if ( ! defined( 'RADIO_PATH' ) ) { define( 'RADIO_PATH', plugin_dir_path( __FILE__ ) ); } if ( ! defined( 'RADIO_PATH' ) ) { define( 'RADIO_PATH', plugin_dir_path( __FILE__ ) ); }
if ( ! defined( 'RADIO_URL' ) ) { define( 'RADIO_URL', plugin_dir_url( __FILE__ ) ); } if ( ! defined( 'RADIO_URL' ) ) { define( 'RADIO_URL', plugin_dir_url( __FILE__ ) ); }