Search & SearchResultPlaylist see all added. Spotify Infinite Query added for infinite scroll

This commit is contained in:
KRTirtho 2021-03-18 11:11:03 +06:00
parent be612610d9
commit d695172804
10 changed files with 135 additions and 47 deletions

View File

@ -75,8 +75,11 @@ const CategoryCard = ({ id, name }: CategoryCardProps) => {
{ initialData: [] } { initialData: [] }
); );
function goToGenre() { function goToGenre(native: any) {
history.push(`/genre/playlists/${id}`, { name }); const mouse = new QMouseEvent(native);
if (mouse.button() === 1) {
history.push(`/genre/playlists/${id}`, { name });
}
} }
if (isError) { if (isError) {
return <></>; return <></>;

View File

@ -1,5 +1,5 @@
import { Direction } from "@nodegui/nodegui"; import { QAbstractButtonSignals } from "@nodegui/nodegui";
import { BoxView, ScrollArea, Text, View, GridView, GridColumn, GridRow } from "@nodegui/react-nodegui"; import { Button, ScrollArea, Text, View } from "@nodegui/react-nodegui";
import React from "react"; import React from "react";
import { useLocation, useParams } from "react-router"; import { useLocation, useParams } from "react-router";
import { QueryCacheKeys } from "../conf"; import { QueryCacheKeys } from "../conf";
@ -12,11 +12,23 @@ function PlaylistGenreView() {
const location = useLocation<{ name: string }>(); const location = useLocation<{ name: string }>();
const { data: playlists } = useSpotifyQuery<SpotifyApi.PlaylistObjectSimplified[]>( const { data: playlists } = useSpotifyQuery<SpotifyApi.PlaylistObjectSimplified[]>(
[QueryCacheKeys.genrePlaylists, id], [QueryCacheKeys.genrePlaylists, id],
(spotifyApi) => spotifyApi.getPlaylistsForCategory(id) (spotifyApi) => spotifyApi.getPlaylistsForCategory(id).then((playlistsRes) => playlistsRes.body.playlists.items),
.then((playlistsRes) => playlistsRes.body.playlists.items),
{ initialData: [] } { initialData: [] }
) );
return <GenreView heading={location.state.name} playlists={playlists ?? []} />;
}
export default PlaylistGenreView;
interface GenreViewProps {
heading: string;
playlists: SpotifyApi.PlaylistObjectSimplified[];
loadMore?: QAbstractButtonSignals["clicked"];
isLoadable?: boolean;
}
export function GenreView({ heading, playlists, loadMore, isLoadable }: GenreViewProps) {
const playlistGenreViewStylesheet = ` const playlistGenreViewStylesheet = `
#genre-container{ #genre-container{
flex-direction: column; flex-direction: column;
@ -38,25 +50,18 @@ function PlaylistGenreView() {
width: 330px; width: 330px;
} }
`; `;
return ( return (
<View id="genre-container" styleSheet={playlistGenreViewStylesheet}> <View id="genre-container" styleSheet={playlistGenreViewStylesheet}>
<BackButton /> <BackButton />
<Text id="heading">{`<h2>${location.state.name}</h2>`}</Text> <Text id="heading">{`<h2>${heading}</h2>`}</Text>
<ScrollArea id="scroll-view"> <ScrollArea id="scroll-view">
<View id="child-container"> <View id="child-container">
{playlists?.map((playlist, index) => { {playlists?.map((playlist, index) => {
return ( return <PlaylistCard key={index + playlist.id} playlist={playlist} />;
<PlaylistCard
key={index + playlist.id}
playlist={playlist}
/>
);
})} })}
{loadMore && <Button text="Load more" on={{ clicked: loadMore }} enabled={isLoadable} />}
</View> </View>
</ScrollArea> </ScrollArea>
</View> </View>
); );
} }
export default PlaylistGenreView;

View File

@ -76,20 +76,22 @@ const PlaylistView: FC = () => {
</> </>
)} )}
{tracks?.map(({ track }, index) => { {tracks?.map(({ track }, index) => {
return ( if (track) {
<TrackButton return (
key={index + track.id} <TrackButton
track={track} key={index + track.id}
index={index} track={track}
active={currentTrack?.id === track.id && currentPlaylist?.id === params.id} index={index}
on={{ active={currentTrack?.id === track.id && currentPlaylist?.id === params.id}
MouseButtonRelease: () => trackClickHandler(track), on={{
}} MouseButtonRelease: () => trackClickHandler(track),
onTrackClick={() => { }}
handlePlaylistPlayPause(index); onTrackClick={() => {
}} handlePlaylistPlayPause(index);
/> }}
); />
);
}
})} })}
</View> </View>
</ScrollArea> </ScrollArea>

