Add Docker support and implement Bun for dependency management
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 1m25s

- Introduced .dockerignore to exclude unnecessary files from Docker context.
- Updated Dockerfile to use Bun for building and running the application.
- Removed entrypoint.sh as it is no longer needed.
- Modified build-and-dockerise.yml to set up Bun and install dependencies.
- Enhanced index.html with improved structure and hover effects.
- Updated client.ts to include tooltips for music display.
- Implemented caching in server.ts for Last.fm data.
- Added custom styles for hover effects and tooltips in style.css.
This commit is contained in:
Cameron Redmore 2025-08-08 11:18:39 +01:00
parent be5a61185b
commit 214982649f
No known key found for this signature in database
9 changed files with 272 additions and 107 deletions

View file

@ -92,9 +92,16 @@ function updateLastSongDisplay(song: LastSong | null, errorMessage?: string): vo
return;
}
const statusText = song.nowPlaying ? 'Currently listening to' : 'Last listened to';
const statusText = song.nowPlaying ? 'Currently Listening To' : 'Last Played';
const imageUrl = getOptimalImageUrl(song.image);
// Create tooltip content for music
const tooltipContent = `
<div class="song-name">${song.name}</div>
<div class="artist-name">by ${song.artist}</div>
${song.album ? `<div class="album-name">from "${song.album}"</div>` : ''}
`;
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">
@ -105,14 +112,17 @@ function updateLastSongDisplay(song: LastSong | null, errorMessage?: string): vo
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>` : ''}
<div class="tooltip music-tooltip">
<a href="${song.url}" target="_blank" class="block hover:text-cyan-200 transition-colors duration-200 w-36">
${imageElement}
<div class="text-center w-full">
<p class="text-sm text-cyan-300 font-medium w-36">${statusText}</p>
<div class="font-semibold text-white-900 text-ellipsis overflow-hidden whitespace-nowrap max-w-full">${song.name}</div>
<div class="text-sm text-white-200 text-ellipsis overflow-hidden whitespace-nowrap max-w-full">${song.artist}</div>
${song.album ? `<div class="text-xs text-white-600 text-ellipsis overflow-hidden whitespace-nowrap max-w-full">${song.album}</div>` : ''}
</div>
</a>
<span class="tooltip-text">${tooltipContent}</span>
</div>
</div>
`;

View file

@ -1,6 +1,35 @@
import { Elysia, t } from 'elysia';
import { staticPlugin } from '@elysiajs/static';
// Simple cache implementation
interface CacheEntry {
data: any;
timestamp: number;
}
const cache = new Map<string, CacheEntry>();
const CACHE_TTL = 30 * 1000; // 30 seconds in milliseconds
function getCachedData(key: string): any | null {
const entry = cache.get(key);
if (!entry) return null;
const now = Date.now();
if (now - entry.timestamp > CACHE_TTL) {
cache.delete(key);
return null;
}
return entry.data;
}
function setCachedData(key: string, data: any): void {
cache.set(key, {
data,
timestamp: Date.now()
});
}
const app = new Elysia()
.use(staticPlugin({
assets: 'dist',
@ -17,13 +46,22 @@ const app = new Elysia()
);
}
// Check cache first
const cacheKey = `lastfm:${username}`;
const cachedData = getCachedData(cacheKey);
if (cachedData) {
console.log('Returning cached Last.fm data');
return cachedData;
}
const url = `https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${username}&api_key=${apiKey}&format=json&limit=1`;
try {
const response = await fetch(url);
const data = await response.json();
const track = data.recenttracks.track[0];
return {
const result = {
name: track.name,
artist: track.artist['#text'],
album: track.album['#text'],
@ -31,6 +69,12 @@ const app = new Elysia()
nowPlaying: track['@attr']?.nowplaying === 'true',
image: track.image || [],
};
// Cache the result
setCachedData(cacheKey, result);
console.log('Fetched and cached new Last.fm data');
return result;
} catch (error) {
console.error('Error fetching from Last.fm:', error);
return new Response(JSON.stringify({ error: 'Failed to fetch last song' }), {
@ -39,7 +83,10 @@ const app = new Elysia()
});
}
})
.listen(3000);
.listen({
port: process.env.PORT || 3000,
hostname: process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost'
});
console.log(
`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`

View file

@ -78,14 +78,16 @@ body {
}
}
/* Custom styling for skill tags and buttons */
.skill-tag {
/* Custom styling for hoverable elements */
.hover-glass {
background: rgba(255, 255, 255, 0.15);
transition: background 0.3s ease, transform 0.3s ease;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0);
transition: background 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease, color 0.3s ease;
}
.skill-tag:hover {
.hover-glass:hover {
background: rgba(255, 255, 255, 0.3);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
transform: translateY(-2px);
}
@ -109,4 +111,67 @@ body {
100% {
background-position: 0% 50%;
}
}
/* Tooltip styles */
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltip-text {
visibility: hidden;
opacity: 0;
width: max-content;
max-width: 200px;
background: rgba(43, 85, 131, 0.9);
color: #fff;
text-align: center;
border-radius: 8px;
padding: 8px 12px;
position: absolute;
z-index: 1000;
bottom: -25%;
left: 50%;
transform: translateX(-50%);
font-size: 14px;
font-weight: 500;
white-space: nowrap;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s ease;
}
.tooltip:hover .tooltip-text {
visibility: visible;
opacity: 1;
transform: translateX(-50%) translateY(-5px);
}
/* Music tooltip specific styling */
.music-tooltip .tooltip-text {
white-space: normal;
text-align: left;
max-width: 250px;
line-height: 1.4;
}
.music-tooltip .tooltip-text .song-name {
font-weight: 600;
color: #4dd0e1;
margin-bottom: 2px;
}
.music-tooltip .tooltip-text .artist-name {
font-weight: 500;
color: #e2e8f0;
margin-bottom: 1px;
}
.music-tooltip .tooltip-text .album-name {
font-weight: 400;
color: #cbd5e0;
font-size: 12px;
}