cmziuk/src/client.ts
Cameron Redmore be5a61185b
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m20s
Fixes to strange bars on mobile, and addition of server which currently powers a new Last.FM-based music tracker.
2025-08-08 09:53:34 +01:00

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 };