Skip to content

Commit 5d5164e

Browse files
authored
Merge pull request #38 from joshuaaaaa/claude/design-improvements-vcoSk
Claude/design improvements vco sk
2 parents c87b57c + 5fdfa35 commit 5d5164e

File tree

1 file changed

+215
-9
lines changed

1 file changed

+215
-9
lines changed

radio-browser-card.js

Lines changed: 215 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ class RadioBrowserCard extends HTMLElement {
6969
}
7070
}
7171

72+
// On first hass connection, auto-restore from HA server if cache was cleared
73+
if (!oldHass && hass) {
74+
this._autoRestoreFromHA();
75+
}
76+
7277
if (!oldHass || Object.keys(oldHass.states).length !== Object.keys(hass.states).length) {
7378
this.updateMediaPlayers();
7479
}
@@ -93,6 +98,7 @@ class RadioBrowserCard extends HTMLElement {
9398
saveFavorites() {
9499
try {
95100
localStorage.setItem('radio_favorites', JSON.stringify(this._favorites));
101+
this._autoBackupToHA();
96102
} catch (e) {
97103
console.error('Error saving favorites:', e);
98104
}
@@ -111,11 +117,66 @@ class RadioBrowserCard extends HTMLElement {
111117
saveCustomStations() {
112118
try {
113119
localStorage.setItem('radio_custom_stations', JSON.stringify(this._customStations));
120+
this._autoBackupToHA();
114121
} catch (e) {
115122
console.error('Error saving custom stations:', e);
116123
}
117124
}
118125

126+
// Auto-backup to Home Assistant server (survives cache clear)
127+
async _autoBackupToHA() {
128+
if (!this._hass) return;
129+
try {
130+
await this._hass.callWS({
131+
type: 'frontend/set_user_data',
132+
key: 'radio_browser_backup',
133+
value: {
134+
favorites: this._favorites,
135+
custom_stations: this._customStations,
136+
timestamp: Date.now()
137+
}
138+
});
139+
console.log('Auto-backup to HA server OK');
140+
} catch (e) {
141+
console.log('Auto-backup to HA server failed:', e);
142+
}
143+
}
144+
145+
// Auto-restore from Home Assistant server (after cache clear)
146+
async _autoRestoreFromHA() {
147+
if (!this._hass) return;
148+
try {
149+
const result = await this._hass.callWS({
150+
type: 'frontend/get_user_data',
151+
key: 'radio_browser_backup'
152+
});
153+
if (!result || !result.value) return;
154+
155+
const data = result.value;
156+
const localFavs = localStorage.getItem('radio_favorites');
157+
const localCustom = localStorage.getItem('radio_custom_stations');
158+
const hasLocalData = (localFavs && JSON.parse(localFavs).length > 0) ||
159+
(localCustom && JSON.parse(localCustom).length > 0);
160+
161+
// Only restore if localStorage is empty (cache was cleared) and server has data
162+
if (!hasLocalData && (data.favorites?.length > 0 || data.custom_stations?.length > 0)) {
163+
console.log('Cache cleared detected - restoring from HA server backup...');
164+
if (data.favorites?.length > 0) {
165+
this._favorites = data.favorites;
166+
localStorage.setItem('radio_favorites', JSON.stringify(data.favorites));
167+
}
168+
if (data.custom_stations?.length > 0) {
169+
this._customStations = data.custom_stations;
170+
localStorage.setItem('radio_custom_stations', JSON.stringify(data.custom_stations));
171+
}
172+
console.log(`Restored: ${data.favorites?.length || 0} favorites, ${data.custom_stations?.length || 0} custom stations`);
173+
this.updatePlaylist();
174+
}
175+
} catch (e) {
176+
console.log('Auto-restore from HA server not available:', e);
177+
}
178+
}
179+
119180
// YouTube URL handling
120181
parseYouTubeUrl(url) {
121182
// Support various YouTube URL formats
@@ -306,19 +367,20 @@ class RadioBrowserCard extends HTMLElement {
306367
return this._favorites.some(fav => fav.media_content_id === station.media_content_id);
307368
}
308369

309-
// Export/Import favorites
370+
// Export/Import favorites + custom stations
310371
exportFavorites() {
311372
const data = {
312-
version: '1.0',
373+
version: '2.0',
313374
exported: new Date().toISOString(),
314-
favorites: this._favorites
375+
favorites: this._favorites,
376+
custom_stations: this._customStations
315377
};
316378
const json = JSON.stringify(data, null, 2);
317379
const blob = new Blob([json], { type: 'application/json' });
318380
const url = URL.createObjectURL(blob);
319381
const a = document.createElement('a');
320382
a.href = url;
321-
a.download = `radio-favorites-${new Date().toISOString().split('T')[0]}.json`;
383+
a.download = `radio-backup-${new Date().toISOString().split('T')[0]}.json`;
322384
a.click();
323385
URL.revokeObjectURL(url);
324386
}
@@ -335,16 +397,25 @@ class RadioBrowserCard extends HTMLElement {
335397
reader.onload = (event) => {
336398
try {
337399
const data = JSON.parse(event.target.result);
400+
const parts = [];
338401
if (data.favorites && Array.isArray(data.favorites)) {
339402
this._favorites = data.favorites;
340403
this.saveFavorites();
404+
parts.push(`${data.favorites.length} favorites`);
405+
}
406+
if (data.custom_stations && Array.isArray(data.custom_stations)) {
407+
this._customStations = data.custom_stations;
408+
this.saveCustomStations();
409+
parts.push(`${data.custom_stations.length} custom stations`);
410+
}
411+
if (parts.length > 0) {
341412
this.updatePlaylist();
342-
alert(`Imported ${data.favorites.length} favorite stations!`);
413+
alert(`Imported: ${parts.join(', ')}!`);
343414
} else {
344415
alert('Invalid file format');
345416
}
346417
} catch (error) {
347-
alert('Error importing favorites: ' + error.message);
418+
alert('Error importing: ' + error.message);
348419
}
349420
};
350421
reader.readAsText(file);
@@ -2239,10 +2310,10 @@ class RadioBrowserCard extends HTMLElement {
22392310
</div>
22402311
</div>
22412312
<div class="settings-group">
2242-
<div class="settings-label">Favorites</div>
2313+
<div class="settings-label">Backup & Restore</div>
22432314
<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>
2315+
<button class="settings-option" onclick="this.getRootNode().host.exportFavorites()">📤 Backup</button>
2316+
<button class="settings-option" onclick="this.getRootNode().host.importFavorites()">📥 Restore</button>
22462317
</div>
22472318
</div>
22482319
</div>
@@ -2590,6 +2661,11 @@ class RadioBrowserCardCompact extends HTMLElement {
25902661
const oldHass = this._hass;
25912662
this._hass = hass;
25922663

2664+
// On first hass connection, auto-restore from HA server if cache was cleared
2665+
if (!oldHass && hass) {
2666+
this._autoRestoreFromHA();
2667+
}
2668+
25932669
if (!oldHass || Object.keys(oldHass.states).length !== Object.keys(hass.states).length) {
25942670
this._updateMediaPlayers();
25952671
}
@@ -2628,6 +2704,37 @@ class RadioBrowserCardCompact extends HTMLElement {
26282704
return all;
26292705
}
26302706

2707+
// Auto-restore from Home Assistant server (after cache clear)
2708+
async _autoRestoreFromHA() {
2709+
if (!this._hass) return;
2710+
try {
2711+
const result = await this._hass.callWS({
2712+
type: 'frontend/get_user_data',
2713+
key: 'radio_browser_backup'
2714+
});
2715+
if (!result || !result.value) return;
2716+
2717+
const data = result.value;
2718+
const localFavs = localStorage.getItem('radio_favorites');
2719+
const localCustom = localStorage.getItem('radio_custom_stations');
2720+
const hasLocalData = (localFavs && JSON.parse(localFavs).length > 0) ||
2721+
(localCustom && JSON.parse(localCustom).length > 0);
2722+
2723+
if (!hasLocalData && (data.favorites?.length > 0 || data.custom_stations?.length > 0)) {
2724+
console.log('Compact card: Restoring from HA server backup...');
2725+
if (data.favorites?.length > 0) {
2726+
localStorage.setItem('radio_favorites', JSON.stringify(data.favorites));
2727+
}
2728+
if (data.custom_stations?.length > 0) {
2729+
localStorage.setItem('radio_custom_stations', JSON.stringify(data.custom_stations));
2730+
}
2731+
this._updateStationSelect();
2732+
}
2733+
} catch (e) {
2734+
console.log('Compact card: Auto-restore not available:', e);
2735+
}
2736+
}
2737+
26312738
// --- State persistence ---
26322739
_saveState() {
26332740
try {
@@ -3004,6 +3111,61 @@ class RadioBrowserCardCompact extends HTMLElement {
30043111
return div.innerHTML;
30053112
}
30063113

3114+
// --- Export/Import (shared format with main card) ---
3115+
_exportFavorites() {
3116+
const favorites = this._loadFavorites();
3117+
const customStations = this._loadCustomStations();
3118+
const data = {
3119+
version: '2.0',
3120+
exported: new Date().toISOString(),
3121+
favorites: favorites,
3122+
custom_stations: customStations
3123+
};
3124+
const json = JSON.stringify(data, null, 2);
3125+
const blob = new Blob([json], { type: 'application/json' });
3126+
const url = URL.createObjectURL(blob);
3127+
const a = document.createElement('a');
3128+
a.href = url;
3129+
a.download = `radio-backup-${new Date().toISOString().split('T')[0]}.json`;
3130+
a.click();
3131+
URL.revokeObjectURL(url);
3132+
}
3133+
3134+
_importFavorites() {
3135+
const input = document.createElement('input');
3136+
input.type = 'file';
3137+
input.accept = 'application/json';
3138+
input.onchange = (e) => {
3139+
const file = e.target.files[0];
3140+
if (!file) return;
3141+
const reader = new FileReader();
3142+
reader.onload = (event) => {
3143+
try {
3144+
const data = JSON.parse(event.target.result);
3145+
const parts = [];
3146+
if (data.favorites && Array.isArray(data.favorites)) {
3147+
localStorage.setItem('radio_favorites', JSON.stringify(data.favorites));
3148+
parts.push(`${data.favorites.length} favorites`);
3149+
}
3150+
if (data.custom_stations && Array.isArray(data.custom_stations)) {
3151+
localStorage.setItem('radio_custom_stations', JSON.stringify(data.custom_stations));
3152+
parts.push(`${data.custom_stations.length} custom stations`);
3153+
}
3154+
if (parts.length > 0) {
3155+
this._updateStationSelect();
3156+
alert(`Imported: ${parts.join(', ')}!`);
3157+
} else {
3158+
alert('Invalid file format');
3159+
}
3160+
} catch (error) {
3161+
alert('Error importing: ' + error.message);
3162+
}
3163+
};
3164+
reader.readAsText(file);
3165+
};
3166+
input.click();
3167+
}
3168+
30073169
// --- Render ---
30083170
render() {
30093171
const c = this._getThemeColors();
@@ -3213,6 +3375,34 @@ class RadioBrowserCardCompact extends HTMLElement {
32133375
color: ${c.primary};
32143376
text-decoration: none;
32153377
}
3378+
3379+
/* Backup row */
3380+
.compact-backup-row {
3381+
display: flex;
3382+
gap: 4px;
3383+
margin-top: 8px;
3384+
}
3385+
.compact-backup-btn {
3386+
flex: 1;
3387+
height: 26px;
3388+
border: 1px solid ${c.surfaceLighter};
3389+
border-radius: 6px;
3390+
background: ${c.surfaceLight};
3391+
color: ${c.textSecondary};
3392+
font-size: 10px;
3393+
font-family: inherit;
3394+
cursor: pointer;
3395+
display: flex;
3396+
align-items: center;
3397+
justify-content: center;
3398+
gap: 4px;
3399+
transition: all 0.15s;
3400+
}
3401+
.compact-backup-btn:hover {
3402+
background: ${c.surfaceLighter};
3403+
color: ${c.text};
3404+
border-color: ${c.primary};
3405+
}
32163406
</style>
32173407
32183408
<div class="compact-card">
@@ -3244,6 +3434,12 @@ class RadioBrowserCardCompact extends HTMLElement {
32443434
<input type="range" class="compact-volume" min="0" max="100" value="${this._volume}" style="--vol: ${this._volume}%;">
32453435
</div>
32463436
3437+
<!-- Backup buttons -->
3438+
<div class="compact-backup-row">
3439+
<button class="compact-backup-btn compact-export-btn" title="Export favorites & stations to JSON file">📤 Backup</button>
3440+
<button class="compact-backup-btn compact-import-btn" title="Import favorites & stations from JSON file">📥 Restore</button>
3441+
</div>
3442+
32473443
<audio class="compact-audio" style="display:none;"></audio>
32483444
</div>
32493445
`;
@@ -3296,6 +3492,16 @@ class RadioBrowserCardCompact extends HTMLElement {
32963492
if (muteBtn) {
32973493
muteBtn.addEventListener('click', () => this._toggleMute());
32983494
}
3495+
3496+
// Backup buttons
3497+
const exportBtn = root.querySelector('.compact-export-btn');
3498+
if (exportBtn) {
3499+
exportBtn.addEventListener('click', () => this._exportFavorites());
3500+
}
3501+
const importBtn = root.querySelector('.compact-import-btn');
3502+
if (importBtn) {
3503+
importBtn.addEventListener('click', () => this._importFavorites());
3504+
}
32993505
}
33003506

33013507
disconnectedCallback() {

0 commit comments

Comments
 (0)