uploaded stuff
This commit is contained in:
commit
9ce49bcf1c
2 changed files with 181 additions and 0 deletions
102
LastFM.svelte
Normal file
102
LastFM.svelte
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
<!-- Planned to make it a vinyl but got lazy and made it a CD-->
|
||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { track, loading, error, fetchTrack } from '../stores/trackStore.js';
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if ($track) {
|
||||||
|
console.log('Loaded cached song');
|
||||||
|
} else {
|
||||||
|
fetchTrack();
|
||||||
|
}
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
if (!$track) {
|
||||||
|
fetchTrack();
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $loading}
|
||||||
|
<p>Loading latest track...</p>
|
||||||
|
{:else if $error}
|
||||||
|
<p style="color:red">Error: {$error}</p>
|
||||||
|
{:else if $track}
|
||||||
|
<div class="track-container">
|
||||||
|
<a href={$track.url} target="_blank" rel="noopener noreferrer" class="track-link">
|
||||||
|
<div class="vinyl">
|
||||||
|
<img src={$track.image} alt="Album cover" />
|
||||||
|
</div>
|
||||||
|
<div class="track-info">
|
||||||
|
<div class="track-name">{$track.name}</div>
|
||||||
|
<div class="track-artist">{$track.artist} — {$track.album}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p>No track info available.</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.track-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.vinyl {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 2px solid black;
|
||||||
|
box-shadow: 0 0 5px rgba(0,0,0,0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
animation: spin 5s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vinyl::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: black;
|
||||||
|
border-radius: 50%;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.track-info {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-artist {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
</style>
|
79
trackStore.js
Normal file
79
trackStore.js
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export const track = writable(null);
|
||||||
|
export const loading = writable(false);
|
||||||
|
export const error = writable(null);
|
||||||
|
|
||||||
|
const CACHE_EXPIRY_MS = 60 * 1000;
|
||||||
|
|
||||||
|
function getCachedTrack() {
|
||||||
|
if (typeof window === 'undefined') return null;
|
||||||
|
|
||||||
|
const cached = localStorage.getItem('cachedTrack');
|
||||||
|
if (!cached) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(cached);
|
||||||
|
if (!parsed.timestamp || !parsed.data) return null;
|
||||||
|
|
||||||
|
const age = Date.now() - parsed.timestamp;
|
||||||
|
if (age < CACHE_EXPIRY_MS) {
|
||||||
|
return parsed.data;
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('cachedTrack');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
localStorage.removeItem('cachedTrack');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const cachedTrack = getCachedTrack();
|
||||||
|
if (cachedTrack) {
|
||||||
|
track.set(cachedTrack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchTrack() {
|
||||||
|
if (typeof window !== 'undefined' && getCachedTrack()) {
|
||||||
|
console.log('%c CACHED - skip fetching', 'font-weight: bold; color: orange;');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.set(true);
|
||||||
|
error.set(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('https://lastfm-last-played.biancarosa.com.br/expect69420/latest-song');
|
||||||
|
if (!res.ok) throw new Error(`Fetch failed: ${res.status}`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
const normalizedTrack = {
|
||||||
|
name: data.track.name,
|
||||||
|
artist: data.track.artist['#text'],
|
||||||
|
album: data.track.album['#text'],
|
||||||
|
url: data.track.url,
|
||||||
|
nowPlaying: data.track['@attr']?.nowplaying === 'true',
|
||||||
|
image: data.track.image.find(img => img.size === 'medium')?.['#text'] || ''
|
||||||
|
};
|
||||||
|
|
||||||
|
track.set(normalizedTrack);
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem('cachedTrack', JSON.stringify({
|
||||||
|
timestamp: Date.now(),
|
||||||
|
data: normalizedTrack
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
error.set(e.message);
|
||||||
|
track.set(null);
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.removeItem('cachedTrack');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading.set(false);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue