All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m20s
150 lines
4.6 KiB
TypeScript
150 lines
4.6 KiB
TypeScript
import { treaty } from '@elysiajs/eden';
|
|
import type { App } from './server';
|
|
|
|
// Create Eden Treaty client using current page origin
|
|
const client = treaty<App>(window.location.origin);
|
|
|
|
interface LastSongImage {
|
|
size: 'small' | 'medium' | 'large' | 'extralarge';
|
|
'#text': string;
|
|
}
|
|
|
|
interface LastSong {
|
|
name: string;
|
|
artist: string;
|
|
album: string;
|
|
url: string;
|
|
nowPlaying: boolean;
|
|
image: LastSongImage[];
|
|
}
|
|
|
|
// Function to fetch and display last song
|
|
async function fetchLastSong(): Promise<void> {
|
|
try {
|
|
const { data, error } = await client.api['last-song'].get();
|
|
|
|
if (error) {
|
|
console.error('Error fetching last song:', error);
|
|
updateLastSongDisplay(null, 'Failed to load song data');
|
|
return;
|
|
}
|
|
|
|
const song = data as LastSong;
|
|
updateLastSongDisplay(song);
|
|
} catch (error) {
|
|
console.error('Network error:', error);
|
|
updateLastSongDisplay(null, 'Network error occurred');
|
|
}
|
|
}
|
|
|
|
// Function to select appropriate image size based on screen width
|
|
function getOptimalImageUrl(images: LastSongImage[]): string {
|
|
if (!images || images.length === 0) return '';
|
|
|
|
const screenWidth = window.innerWidth;
|
|
|
|
// Select image size based on screen width and device pixel ratio
|
|
const pixelRatio = window.devicePixelRatio || 1;
|
|
const effectiveWidth = screenWidth * pixelRatio;
|
|
|
|
let targetSize: string;
|
|
|
|
if (effectiveWidth <= 400) {
|
|
targetSize = 'small'; // 34px
|
|
} else if (effectiveWidth <= 800) {
|
|
targetSize = 'medium'; // 64px
|
|
} else if (effectiveWidth <= 1400) {
|
|
targetSize = 'large'; // 174px
|
|
} else {
|
|
targetSize = 'extralarge'; // 300px
|
|
}
|
|
|
|
// Find the target size, fallback to largest available
|
|
const targetImage = images.find(img => img.size === targetSize);
|
|
if (targetImage) return targetImage['#text'];
|
|
|
|
// Fallback: return the largest available image
|
|
const fallbackOrder = ['extralarge', 'large', 'medium', 'small'];
|
|
for (const size of fallbackOrder) {
|
|
const image = images.find(img => img.size === size);
|
|
if (image) return image['#text'];
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
// Function to update the UI with song information
|
|
function updateLastSongDisplay(song: LastSong | null, errorMessage?: string): void {
|
|
const containers = [
|
|
document.getElementById('last-song-container'),
|
|
document.getElementById('last-song-container-mobile')
|
|
];
|
|
|
|
containers.forEach(container => {
|
|
if (!container) return;
|
|
|
|
if (errorMessage || !song) {
|
|
container.innerHTML = `
|
|
<div class="text-red-400 text-sm">
|
|
${errorMessage || 'No song data available'}
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
const statusText = song.nowPlaying ? 'Currently listening to' : 'Last listened to';
|
|
const imageUrl = getOptimalImageUrl(song.image);
|
|
|
|
const imageElement = imageUrl
|
|
? `<img src="${imageUrl}" alt="${song.name}" class="w-24 h-24 rounded-lg object-cover mx-auto border border-white/20" loading="lazy">`
|
|
: `<div class="w-16 h-16 rounded-lg bg-gradient-to-br from-cyan-500/20 to-purple-500/20 flex items-center justify-center mx-auto border border-white/20">
|
|
<svg class="w-8 h-8 text-cyan-300" fill="currentColor" viewBox="0 0 20 20">
|
|
<path fill-rule="evenodd" d="M18 3a1 1 0 00-1.447-.894L8.763 6H5a3 3 0 000 6h.28l1.771 5.316A1 1 0 008 18a1 1 0 001-1v-4.382l6.553 3.276A1 1 0 0017 15V3z" clip-rule="evenodd" />
|
|
</svg>
|
|
</div>`;
|
|
|
|
container.innerHTML = `
|
|
<div class="flex flex-col items-center space-y-3">
|
|
${imageElement}
|
|
<div class="text-center w-full">
|
|
<p class="text-sm text-cyan-300 font-medium">${statusText}</p>
|
|
<a href="${song.url}" target="_blank" class="block hover:text-cyan-200 transition-colors duration-200">
|
|
<p class="font-semibold text-white">${song.name}</p>
|
|
<p class="text-sm text-gray-300">by ${song.artist}</p>
|
|
${song.album ? `<p class="text-xs text-white-400">from ${song.album}</p>` : ''}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
// Auto-refresh functionality
|
|
let refreshInterval: number;
|
|
|
|
function startAutoRefresh(): void {
|
|
// Fetch immediately
|
|
fetchLastSong();
|
|
|
|
// Then fetch every 30 seconds
|
|
refreshInterval = window.setInterval(fetchLastSong, 30000);
|
|
}
|
|
|
|
function stopAutoRefresh(): void {
|
|
if (refreshInterval) {
|
|
clearInterval(refreshInterval);
|
|
}
|
|
}
|
|
|
|
// Initialize when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
startAutoRefresh();
|
|
});
|
|
|
|
// Clean up on page unload
|
|
window.addEventListener('beforeunload', () => {
|
|
stopAutoRefresh();
|
|
});
|
|
|
|
// Export functions for manual control if needed
|
|
export { fetchLastSong, startAutoRefresh, stopAutoRefresh };
|