Skip to content

Commit 39b0652

Browse files
committed
Add backup/restore for favorites and custom stations (JSON export)
- Export now saves both favorites AND custom stations (version 2.0 format) - Import handles both v1.0 (favorites only) and v2.0 (favorites + custom) - Added visible Backup/Restore buttons to compact card - Renamed settings label from "Favorites" to "Backup & Restore" in main card - Both cards use the same JSON format for full compatibility Data survives cache clearing when backed up to a JSON file. https://claude.ai/code/session_01PHdKi2HPbQoM3EevzgSQFc
1 parent 3f35624 commit 39b0652

File tree

1 file changed

+118
-9
lines changed

1 file changed

+118
-9
lines changed

radio-browser-card.js

Lines changed: 118 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -306,19 +306,20 @@ class RadioBrowserCard extends HTMLElement {
306306
return this._favorites.some(fav => fav.media_content_id === station.media_content_id);
307307
}
308308

309-
// Export/Import favorites
309+
// Export/Import favorites + custom stations
310310
exportFavorites() {
311311
const data = {
312-
version: '1.0',
312+
version: '2.0',
313313
exported: new Date().toISOString(),
314-
favorites: this._favorites
314+
favorites: this._favorites,
315+
custom_stations: this._customStations
315316
};
316317
const json = JSON.stringify(data, null, 2);
317318
const blob = new Blob([json], { type: 'application/json' });
318319
const url = URL.createObjectURL(blob);
319320
const a = document.createElement('a');
320321
a.href = url;
321-
a.download = `radio-favorites-${new Date().toISOString().split('T')[0]}.json`;
322+
a.download = `radio-backup-${new Date().toISOString().split('T')[0]}.json`;
322323
a.click();
323324
URL.revokeObjectURL(url);
324325
}
@@ -335,16 +336,25 @@ class RadioBrowserCard extends HTMLElement {
335336
reader.onload = (event) => {
336337
try {
337338
const data = JSON.parse(event.target.result);
339+
const parts = [];
338340
if (data.favorites && Array.isArray(data.favorites)) {
339341
this._favorites = data.favorites;
340342
this.saveFavorites();
343+
parts.push(`${data.favorites.length} favorites`);
344+
}
345+
if (data.custom_stations && Array.isArray(data.custom_stations)) {
346+
this._customStations = data.custom_stations;
347+
this.saveCustomStations();
348+
parts.push(`${data.custom_stations.length} custom stations`);
349+
}
350+
if (parts.length > 0) {
341351
this.updatePlaylist();
342-
alert(`Imported ${data.favorites.length} favorite stations!`);
352+
alert(`Imported: ${parts.join(', ')}!`);
343353
} else {
344354
alert('Invalid file format');
345355
}
346356
} catch (error) {
347-
alert('Error importing favorites: ' + error.message);
357+
alert('Error importing: ' + error.message);
348358
}
349359
};
350360
reader.readAsText(file);
@@ -2239,10 +2249,10 @@ class RadioBrowserCard extends HTMLElement {
22392249
</div>
22402250
</div>
22412251
<div class="settings-group">
2242-
<div class="settings-label">Favorites</div>
2252+
<div class="settings-label">Backup & Restore</div>
22432253
<div class="settings-options">
2244-
<button class="settings-option" onclick="this.getRootNode().host.exportFavorites()">📤 Export</button>
2245-
<button class="settings-option" onclick="this.getRootNode().host.importFavorites()">📥 Import</button>
2254+
<button class="settings-option" onclick="this.getRootNode().host.exportFavorites()">📤 Backup</button>
2255+
<button class="settings-option" onclick="this.getRootNode().host.importFavorites()">📥 Restore</button>
22462256
</div>
22472257
</div>
22482258
</div>
@@ -3004,6 +3014,61 @@ class RadioBrowserCardCompact extends HTMLElement {
30043014
return div.innerHTML;
30053015
}
30063016

3017+
// --- Export/Import (shared format with main card) ---
3018+
_exportFavorites() {
3019+
const favorites = this._loadFavorites();
3020+
const customStations = this._loadCustomStations();
3021+
const data = {
3022+
version: '2.0',
3023+
exported: new Date().toISOString(),
3024+
favorites: favorites,
3025+
custom_stations: customStations
3026+
};
3027+
const json = JSON.stringify(data, null, 2);
3028+
const blob = new Blob([json], { type: 'application/json' });
3029+
const url = URL.createObjectURL(blob);
3030+
const a = document.createElement('a');
3031+
a.href = url;
3032+
a.download = `radio-backup-${new Date().toISOString().split('T')[0]}.json`;
3033+
a.click();
3034+
URL.revokeObjectURL(url);
3035+
}
3036+
3037+
_importFavorites() {
3038+
const input = document.createElement('input');
3039+
input.type = 'file';
3040+
input.accept = 'application/json';
3041+
input.onchange = (e) => {
3042+
const file = e.target.files[0];
3043+
if (!file) return;
3044+
const reader = new FileReader();
3045+
reader.onload = (event) => {
3046+
try {
3047+
const data = JSON.parse(event.target.result);
3048+
const parts = [];
3049+
if (data.favorites && Array.isArray(data.favorites)) {
3050+
localStorage.setItem('radio_favorites', JSON.stringify(data.favorites));
3051+
parts.push(`${data.favorites.length} favorites`);
3052+
}
3053+
if (data.custom_stations && Array.isArray(data.custom_stations)) {
3054+
localStorage.setItem('radio_custom_stations', JSON.stringify(data.custom_stations));
3055+
parts.push(`${data.custom_stations.length} custom stations`);
3056+
}
3057+
if (parts.length > 0) {
3058+
this._updateStationSelect();
3059+
alert(`Imported: ${parts.join(', ')}!`);
3060+
} else {
3061+
alert('Invalid file format');
3062+
}
3063+
} catch (error) {
3064+
alert('Error importing: ' + error.message);
3065+
}
3066+
};
3067+
reader.readAsText(file);
3068+
};
3069+
input.click();
3070+
}
3071+
30073072
// --- Render ---
30083073
render() {
30093074
const c = this._getThemeColors();
@@ -3213,6 +3278,34 @@ class RadioBrowserCardCompact extends HTMLElement {
32133278
color: ${c.primary};
32143279
text-decoration: none;
32153280
}
3281+
3282+
/* Backup row */
3283+
.compact-backup-row {
3284+
display: flex;
3285+
gap: 4px;
3286+
margin-top: 8px;
3287+
}
3288+
.compact-backup-btn {
3289+
flex: 1;
3290+
height: 26px;
3291+
border: 1px solid ${c.surfaceLighter};
3292+
border-radius: 6px;
3293+
background: ${c.surfaceLight};
3294+
color: ${c.textSecondary};
3295+
font-size: 10px;
3296+
font-family: inherit;
3297+
cursor: pointer;
3298+
display: flex;
3299+
align-items: center;
3300+
justify-content: center;
3301+
gap: 4px;
3302+
transition: all 0.15s;
3303+
}
3304+
.compact-backup-btn:hover {
3305+
background: ${c.surfaceLighter};
3306+
color: ${c.text};
3307+
border-color: ${c.primary};
3308+
}
32163309
</style>
32173310
32183311
<div class="compact-card">
@@ -3244,6 +3337,12 @@ class RadioBrowserCardCompact extends HTMLElement {
32443337
<input type="range" class="compact-volume" min="0" max="100" value="${this._volume}" style="--vol: ${this._volume}%;">
32453338
</div>
32463339
3340+
<!-- Backup buttons -->
3341+
<div class="compact-backup-row">
3342+
<button class="compact-backup-btn compact-export-btn" title="Export favorites & stations to JSON file">📤 Backup</button>
3343+
<button class="compact-backup-btn compact-import-btn" title="Import favorites & stations from JSON file">📥 Restore</button>
3344+
</div>
3345+
32473346
<audio class="compact-audio" style="display:none;"></audio>
32483347
</div>
32493348
`;
@@ -3296,6 +3395,16 @@ class RadioBrowserCardCompact extends HTMLElement {
32963395
if (muteBtn) {
32973396
muteBtn.addEventListener('click', () => this._toggleMute());
32983397
}
3398+
3399+
// Backup buttons
3400+
const exportBtn = root.querySelector('.compact-export-btn');
3401+
if (exportBtn) {
3402+
exportBtn.addEventListener('click', () => this._exportFavorites());
3403+
}
3404+
const importBtn = root.querySelector('.compact-import-btn');
3405+
if (importBtn) {
3406+
importBtn.addEventListener('click', () => this._importFavorites());
3407+
}
32993408
}
33003409

33013410
disconnectedCallback() {

0 commit comments

Comments
 (0)