Results
-
+
- {}}
- >
- Raw JSON
-
- {}}> Raw JSON
+ {
try {
@@ -181,7 +176,7 @@
Copy to Clipboard
-
+
{JSON.stringify(searchResults, null, 2)}
@@ -206,7 +201,7 @@
justify-content: center;
backdrop-filter: blur(4px);
}
-
+
.modal-container {
background: rgba(20, 20, 20, 0.98);
border-radius: $unit * 1.5;
@@ -218,21 +213,21 @@
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.8);
border: 1px solid rgba(255, 255, 255, 0.1);
}
-
+
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: $unit * 2;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
-
+
h2 {
margin: 0;
color: white;
font-size: 18px;
font-weight: 600;
}
-
+
.close-btn {
background: none;
border: none;
@@ -241,34 +236,34 @@
padding: $unit-half;
border-radius: 4px;
transition: all 0.2s;
-
+
:global(svg) {
width: 20px;
height: 20px;
}
-
+
&:hover {
color: white;
background: rgba(255, 255, 255, 0.1);
}
}
}
-
+
.modal-body {
flex: 1;
overflow-y: auto;
padding: $unit * 2;
}
-
+
.search-controls {
display: flex;
gap: $unit * 2;
margin-bottom: $unit * 2;
align-items: flex-end;
-
+
.control-group {
flex: 1;
-
+
label {
display: block;
color: rgba(255, 255, 255, 0.8);
@@ -276,8 +271,9 @@
font-weight: 500;
margin-bottom: $unit-half;
}
-
- input, select {
+
+ input,
+ select {
width: 100%;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
@@ -286,24 +282,24 @@
border-radius: 4px;
font-size: 14px;
font-family: inherit;
-
+
&::placeholder {
color: rgba(255, 255, 255, 0.4);
}
-
+
&:focus {
outline: none;
border-color: $primary-color;
background: rgba(255, 255, 255, 0.15);
}
-
+
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
}
-
+
.search-btn {
padding: $unit $unit * 2;
background: $primary-color;
@@ -318,23 +314,23 @@
align-items: center;
gap: $unit-half;
white-space: nowrap;
-
+
&:hover:not(:disabled) {
background: darken($primary-color, 10%);
}
-
+
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
-
+
:global(.icon) {
width: 16px;
height: 16px;
}
}
}
-
+
.error-message {
background: rgba(255, 59, 48, 0.1);
border: 1px solid rgba(255, 59, 48, 0.3);
@@ -344,16 +340,16 @@
font-size: 13px;
margin-bottom: $unit * 2;
}
-
+
.response-time {
color: rgba(255, 255, 255, 0.6);
font-size: 12px;
margin-bottom: $unit;
}
-
+
.results-section {
margin-top: $unit * 2;
-
+
h3 {
margin: 0 0 $unit 0;
color: #87ceeb;
@@ -361,14 +357,14 @@
font-weight: 600;
}
}
-
+
.result-tabs {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: $unit * 2;
-
+
.tab {
padding: $unit $unit * 2;
background: none;
@@ -379,17 +375,17 @@
font-weight: 500;
transition: all 0.2s;
border-bottom: 2px solid transparent;
-
+
&:hover {
color: rgba(255, 255, 255, 0.8);
}
-
+
&.active {
color: white;
border-bottom-color: $primary-color;
}
}
-
+
.copy-btn {
padding: $unit-half $unit;
background: rgba(255, 255, 255, 0.1);
@@ -400,7 +396,7 @@
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
-
+
&:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.3);
@@ -408,14 +404,14 @@
}
}
}
-
+
.results-content {
background: rgba(0, 0, 0, 0.5);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 4px;
max-height: 400px;
overflow-y: auto;
-
+
pre {
margin: 0;
padding: $unit * 1.5;
@@ -425,11 +421,11 @@
font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
}
}
-
+
:global(.spinning) {
animation: spin 1s linear infinite;
}
-
+
@keyframes spin {
from {
transform: rotate(0deg);
@@ -438,4 +434,4 @@
transform: rotate(360deg);
}
}
-
\ No newline at end of file
+
diff --git a/src/lib/components/Avatar.svelte b/src/lib/components/Avatar.svelte
index d5a982d..a69a0ed 100644
--- a/src/lib/components/Avatar.svelte
+++ b/src/lib/components/Avatar.svelte
@@ -12,7 +12,6 @@
let isBlinking = $state(false)
let isPlayingMusic = $state(forcePlayingMusic)
-
const scale = new Spring(1, {
stiffness: 0.1,
damping: 0.125
diff --git a/src/lib/components/DebugPanel.svelte b/src/lib/components/DebugPanel.svelte
index 9543a6b..6c82c33 100644
--- a/src/lib/components/DebugPanel.svelte
+++ b/src/lib/components/DebugPanel.svelte
@@ -3,7 +3,7 @@
import { musicStream } from '$lib/stores/music-stream'
import type { Album } from '$lib/types/lastfm'
import { toast } from 'svelte-sonner'
-
+
// Import SVG icons
import SettingsIcon from '$icons/settings.svg'
import CheckIcon from '$icons/check.svg'
@@ -20,27 +20,27 @@
let isMinimized = $state(true)
let activeTab = $state<'nowplaying' | 'albums' | 'cache'>('nowplaying')
-
+
// Music stream state
let albums = $state
([])
let nowPlaying = $state<{ album: Album; track?: string } | null>(null)
let connected = $state(false)
let lastUpdate = $state(null)
let updateFlash = $state(false)
-
+
// Expanded album view
let expandedAlbumId = $state(null)
-
+
// Update timing
let nextUpdateIn = $state(0)
let updateInterval = $state(30) // seconds
let trackRemainingTime = $state(0) // seconds
-
+
// Cache management
let cacheKey = $state('')
let isClearing = $state(false)
let clearingAlbums = $state(new Set())
-
+
// Search modal reference
let searchModal: AppleMusicSearchModal
@@ -49,27 +49,34 @@
const unsubscribe = musicStream.subscribe((state) => {
albums = state.albums
connected = state.connected
-
+
// Flash indicator when update is received
- if (state.lastUpdate && (!lastUpdate || state.lastUpdate.getTime() !== lastUpdate.getTime())) {
+ if (
+ state.lastUpdate &&
+ (!lastUpdate || state.lastUpdate.getTime() !== lastUpdate.getTime())
+ ) {
updateFlash = true
- setTimeout(() => updateFlash = false, 500)
+ setTimeout(() => (updateFlash = false), 500)
}
-
+
lastUpdate = state.lastUpdate
-
+
// Calculate smart interval based on track remaining time
- const nowPlayingAlbum = state.albums.find(a => a.isNowPlaying)
- if (nowPlayingAlbum?.nowPlayingTrack && nowPlayingAlbum.appleMusicData?.tracks && nowPlayingAlbum.lastScrobbleTime) {
+ const nowPlayingAlbum = state.albums.find((a) => a.isNowPlaying)
+ if (
+ nowPlayingAlbum?.nowPlayingTrack &&
+ nowPlayingAlbum.appleMusicData?.tracks &&
+ nowPlayingAlbum.lastScrobbleTime
+ ) {
const track = nowPlayingAlbum.appleMusicData.tracks.find(
- t => t.name === nowPlayingAlbum.nowPlayingTrack
+ (t) => t.name === nowPlayingAlbum.nowPlayingTrack
)
-
+
if (track?.durationMs) {
const elapsed = Date.now() - new Date(nowPlayingAlbum.lastScrobbleTime).getTime()
const remaining = Math.max(0, track.durationMs - elapsed)
trackRemainingTime = Math.round(remaining / 1000)
-
+
// Smart interval based on remaining time
if (remaining < 20000) {
updateInterval = 5
@@ -98,29 +105,29 @@
})
return unsubscribe
})
-
+
// Calculate next update countdown
$effect(() => {
if (!lastUpdate) {
nextUpdateIn = updateInterval
return
}
-
+
// Calculate initial remaining time
const calculateRemaining = () => {
const elapsed = Date.now() - lastUpdate.getTime()
- const remaining = (updateInterval * 1000) - elapsed
+ const remaining = updateInterval * 1000 - elapsed
return Math.max(0, Math.ceil(remaining / 1000))
}
-
+
// Set initial value
nextUpdateIn = calculateRemaining()
-
+
// Update every second
const timer = setInterval(() => {
nextUpdateIn = calculateRemaining()
}, 1000)
-
+
return () => clearInterval(timer)
})
@@ -129,7 +136,7 @@
toast.error('Please enter a cache key')
return
}
-
+
isClearing = true
try {
const response = await fetch('/api/admin/debug/clear-cache', {
@@ -137,7 +144,7 @@
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: cacheKey })
})
-
+
if (response.ok) {
const result = await response.json()
toast.success(`Cleared cache: ${result.deleted} keys deleted`)
@@ -152,7 +159,7 @@
isClearing = false
}
}
-
+
async function clearAllMusicCache() {
isClearing = true
try {
@@ -161,7 +168,7 @@
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pattern: 'apple:album:*' })
})
-
+
if (response.ok) {
const result = await response.json()
toast.success(`Cleared all music cache: ${result.deleted} keys deleted`)
@@ -175,11 +182,11 @@
isClearing = false
}
}
-
+
async function clearAlbumCache(album: Album) {
const albumKey = `apple:album:${album.artist.name}:${album.name}`
const albumId = `${album.artist.name}:${album.name}`
-
+
clearingAlbums.add(albumId)
try {
const response = await fetch('/api/admin/debug/clear-cache', {
@@ -187,7 +194,7 @@
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: albumKey })
})
-
+
if (response.ok) {
const result = await response.json()
toast.success(`Cleared cache for "${album.name}"`)
@@ -213,56 +220,64 @@
{#if dev}
-