From 59370367bd5508bf9bab5c7fee3619a0630b0c26 Mon Sep 17 00:00:00 2001 From: "cool.gitter.not.me.again.duh" Date: Mon, 26 May 2025 20:44:25 -0600 Subject: [PATCH] polishing the edges --- .dockerignore | 21 +-- .gitignore | 3 +- Dockerfile | 10 ++ docker-compose.yaml | 2 +- {static => src}/js/album.ts | 0 {static => src}/js/artist.ts | 0 {static => src}/js/config.ts | 23 +++- {static => src}/js/main.ts | 227 +++++++++++++++++++++++++-------- {static => src}/js/playlist.ts | 97 ++++++++++++-- {static => src}/js/queue.ts | 12 +- {static => src}/js/track.ts | 0 tsconfig.json | 16 ++- 12 files changed, 313 insertions(+), 98 deletions(-) rename {static => src}/js/album.ts (100%) rename {static => src}/js/artist.ts (100%) rename {static => src}/js/config.ts (98%) rename {static => src}/js/main.ts (63%) rename {static => src}/js/playlist.ts (84%) rename {static => src}/js/queue.ts (99%) rename {static => src}/js/track.ts (100%) diff --git a/.dockerignore b/.dockerignore index c69773a..6c1fe0a 100755 --- a/.dockerignore +++ b/.dockerignore @@ -6,24 +6,10 @@ /Test.py /prgs/ /flask_server.log -/routes/__pycache__/ -routes/utils/__pycache__/ test.sh __pycache__/ -routes/__pycache__/__init__.cpython-312.pyc -routes/__pycache__/credentials.cpython-312.pyc -routes/__pycache__/search.cpython-312.pyc -routes/utils/__pycache__/__init__.cpython-312.pyc -routes/utils/__pycache__/credentials.cpython-312.pyc -routes/utils/__pycache__/search.cpython-312.pyc -routes/utils/__pycache__/__init__.cpython-312.pyc -routes/utils/__pycache__/credentials.cpython-312.pyc -routes/utils/__pycache__/search.cpython-312.pyc -routes/utils/__pycache__/credentials.cpython-312.pyc -routes/utils/__pycache__/search.cpython-312.pyc -routes/utils/__pycache__/__init__.cpython-312.pyc -routes/utils/__pycache__/credentials.cpython-312.pyc -routes/utils/__pycache__/search.cpython-312.pyc +routes/__pycache__/* +routes/utils/__pycache__/* search_test.py config/main.json .cache @@ -31,4 +17,5 @@ config/state/queue_state.json output.log queue_state.json search_demo.py -celery_worker.log \ No newline at end of file +celery_worker.log +static/js/* \ No newline at end of file diff --git a/.gitignore b/.gitignore index f53d8b2..351e496 100755 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,5 @@ queue_state.json search_demo.py celery_worker.log logs/spotizerr.log -/.venv \ No newline at end of file +/.venv +static/js diff --git a/Dockerfile b/Dockerfile index f53b86b..82659d4 100755 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ gosu \ git \ ffmpeg \ + nodejs \ + npm \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* @@ -22,6 +24,14 @@ RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY . . +# Install TypeScript globally +RUN npm install -g typescript + +# Compile TypeScript +# tsc will use tsconfig.json from the current directory (/app) +# It will read from /app/src/js and output to /app/static/js +RUN tsc + # Create necessary directories with proper permissions RUN mkdir -p downloads config creds logs && \ chmod 777 downloads config creds logs diff --git a/docker-compose.yaml b/docker-compose.yaml index c0449af..a6d4ddb 100755 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -9,7 +9,7 @@ services: - ./logs:/app/logs # <-- Volume for persistent logs ports: - 7171:7171 - image: cooldockerizer93/spotizerr:dev + image: test container_name: spotizerr-app restart: unless-stopped environment: diff --git a/static/js/album.ts b/src/js/album.ts similarity index 100% rename from static/js/album.ts rename to src/js/album.ts diff --git a/static/js/artist.ts b/src/js/artist.ts similarity index 100% rename from static/js/artist.ts rename to src/js/artist.ts diff --git a/static/js/config.ts b/src/js/config.ts similarity index 98% rename from static/js/config.ts rename to src/js/config.ts index 21a4840..2c13d56 100644 --- a/static/js/config.ts +++ b/src/js/config.ts @@ -1,12 +1,27 @@ import { downloadQueue } from './queue.js'; +// Interfaces for validator data +interface SpotifyValidatorData { + username: string; + credentials?: string; // Credentials might be optional if only username is used as an identifier +} + +interface SpotifySearchValidatorData { + client_id: string; + client_secret: string; +} + +interface DeezerValidatorData { + arl: string; +} + const serviceConfig: Record = { spotify: { fields: [ { id: 'username', label: 'Username', type: 'text' }, - { id: 'credentials', label: 'Credentials', type: 'text' } + { id: 'credentials', label: 'Credentials', type: 'text' } // Assuming this is password/token ], - validator: (data) => ({ + validator: (data: SpotifyValidatorData) => ({ // Typed data username: data.username, credentials: data.credentials }), @@ -15,7 +30,7 @@ const serviceConfig: Record = { { id: 'client_id', label: 'Client ID', type: 'text' }, { id: 'client_secret', label: 'Client Secret', type: 'password' } ], - searchValidator: (data) => ({ + searchValidator: (data: SpotifySearchValidatorData) => ({ // Typed data client_id: data.client_id, client_secret: data.client_secret }) @@ -24,7 +39,7 @@ const serviceConfig: Record = { fields: [ { id: 'arl', label: 'ARL', type: 'text' } ], - validator: (data) => ({ + validator: (data: DeezerValidatorData) => ({ // Typed data arl: data.arl }) } diff --git a/static/js/main.ts b/src/js/main.ts similarity index 63% rename from static/js/main.ts rename to src/js/main.ts index 9560186..14340d1 100644 --- a/static/js/main.ts +++ b/src/js/main.ts @@ -1,6 +1,75 @@ // main.ts import { downloadQueue } from './queue.js'; +// Define interfaces for API data and search results +interface Image { + url: string; + height?: number; + width?: number; +} + +interface Artist { + id?: string; // Artist ID might not always be present in search results for track artists + name: string; + external_urls?: { spotify?: string }; + genres?: string[]; // For artist type results +} + +interface Album { + id?: string; // Album ID might not always be present + name: string; + images?: Image[]; + album_type?: string; // Used in startDownload + artists?: Artist[]; // Album can have artists too + total_tracks?: number; + release_date?: string; + external_urls?: { spotify?: string }; +} + +interface Track { + id: string; + name: string; + artists: Artist[]; + album: Album; + duration_ms?: number; + explicit?: boolean; + external_urls: { spotify: string }; + href?: string; // Some spotify responses use href +} + +interface Playlist { + id: string; + name: string; + owner: { display_name?: string; id?: string }; + images?: Image[]; + tracks: { total: number }; // Simplified for search results + external_urls: { spotify: string }; + href?: string; // Some spotify responses use href + explicit?: boolean; // Playlists themselves aren't explicit, but items can be +} + +// Specific item types for search results +interface TrackResultItem extends Track {} +interface AlbumResultItem extends Album { id: string; images?: Image[]; explicit?: boolean; external_urls: { spotify: string }; href?: string; } +interface PlaylistResultItem extends Playlist {} +interface ArtistResultItem extends Artist { id: string; images?: Image[]; explicit?: boolean; external_urls: { spotify: string }; href?: string; followers?: { total: number }; } + +// Union type for any search result item +type SearchResultItem = TrackResultItem | AlbumResultItem | PlaylistResultItem | ArtistResultItem; + +// Interface for the API response structure +interface SearchResponse { + items: SearchResultItem[]; + // Add other top-level properties from the search API if needed (e.g., total, limit, offset) +} + +// Interface for the item passed to downloadQueue.download +interface DownloadQueueItem { + name: string; + artist?: string; + album?: { name: string; album_type?: string }; +} + document.addEventListener('DOMContentLoaded', function() { // DOM elements const searchInput = document.getElementById('searchInput') as HTMLInputElement | null; @@ -106,7 +175,7 @@ document.addEventListener('DOMContentLoaded', function() { throw new Error('Network response was not ok'); } - const data = await response.json(); + const data = await response.json() as SearchResponse; // Assert type for API response // Hide loading indicator showLoading(false); @@ -164,7 +233,7 @@ document.addEventListener('DOMContentLoaded', function() { /** * Filters out items with null/undefined essential display parameters based on search type */ - function filterValidItems(items: any[], type: string) { + function filterValidItems(items: SearchResultItem[], type: string): SearchResultItem[] { if (!items) return []; return items.filter(item => { @@ -172,59 +241,59 @@ document.addEventListener('DOMContentLoaded', function() { if (!item) return false; // Skip explicit content if filter is enabled - if (downloadQueue.isExplicitFilterEnabled() && item.explicit === true) { + if (downloadQueue.isExplicitFilterEnabled() && ('explicit' in item && item.explicit === true)) { return false; } // Check essential parameters based on search type switch (type) { case 'track': - // For tracks, we need name, artists, and album + const trackItem = item as TrackResultItem; return ( - item.name && - item.artists && - item.artists.length > 0 && - item.artists[0] && - item.artists[0].name && - item.album && - item.album.name && - item.external_urls && - item.external_urls.spotify + trackItem.name && + trackItem.artists && + trackItem.artists.length > 0 && + trackItem.artists[0] && + trackItem.artists[0].name && + trackItem.album && + trackItem.album.name && + trackItem.external_urls && + trackItem.external_urls.spotify ); case 'album': - // For albums, we need name, artists, and cover image + const albumItem = item as AlbumResultItem; return ( - item.name && - item.artists && - item.artists.length > 0 && - item.artists[0] && - item.artists[0].name && - item.external_urls && - item.external_urls.spotify + albumItem.name && + albumItem.artists && + albumItem.artists.length > 0 && + albumItem.artists[0] && + albumItem.artists[0].name && + albumItem.external_urls && + albumItem.external_urls.spotify ); case 'playlist': - // For playlists, we need name, owner, and tracks + const playlistItem = item as PlaylistResultItem; return ( - item.name && - item.owner && - item.owner.display_name && - item.tracks && - item.external_urls && - item.external_urls.spotify + playlistItem.name && + playlistItem.owner && + playlistItem.owner.display_name && + playlistItem.tracks && + playlistItem.external_urls && + playlistItem.external_urls.spotify ); case 'artist': - // For artists, we need name + const artistItem = item as ArtistResultItem; return ( - item.name && - item.external_urls && - item.external_urls.spotify + artistItem.name && + artistItem.external_urls && + artistItem.external_urls.spotify ); default: - // Default case - just check if the item exists + // Default case - just check if the item exists (already handled by `if (!item) return false;`) return true; } }); @@ -233,7 +302,7 @@ document.addEventListener('DOMContentLoaded', function() { /** * Attaches download handlers to result cards */ - function attachDownloadListeners(items: any[]) { + function attachDownloadListeners(items: SearchResultItem[]) { document.querySelectorAll('.download-btn').forEach((btnElm) => { const btn = btnElm as HTMLButtonElement; btn.addEventListener('click', (e: Event) => { @@ -262,10 +331,37 @@ document.addEventListener('DOMContentLoaded', function() { } // Prepare metadata for the download - const metadata = { - name: item.name || 'Unknown', - artist: item.artists ? item.artists[0]?.name : undefined - }; + let metadata: DownloadQueueItem; + if (currentSearchType === 'track') { + const trackItem = item as TrackResultItem; + metadata = { + name: trackItem.name || 'Unknown', + artist: trackItem.artists ? trackItem.artists[0]?.name : undefined, + album: trackItem.album ? { name: trackItem.album.name, album_type: trackItem.album.album_type } : undefined + }; + } else if (currentSearchType === 'album') { + const albumItem = item as AlbumResultItem; + metadata = { + name: albumItem.name || 'Unknown', + artist: albumItem.artists ? albumItem.artists[0]?.name : undefined, + album: { name: albumItem.name, album_type: albumItem.album_type} + }; + } else if (currentSearchType === 'playlist') { + const playlistItem = item as PlaylistResultItem; + metadata = { + name: playlistItem.name || 'Unknown', + // artist for playlist is owner + artist: playlistItem.owner?.display_name + }; + } else if (currentSearchType === 'artist') { + const artistItem = item as ArtistResultItem; + metadata = { + name: artistItem.name || 'Unknown', + artist: artistItem.name // For artist type, artist is the item name itself + }; + } else { + metadata = { name: item.name || 'Unknown' }; // Fallback + } // Disable the button and update text btn.disabled = true; @@ -278,7 +374,8 @@ document.addEventListener('DOMContentLoaded', function() { } // Start the download - startDownload(url, currentSearchType, metadata, item.album ? item.album.album_type : null) + startDownload(url, currentSearchType, metadata, + (item as AlbumResultItem).album_type || ((item as TrackResultItem).album ? (item as TrackResultItem).album.album_type : null)) .then(() => { // For artists, show how many albums were queued if (currentSearchType === 'artist') { @@ -301,7 +398,7 @@ document.addEventListener('DOMContentLoaded', function() { /** * Starts the download process via API */ - async function startDownload(url: string, type: string, item: any, albumType: string | null) { + async function startDownload(url: string, type: string, item: DownloadQueueItem, albumType: string | null | undefined) { if (!url || !type) { showError('Missing URL or type for download'); return; @@ -385,7 +482,7 @@ document.addEventListener('DOMContentLoaded', function() { /** * Creates a result card element */ - function createResultCard(item: any, type: string, index: number): HTMLDivElement { + function createResultCard(item: SearchResultItem, type: string, index: number): HTMLDivElement { const cardElement = document.createElement('div'); cardElement.className = 'result-card'; @@ -394,10 +491,22 @@ document.addEventListener('DOMContentLoaded', function() { // Get the appropriate image URL let imageUrl = '/static/images/placeholder.jpg'; - if (item.album && item.album.images && item.album.images.length > 0) { - imageUrl = item.album.images[0].url; - } else if (item.images && item.images.length > 0) { - imageUrl = item.images[0].url; + // Type guards to safely access images + if (type === 'album' || type === 'artist') { + const albumOrArtistItem = item as AlbumResultItem | ArtistResultItem; + if (albumOrArtistItem.images && albumOrArtistItem.images.length > 0) { + imageUrl = albumOrArtistItem.images[0].url; + } + } else if (type === 'track') { + const trackItem = item as TrackResultItem; + if (trackItem.album && trackItem.album.images && trackItem.album.images.length > 0) { + imageUrl = trackItem.album.images[0].url; + } + } else if (type === 'playlist') { + const playlistItem = item as PlaylistResultItem; + if (playlistItem.images && playlistItem.images.length > 0) { + imageUrl = playlistItem.images[0].url; + } } // Get the appropriate details based on type @@ -406,20 +515,32 @@ document.addEventListener('DOMContentLoaded', function() { switch (type) { case 'track': - subtitle = item.artists ? item.artists.map(a => a.name).join(', ') : 'Unknown Artist'; - details = item.album ? `${item.album.name}${msToMinutesSeconds(item.duration_ms)}` : ''; + { + const trackItem = item as TrackResultItem; + subtitle = trackItem.artists ? trackItem.artists.map((a: Artist) => a.name).join(', ') : 'Unknown Artist'; + details = trackItem.album ? `${trackItem.album.name}${msToMinutesSeconds(trackItem.duration_ms)}` : ''; + } break; case 'album': - subtitle = item.artists ? item.artists.map(a => a.name).join(', ') : 'Unknown Artist'; - details = `${item.total_tracks || 0} tracks${item.release_date ? new Date(item.release_date).getFullYear() : ''}`; + { + const albumItem = item as AlbumResultItem; + subtitle = albumItem.artists ? albumItem.artists.map((a: Artist) => a.name).join(', ') : 'Unknown Artist'; + details = `${albumItem.total_tracks || 0} tracks${albumItem.release_date ? new Date(albumItem.release_date).getFullYear() : ''}`; + } break; case 'playlist': - subtitle = `By ${item.owner ? item.owner.display_name : 'Unknown'}`; - details = `${item.tracks && item.tracks.total ? item.tracks.total : 0} tracks`; + { + const playlistItem = item as PlaylistResultItem; + subtitle = `By ${playlistItem.owner ? playlistItem.owner.display_name : 'Unknown'}`; + details = `${playlistItem.tracks && playlistItem.tracks.total ? playlistItem.tracks.total : 0} tracks`; + } break; case 'artist': - subtitle = 'Artist'; - details = item.genres ? `${item.genres.slice(0, 2).join(', ')}` : ''; + { + const artistItem = item as ArtistResultItem; + subtitle = 'Artist'; + details = artistItem.genres ? `${artistItem.genres.slice(0, 2).join(', ')}` : ''; + } break; } diff --git a/static/js/playlist.ts b/src/js/playlist.ts similarity index 84% rename from static/js/playlist.ts rename to src/js/playlist.ts index c06fb9d..2a80ca7 100644 --- a/static/js/playlist.ts +++ b/src/js/playlist.ts @@ -1,6 +1,68 @@ // Import the downloadQueue singleton from your working queue.js implementation. import { downloadQueue } from './queue.js'; +// Define interfaces for API data +interface Image { + url: string; + height?: number; + width?: number; +} + +interface Artist { + id: string; + name: string; + external_urls?: { spotify?: string }; +} + +interface Album { + id: string; + name: string; + images?: Image[]; + external_urls?: { spotify?: string }; +} + +interface Track { + id: string; + name: string; + artists: Artist[]; + album: Album; + duration_ms: number; + explicit: boolean; + external_urls?: { spotify?: string }; +} + +interface PlaylistItem { + track: Track | null; + // Add other playlist item properties like added_at, added_by if needed +} + +interface Playlist { + id: string; + name: string; + description: string | null; + owner: { + display_name?: string; + id?: string; + }; + images: Image[]; + tracks: { + items: PlaylistItem[]; + total: number; + }; + followers?: { + total: number; + }; + external_urls?: { spotify?: string }; +} + +interface DownloadQueueItem { + name: string; + artist?: string; // Can be a simple string for the queue + album?: { name: string }; // Match QueueItem's album structure + owner?: string; // For playlists, owner can be a string + // Add any other properties your item might have, compatible with QueueItem +} + document.addEventListener('DOMContentLoaded', () => { // Parse playlist ID from URL const pathSegments = window.location.pathname.split('/'); @@ -15,7 +77,7 @@ document.addEventListener('DOMContentLoaded', () => { fetch(`/api/playlist/info?id=${encodeURIComponent(playlistId)}`) .then(response => { if (!response.ok) throw new Error('Network response was not ok'); - return response.json(); + return response.json() as Promise; }) .then(data => renderPlaylist(data)) .catch(error => { @@ -34,7 +96,7 @@ document.addEventListener('DOMContentLoaded', () => { /** * Renders playlist header and tracks. */ -function renderPlaylist(playlist: any) { +function renderPlaylist(playlist: Playlist) { // Hide loading and error messages const loadingEl = document.getElementById('loading'); if (loadingEl) loadingEl.classList.add('hidden'); @@ -80,7 +142,7 @@ function renderPlaylist(playlist: any) { // Check if any track in the playlist is explicit when filter is enabled let hasExplicitTrack = false; if (isExplicitFilterEnabled && playlist.tracks?.items) { - hasExplicitTrack = playlist.tracks.items.some(item => item?.track && item.track.explicit); + hasExplicitTrack = playlist.tracks.items.some((item: PlaylistItem) => item?.track && item.track.explicit); } // --- Add "Download Whole Playlist" Button --- @@ -178,7 +240,7 @@ function renderPlaylist(playlist: any) { tracksList.innerHTML = ''; // Clear any existing content if (playlist.tracks?.items) { - playlist.tracks.items.forEach((item, index) => { + playlist.tracks.items.forEach((item: PlaylistItem, index: number) => { if (!item || !item.track) return; // Skip null/undefined tracks const track = item.track; @@ -281,7 +343,10 @@ function attachDownloadListeners() { const name = currentTarget.dataset.name || extractName(url) || 'Unknown'; // Remove the button immediately after click. currentTarget.remove(); - startDownload(url, type, { name }, ''); // Added empty string for albumType + // For individual track downloads, we might not have album/artist name readily here. + // The queue.ts download method should be robust enough or we might need to fetch more data. + // For now, pass what we have. + startDownload(url, type, { name }, ''); // Pass name, artist/album are optional in DownloadQueueItem }); }); } @@ -289,7 +354,7 @@ function attachDownloadListeners() { /** * Initiates the whole playlist download by calling the playlist endpoint. */ -async function downloadWholePlaylist(playlist: any) { +async function downloadWholePlaylist(playlist: Playlist) { if (!playlist) { throw new Error('Invalid playlist data'); } @@ -301,7 +366,11 @@ async function downloadWholePlaylist(playlist: any) { try { // Use the centralized downloadQueue.download method - await downloadQueue.download(url, 'playlist', { name: playlist.name || 'Unknown Playlist' }); + await downloadQueue.download(url, 'playlist', { + name: playlist.name || 'Unknown Playlist', + owner: playlist.owner?.display_name // Pass owner as a string + // total_tracks can also be passed if QueueItem supports it directly + }); // Make the queue visible after queueing downloadQueue.toggleVisibility(true); } catch (error: any) { @@ -315,15 +384,15 @@ async function downloadWholePlaylist(playlist: any) { * adding a 20ms delay between each album download and updating the button * with the progress (queued_albums/total_albums). */ -async function downloadPlaylistAlbums(playlist: any) { +async function downloadPlaylistAlbums(playlist: Playlist) { if (!playlist?.tracks?.items) { showError('No tracks found in this playlist.'); return; } // Build a map of unique albums (using album ID as the key). - const albumMap = new Map(); - playlist.tracks.items.forEach(item => { + const albumMap = new Map(); + playlist.tracks.items.forEach((item: PlaylistItem) => { if (!item?.track?.album) return; const album = item.track.album; @@ -359,7 +428,11 @@ async function downloadPlaylistAlbums(playlist: any) { await downloadQueue.download( albumUrl, 'album', - { name: album.name || 'Unknown Album' } + { + name: album.name || 'Unknown Album', + // If artist information is available on album objects from playlist, pass it + // artist: album.artists?.[0]?.name + } ); // Update button text with current progress. @@ -387,7 +460,7 @@ async function downloadPlaylistAlbums(playlist: any) { /** * Starts the download process using the centralized download method from the queue. */ -async function startDownload(url: string, type: string, item: any, albumType?: string) { +async function startDownload(url: string, type: string, item: DownloadQueueItem, albumType?: string) { if (!url || !type) { showError('Missing URL or type for download'); return; diff --git a/static/js/queue.ts b/src/js/queue.ts similarity index 99% rename from static/js/queue.ts rename to src/js/queue.ts index c910ea4..f7d8d2f 100644 --- a/static/js/queue.ts +++ b/src/js/queue.ts @@ -826,7 +826,7 @@ createQueueItem(item: QueueItem, type: string, prgFile: string, queueId: string) const entries = Object.values(this.queueEntries); // Sorting: errors/canceled first (group 0), ongoing next (group 1), queued last (group 2, sorted by position). - entries.sort((a, b) => { + entries.sort((a: QueueEntry, b: QueueEntry) => { const getGroup = (entry: QueueEntry) => { // Add type if (entry.lastStatus && (entry.lastStatus.status === "error" || entry.lastStatus.status === "cancel")) { return 0; @@ -881,7 +881,7 @@ createQueueItem(item: QueueItem, type: string, prgFile: string, queueId: string) if (visibleItems.length === 0) { // No items in container, append all visible entries container.innerHTML = ''; // Clear any empty state - visibleEntries.forEach(entry => { + visibleEntries.forEach((entry: QueueEntry) => { // We no longer automatically start monitoring here // Monitoring is now explicitly started by the methods that create downloads container.appendChild(entry.element); @@ -890,7 +890,7 @@ createQueueItem(item: QueueItem, type: string, prgFile: string, queueId: string) // Container already has items, update more efficiently // Create a map of current DOM elements by queue ID - const existingElementMap = {}; + const existingElementMap: { [key: string]: HTMLElement } = {}; visibleItems.forEach(el => { const queueId = (el.querySelector('.cancel-btn') as HTMLElement | null)?.dataset.queueid; // Optional chaining if (queueId) existingElementMap[queueId] = el as HTMLElement; // Cast to HTMLElement @@ -900,7 +900,7 @@ createQueueItem(item: QueueItem, type: string, prgFile: string, queueId: string) container.innerHTML = ''; // Add visible entries in correct order - visibleEntries.forEach(entry => { + visibleEntries.forEach((entry: QueueEntry) => { // We no longer automatically start monitoring here container.appendChild(entry.element); @@ -931,7 +931,7 @@ createQueueItem(item: QueueItem, type: string, prgFile: string, queueId: string) /* Checks if an entry is visible in the queue display. */ isEntryVisible(queueId: string): boolean { // Add return type const entries = Object.values(this.queueEntries); - entries.sort((a, b) => { + entries.sort((a: QueueEntry, b: QueueEntry) => { const getGroup = (entry: QueueEntry) => { // Add type if (entry.lastStatus && (entry.lastStatus.status === "error" || entry.lastStatus.status === "cancel")) { return 0; @@ -954,7 +954,7 @@ createQueueItem(item: QueueItem, type: string, prgFile: string, queueId: string) return a.lastUpdated - b.lastUpdated; } }); - const index = entries.findIndex(e => e.uniqueId === queueId); + const index = entries.findIndex((e: QueueEntry) => e.uniqueId === queueId); return index >= 0 && index < this.visibleCount; } diff --git a/static/js/track.ts b/src/js/track.ts similarity index 100% rename from static/js/track.ts rename to src/js/track.ts diff --git a/tsconfig.json b/tsconfig.json index 34db1e4..f147cc1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,23 @@ { "compilerOptions": { - "target": "es2017", // Specify ECMAScript target version + "target": "ES2017", // Specify ECMAScript target version "module": "ES2020", // Specify module code generation "strict": true, // Enable all strict type-checking options - "noImplicitAny": false, // Allow implicit 'any' types "esModuleInterop": true, // Enables emit interoperability between CommonJS and ES Modules "skipLibCheck": true, // Skip type checking of declaration files - "forceConsistentCasingInFileNames": true // Disallow inconsistently-cased references to the same file. + "forceConsistentCasingInFileNames": true, // Disallow inconsistently-cased references to the same file. + "outDir": "./static/js", + "rootDir": "./src/js" }, "include": [ - "static/js/**/*.ts" // Specifies the TypeScript files to be included in compilation + "src/js/**/*.ts", + "src/js/album.ts", + "src/js/artist.ts", + "src/js/config.ts", + "src/js/main.ts", + "src/js/playlist.ts", + "src/js/queue.ts", + "src/js/track.ts" ], "exclude": [ "node_modules" // Specifies an array of filenames or patterns that should be skipped when resolving include.