View File

@ -1,6 +1,7 @@
import { QIcon } from "@nodegui/nodegui"; import { QIcon, QMouseEvent } from "@nodegui/nodegui";
import { LineEdit, ScrollArea, Text, View } from "@nodegui/react-nodegui"; import { LineEdit, ScrollArea, Text, View } from "@nodegui/react-nodegui";
import React, { useState } from "react"; import React, { useState } from "react";
import { useHistory } from "react-router";
import { QueryCacheKeys } from "../conf"; import { QueryCacheKeys } from "../conf";
import showError from "../helpers/showError"; import showError from "../helpers/showError";
import useSpotifyQuery from "../hooks/useSpotifyQuery"; import useSpotifyQuery from "../hooks/useSpotifyQuery";
@ -10,10 +11,11 @@ import { TrackButton, TrackTableIndex } from "./PlaylistView";
import IconButton from "./shared/IconButton"; import IconButton from "./shared/IconButton";
function Search() { function Search() {
const history = useHistory<{ searchQuery: string }>();
const [searchQuery, setSearchQuery] = useState<string>(""); const [searchQuery, setSearchQuery] = useState<string>("");
const { data: searchResults, isError, refetch } = useSpotifyQuery<SpotifyApi.SearchResponse>( const { data: searchResults, isError, refetch } = useSpotifyQuery<SpotifyApi.SearchResponse>(
QueryCacheKeys.search, QueryCacheKeys.search,
(spotifyApi) => spotifyApi.search(searchQuery, ["playlist", "track"]).then((res) => res.body), (spotifyApi) => spotifyApi.search(searchQuery, ["playlist", "track"], { limit: 4 }).then((res) => res.body),
{ enabled: false } { enabled: false }
); );
@ -45,18 +47,23 @@ function Search() {
<Text>{`<h2>Songs</h2>`}</Text> <Text>{`<h2>Songs</h2>`}</Text>
<TrackTableIndex /> <TrackTableIndex />
{searchResults?.tracks?.items.map((track, index) => ( {searchResults?.tracks?.items.map((track, index) => (
<TrackButton active={false} index={index} track={track} on={{}} onTrackClick={() => {}} /> <TrackButton key={index+track.id} active={false} index={index} track={track} on={{}} onTrackClick={() => {}} />
))} ))}
</View> </View>
<View style="flex: 1; flex-direction: 'column';"> <View style="flex: 1; flex-direction: 'column';">
<Text>{`<h2>Playlists</h2>`}</Text> <Text
<ScrollArea style="flex: 1;"> on={{
<View style="flex-direction: 'row'; align-items: 'center'; flex-wrap: 'wrap'; width: 330px;"> MouseButtonRelease(native: any) {
{searchResults?.playlists?.items.map((playlist) => ( if (new QMouseEvent(native).button() === 1) {
<PlaylistCard playlist={playlist} /> history.push("/search/playlists", { searchQuery });
))} }
</View> },
</ScrollArea> }}>{`<h2>Playlists</h2>`}</Text>
<View style="flex: 1; justify-content: 'space-around'; align-items: 'center';">
{searchResults?.playlists?.items.map((playlist, index) => (
<PlaylistCard key={index+playlist.id} playlist={playlist} />
))}
</View>
</View> </View>
</View> </View>
</ScrollArea> </ScrollArea>

View File

@ -0,0 +1,35 @@
import React, { useState } from "react";
import { useLocation } from "react-router";
import { QueryCacheKeys } from "../conf";
import useSpotifyInfiniteQuery from "../hooks/useSpotifyInfiniteQuery";
import { GenreView } from "./PlaylistGenreView";
function SearchResultPlaylistCollection() {
const location = useLocation<{ searchQuery: string }>();
const { data: searchResults, fetchNextPage, hasNextPage, isFetchingNextPage } = useSpotifyInfiniteQuery<SpotifyApi.SearchResponse>(
QueryCacheKeys.searchPlaylist,
(spotifyApi, { pageParam }) => spotifyApi.searchPlaylists(location.state.searchQuery, { limit: 20, offset: pageParam }).then((res) => res.body),
{
getNextPageParam: (lastPage) => {
return (lastPage.playlists?.offset ?? 0) + 1;
},
}
);
return (
<GenreView
heading={"Search: "+location.state.searchQuery}
playlists={
(searchResults?.pages
?.map((page) => page.playlists?.items)
.filter(Boolean)
.flat(1) as SpotifyApi.PlaylistObjectSimplified[]) ?? []
}
loadMore={() => {
fetchNextPage();
}}
isLoadable={hasNextPage || !isFetchingNextPage}
/>
);
}
export default SearchResultPlaylistCollection;

View File

@ -16,5 +16,6 @@ export enum QueryCacheKeys{
playlistTracks="playlistTracks", playlistTracks="playlistTracks",
userPlaylists = "user-palylists", userPlaylists = "user-palylists",
userSavedTracks = "user-saved-tracks", userSavedTracks = "user-saved-tracks",
search="search" search = "search",
searchPlaylist="searchPlaylist"
} }

View File

@ -17,7 +17,6 @@ function useSpotifyApi() {
useEffect(() => { useEffect(() => {
if (isLoggedIn && clientId && clientSecret && refreshToken) { if (isLoggedIn && clientId && clientSecret && refreshToken) {
console.log(chalk.bgCyan.black("Setting up spotify credentials"));
spotifyApi.setClientId(clientId); spotifyApi.setClientId(clientId);
spotifyApi.setClientSecret(clientSecret); spotifyApi.setClientSecret(clientSecret);
spotifyApi.setRefreshToken(refreshToken); spotifyApi.setRefreshToken(refreshToken);
@ -25,7 +24,6 @@ function useSpotifyApi() {
spotifyApi spotifyApi
.refreshAccessToken() .refreshAccessToken()
.then((token) => { .then((token) => {
console.log(chalk.bgRedBright.yellow("Refreshing access token from useSpotifyApi"));
setAccess_token(token.body.access_token); setAccess_token(token.body.access_token);
}) })
.catch((error) => { .catch((error) => {
@ -33,7 +31,6 @@ function useSpotifyApi() {
}); });
} }
spotifyApi.setAccessToken(access_token); spotifyApi.setAccessToken(access_token);
console.log(chalk.bgCyan.green("Finished setting up credentials"));
} }
}, [access_token, clientId, clientSecret, isLoggedIn]); }, [access_token, clientId, clientSecret, isLoggedIn]);

View File

@ -0,0 +1,28 @@
import { useEffect } from "react";
import { QueryFunctionContext, QueryKey, useInfiniteQuery, UseInfiniteQueryOptions, UseInfiniteQueryResult } from "react-query";
import SpotifyWebApi from "spotify-web-api-node";
import useSpotifyApi from "./useSpotifyApi";
import useSpotifyApiError from "./useSpotifyApiError";
type SpotifyQueryFn<TQueryData> = (spotifyApi: SpotifyWebApi, pageArgs: QueryFunctionContext) => Promise<TQueryData>;
function useSpotifyInfiniteQuery<TQueryData = unknown>(
queryKey: QueryKey,
queryHandler: SpotifyQueryFn<TQueryData>,
options: UseInfiniteQueryOptions<TQueryData, SpotifyApi.ErrorObject> = {}
): UseInfiniteQueryResult<TQueryData, SpotifyApi.ErrorObject> {
const spotifyApi = useSpotifyApi();
const handleSpotifyError = useSpotifyApiError(spotifyApi);
const query = useInfiniteQuery<TQueryData, SpotifyApi.ErrorObject>(queryKey, (pageArgs) => queryHandler(spotifyApi, pageArgs), options);
const { isError, error } = query;
useEffect(() => {
if (isError && error) {
handleSpotifyError(error);
}
}, [isError, error]);
return query;
}
export default useSpotifyInfiniteQuery;

View File

@ -9,6 +9,7 @@ import TabMenu from "./components/TabMenu";
import CurrentPlaylist from "./components/CurrentPlaylist"; import CurrentPlaylist from "./components/CurrentPlaylist";
import Library from "./components/Library"; import Library from "./components/Library";
import Search from "./components/Search"; import Search from "./components/Search";
import SearchResultPlaylistCollection from "./components/SearchResultPlaylistCollection";
function Routes() { function Routes() {
const { isLoggedIn } = useContext(authContext); const { isLoggedIn } = useContext(authContext);
@ -33,13 +34,18 @@ function Routes() {
<Login /> <Login />
)} )}
</Route> </Route>
<Route path="/search"><Search/></Route>
<Route path="/currently"> <Route path="/currently">
<CurrentPlaylist /> <CurrentPlaylist />
</Route> </Route>
<Route path="/library"> <Route path="/library">
<Library /> <Library />
</Route> </Route>
<Route exact path="/search">
<Search />
</Route>
<Route exact path="/search/playlists">
<SearchResultPlaylistCollection />
</Route>
</> </>
); );
} }

View File

@ -2,6 +2,10 @@
"compilerOptions": { "compilerOptions": {
"target": "es2016", "target": "es2016",
"module": "commonjs", "module": "commonjs",
"lib": [
"ES2019.Array",
"DOM"
],
"jsx": "react", "jsx": "react",
"strict": true, "strict": true,
"alwaysStrict": true, "alwaysStrict": true,