diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..3662b370 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} \ No newline at end of file diff --git a/assets/loading-spinner.gif b/assets/loading-spinner.gif new file mode 100644 index 00000000..fb9c052f Binary files /dev/null and b/assets/loading-spinner.gif differ diff --git a/src/app.tsx b/src/app.tsx index 83a3bb99..cff361e4 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -53,7 +53,6 @@ function RootApp() { if (nativeEv) { const event = new QKeyEvent(nativeEv); const eventKey = event.key(); - console.log("eventKey:", eventKey); if (audioPlayer.isRunning() && currentTrack) switch (eventKey) { case 32: //space diff --git a/src/components/CurrentPlaylist.tsx b/src/components/CurrentPlaylist.tsx index c16c4466..92dd5438 100644 --- a/src/components/CurrentPlaylist.tsx +++ b/src/components/CurrentPlaylist.tsx @@ -1,15 +1,22 @@ import { ScrollArea, Text, View } from "@nodegui/react-nodegui"; import React, { useContext } from "react"; import playerContext from "../context/playerContext"; -import { TrackButton, TrackTableIndex } from "./PlaylistView"; +import { TrackTableIndex } from "./PlaylistView"; +import { TrackButton } from "./shared/TrackButton"; function CurrentPlaylist() { - const { currentPlaylist, currentTrack, setCurrentTrack } = useContext(playerContext); + const { currentPlaylist, currentTrack } = useContext(playerContext); if (!currentPlaylist && !currentTrack) { return {`
There is nothing being played now
`}
; } + if (currentTrack && !currentPlaylist) { + + + + } + return ( {`

${currentPlaylist?.name}

`}
@@ -17,16 +24,7 @@ function CurrentPlaylist() { {currentPlaylist?.tracks.map(({ track }, index) => { - return ( - setCurrentTrack(track) }} - onTrackClick={() => {}} - /> - ); + return ; })} diff --git a/src/components/Home.tsx b/src/components/Home.tsx index 359aea05..85f9ecc1 100644 --- a/src/components/Home.tsx +++ b/src/components/Home.tsx @@ -1,21 +1,14 @@ -import React, { useContext, useMemo, useState } from "react"; -import { Button, ScrollArea, View, Text } from "@nodegui/react-nodegui"; +import React from "react"; +import { Button, ScrollArea, View } from "@nodegui/react-nodegui"; import { useHistory } from "react-router"; -import CachedImage from "./shared/CachedImage"; -import { CursorShape, QIcon, QMouseEvent } from "@nodegui/nodegui"; +import { CursorShape, QMouseEvent } from "@nodegui/nodegui"; import { QueryCacheKeys } from "../conf"; import useSpotifyQuery from "../hooks/useSpotifyQuery"; -import ErrorApplet from "./shared/ErrorApplet"; -import IconButton from "./shared/IconButton"; -import { heart, heartRegular, pause, play } from "../icons"; -import playerContext from "../context/playerContext"; -import { audioPlayer } from "./Player"; -import showError from "../helpers/showError"; -import { generateRandomColor, getDarkenForeground } from "../helpers/RandomColor"; -import usePlaylistReaction from "../hooks/usePlaylistReaction"; +import PlaceholderApplet from "./shared/PlaceholderApplet"; +import PlaylistCard from "./shared/PlaylistCard"; function Home() { - const { data: categories, isError, isRefetchError, refetch } = useSpotifyQuery( + const { data: categories, isError, refetch, isLoading } = useSpotifyQuery( QueryCacheKeys.categories, (spotifyApi) => spotifyApi.getCategories({ country: "US" }).then((categoriesReceived) => categoriesReceived.body.categories.items), { initialData: [] } @@ -24,7 +17,7 @@ function Home() { return ( - {(isError || isRefetchError) && } + {categories?.map((category, index) => { return ; })} @@ -95,110 +88,3 @@ const CategoryCard = ({ id, name }: CategoryCardProps) => { ); }; - -interface PlaylistCardProps { - playlist: SpotifyApi.PlaylistObjectSimplified; -} - -export const PlaylistCard = React.memo(({ playlist }: PlaylistCardProps) => { - const { id, description, name, images } = playlist; - const history = useHistory(); - const [hovered, setHovered] = useState(false); - const { setCurrentTrack, currentPlaylist, setCurrentPlaylist } = useContext(playerContext); - const { refetch } = useSpotifyQuery([QueryCacheKeys.playlistTracks, id], (spotifyApi) => spotifyApi.getPlaylistTracks(id).then((track) => track.body.items), { - initialData: [], - enabled: false, - }); - const { reactToPlaylist, isFavorite } = usePlaylistReaction(); - - const handlePlaylistPlayPause = async () => { - try { - const { data: tracks, isSuccess } = await refetch(); - if (currentPlaylist?.id !== id && isSuccess && tracks) { - setCurrentPlaylist({ tracks, id, name, thumbnail: images[0].url }); - setCurrentTrack(tracks[0].track); - } else { - await audioPlayer.stop(); - setCurrentTrack(undefined); - setCurrentPlaylist(undefined); - } - } catch (error) { - showError(error, "[Failed adding playlist to queue]: "); - } - }; - - function gotoPlaylist(native?: any) { - const key = new QMouseEvent(native); - if (key.button() === 1) { - history.push(`/playlist/${id}`, { name, thumbnail: images[0].url }); - } - } - - const bgColor1 = useMemo(() => generateRandomColor(), []); - const color = useMemo(() => getDarkenForeground(bgColor1), [bgColor1]); - - const playlistStyleSheet = ` - #playlist-container{ - width: 150px; - flex-direction: column; - padding: 5px; - min-height: 150px; - background-color: ${bgColor1}; - border-radius: 5px; - } - #playlist-container:hover{ - border: 1px solid green; - } - #playlist-container:clicked{ - border: 5px solid green; - } - `; - - return ( - - {/* */} - - {` -
-

${name}

-

${description}

-
- `} -
- {(hovered || currentPlaylist?.id === id) && ( - <> - - - - )} -
- ); -}); diff --git a/src/components/Library.tsx b/src/components/Library.tsx index fcf5fe3e..cce426fd 100644 --- a/src/components/Library.tsx +++ b/src/components/Library.tsx @@ -1,11 +1,14 @@ -import { ScrollArea, View } from "@nodegui/react-nodegui"; +import { Button, ScrollArea, View } from "@nodegui/react-nodegui"; import React, { useContext } from "react"; import { Redirect, Route } from "react-router"; import { QueryCacheKeys } from "../conf"; import playerContext from "../context/playerContext"; +import useSpotifyInfiniteQuery from "../hooks/useSpotifyInfiniteQuery"; import useSpotifyQuery from "../hooks/useSpotifyQuery"; -import { PlaylistCard } from "./Home"; -import { PlaylistSimpleControls, TrackButton, TrackTableIndex } from "./PlaylistView"; +import { PlaylistSimpleControls, TrackTableIndex } from "./PlaylistView"; +import PlaceholderApplet from "./shared/PlaceholderApplet"; +import PlaylistCard from "./shared/PlaylistCard"; +import { TrackButton } from "./shared/TrackButton"; import { TabMenuItem } from "./TabMenu"; function Library() { @@ -38,6 +41,7 @@ function UserPlaylists() { return ( + {userPlaylists?.map((playlist, index) => ( ))} @@ -48,10 +52,23 @@ function UserPlaylists() { function UserSavedTracks() { const userSavedPlaylistId = "user-saved-tracks"; - const { data: userTracks, isError, isLoading } = useSpotifyQuery(QueryCacheKeys.userSavedTracks, (spotifyApi) => - spotifyApi.getMySavedTracks({ limit: 50 }).then((tracks) => tracks.body.items) + const { data: userSavedTracks, fetchNextPage, hasNextPage, isFetchingNextPage, isError, isLoading, refetch } = useSpotifyInfiniteQuery( + QueryCacheKeys.userSavedTracks, + (spotifyApi, { pageParam }) => spotifyApi.getMySavedTracks({ limit: 50, offset: pageParam }).then((res) => res.body), + { + getNextPageParam(lastPage) { + if (lastPage.next) { + return lastPage.offset + lastPage.limit; + } + }, + } ); - const { currentPlaylist, setCurrentPlaylist, setCurrentTrack, currentTrack } = useContext(playerContext); + const { currentPlaylist, setCurrentPlaylist, setCurrentTrack } = useContext(playerContext); + + const userTracks = userSavedTracks?.pages + ?.map((page) => page.items) + .filter(Boolean) + .flat(1) as SpotifyApi.SavedTrackObject[] | undefined; function handlePlaylistPlayPause(index?: number) { if (currentPlaylist?.id !== userSavedPlaylistId && userTracks) { @@ -63,26 +80,58 @@ function UserSavedTracks() { } } + const playlist: SpotifyApi.PlaylistObjectFull = { + collaborative: false, + description: "User Playlist", + tracks: { + items: [userTracks ?? []].map( + (userTrack) => + (({ + ...userTrack, + added_by: "Me", + is_local: false, + added_at: Date.now(), + } as unknown) as SpotifyApi.PlaylistTrackObject) + ), + limit: 20, + href: "", + next: "", + offset: 0, + previous: "", + total: 20, + }, + external_urls: { spotify: "" }, + followers: { href: null, total: 2 }, + href: "", + id: userSavedPlaylistId, + images: [], + name: "User saved track", + owner: { external_urls: { spotify: "" }, href: "", id: "Me", type: "user", uri: "spotify:user:me", display_name: "User", followers: { href: null, total: 0 } }, + public: false, + snapshot_id: userSavedPlaylistId + "snapshot", + type: "playlist", + uri: "spotify:user:me:saved-tracks", + }; return ( - + - {userTracks?.map(({ track }, index) => ( - + {userTracks?.map(({ track }, index) => track && )} + {hasNextPage && ( +