Error Applet component, little incomplete documentation & incomplete tabmenu added

This commit is contained in:
root 2021-03-03 10:31:45 +06:00
parent faea24e86e
commit fe39ab0ffd
13 changed files with 590 additions and 184 deletions

View File

@ -1,63 +1,44 @@
# react-nodegui-starter # Spotube
Spotube is a [qt](https://qt.io) based lightweight spotify client which uses [nodegui/react-nodegui](https://github.com/nodegui/react-nodegui) as frontend & nodejs as backend. It utilizes the power of Spotify & Youtube's public API & creates a hazardless, performant & resource friendly User Experience
**Clone and run for a quick way to see React NodeGui in action.** ## Features
Following are the features that currently spotube offers:
- Open Source
- No telementry, diagnostics or user data collection
- Lightweight & resource friendly
- Near native performance & seemless with default desktop themes (Win10, Win7, OSX, QT-default)
- Playback control is on user's machine instead of server based
- Small size & less data hungry
- No spotify or youtube ads since it uses all public & free APIs (But it's recommended to support the creators by watching/liking/subscribing to the artists youtube channel or add as favourite track in spotify. Mostly buying spotify premium is the best way to support their valuable creations)
<img alt="logo" src="https://github.com/nodegui/react-nodegui-starter/raw/master/assets/demo.png" height="500" /> ## Installation
I'm always releasing newer versions of binary of the software each 2-3 month with minor changes & each 6-8 month with major changes. Grab the binaries
## To Use Windows: [.exe]()
To clone and run this repository you'll need [Git](https://git-scm.com) and [Node.js](https://nodejs.org/en/download/) (which comes with [npm](http://npmjs.com)) installed on your computer. From your command line: OSX: **I hate apple** (Just kidding, actually don't have a mac)
```bash Linux: [.appimage]()
# Clone this repository
git clone https://github.com/nodegui/react-nodegui-starter
# Install CMake
brew install cmake
# Go into the repository
cd react-nodegui-starter
# Install dependencies
npm install
# Run the dev server
npm run dev
# Open andother terminal and run the app
npm start
```
## Installation & Resources for learning React NodeGui
- [Documentation](https://react.nodegui.org) - all of React NodeGui's documentation. **I'll/try to upload the package binaries to linux debian/arch/ubuntu/snap/flatpack/redhat stores or software centers or repositories**
- [NodeGui](https://nodegui.org) - all of NodeGui's documentation.
## Packaging app as a distributable ## Configuration
There are some configurations that needs to be done to start using this software
In order to distribute your finished app, you can use [@nodegui/packer](https://github.com/nodegui/packer) You need a spotify account & a web app for
### Step 1: (_**Run this command only once**_) - clientId
- clientSecret
```sh See these screenshots:
npx nodegui-packer --init MyAppName
```
This will produce the deploy directory containing the template. You can modify this to suite your needs. Like add icons, change the name, description and add other native features or dependencies. Make sure you commit this directory. [1]()
### Step 2: (_**Run this command every time you want to build a new distributable**_) [2]()
Next you can run the pack command: [3]()
```sh [4]()
npm run build
```
This will produce the js bundle along with assets inside the `./dist` directory **[Important]!**: No personal data or any kind of sensitive information won't be collected from spotify. Don't believe? See the code for yourself
```sh
npx nodegui-packer --pack ./dist
```
This will build the distributable using @nodegui/packer based on your template. The output of the command is found under the build directory. You should gitignore the build directory.
More details about packer can be found here: https://github.com/nodegui/packer
## License
MIT

327
spotube.sublime-workspace Normal file
View File

@ -0,0 +1,327 @@
{
"auto_complete":
{
"selected_items":
[
]
},
"buffers":
[
{
"file": "src/components/Player.tsx",
"settings":
{
"buffer_size": 8355,
"line_ending": "Unix"
}
},
{
"file": "src/routes.tsx",
"settings":
{
"buffer_size": 688,
"line_ending": "Unix"
}
}
],
"build_system": "",
"build_system_choices":
[
],
"build_varint": "",
"command_palette":
{
"height": 0.0,
"last_filter": "",
"selected_items":
[
[
"Types: Sh",
"TypeScript: Show Error List"
],
[
"pro",
"Project: Save As"
],
[
"insta",
"Package Control: Install Package"
],
[
"re",
"Package Control: Remove Package"
],
[
"lsp type",
"Preferences: LSP-typescript Settings"
],
[
"install ",
"Package Control: Install Package"
],
[
"LSP: enale",
"LSP: Enable Language Server in Project"
],
[
"remove",
"Package Control: Remove Package"
],
[
"theme",
"UI: Select Theme"
],
[
"termi",
"Terminus: Close"
],
[
"Sync Up",
"Sync Settings: Upload"
],
[
"Sync crea",
"Sync Settings: Create and Upload"
],
[
"package ins",
"Package Control: Install Package"
],
[
"pac",
"Install Package Control"
]
],
"width": 0.0
},
"console":
{
"height": 170.0,
"history":
[
]
},
"distraction_free":
{
"menu_visible": true,
"show_minimap": false,
"show_open_files": false,
"show_tabs": false,
"side_bar_visible": false,
"status_bar_visible": false
},
"file_history":
[
"/home/krtirtho/development/desktop-dev/spotube/spotube.sublime-project",
"/home/krtirtho/.config/sublime-text-3/Packages/User/Preferences.sublime-settings",
"/home/krtirtho/.config/sublime-text-3/Packages/TypeScript/Preferences.sublime-settings",
"/home/krtirtho/.config/sublime-text-3/Packages/TypeScript/TypeScriptReact.sublime-settings",
"/home/krtirtho/.config/sublime-text-3/Packages/TypeScript/TypeScript.sublime-settings",
"/home/krtirtho/.config/sublime-text-3/Packages/Default/Default (Linux).sublime-keymap",
"/home/krtirtho/.config/sublime-text-3/Packages/User/Default (Linux).sublime-keymap",
"/home/krtirtho/.config/sublime-text-3/Packages/Sync Settings/SyncSettings.sublime-settings",
"/home/krtirtho/.config/sublime-text-3/Packages/User/SyncSettings.sublime-settings"
],
"find":
{
"height": 26.0
},
"find_in_files":
{
"height": 0.0,
"where_history":
[
]
},
"find_state":
{
"case_sensitive": false,
"find_history":
[
],
"highlight": true,
"in_selection": false,
"preserve_case": false,
"regex": false,
"replace_history":
[
],
"reverse": false,
"show_context": true,
"use_buffer2": true,
"whole_word": false,
"wrap": true
},
"groups":
[
{
"selected": 1,
"sheets":
[
{
"buffer": 0,
"file": "src/components/Player.tsx",
"semi_transient": false,
"settings":
{
"buffer_size": 8355,
"regions":
{
},
"selection":
[
[
2966,
2966
]
],
"settings":
{
"syntax": "Packages/TypeScript/TypeScriptReact.tmLanguage",
"tab_size": 2,
"translate_tabs_to_spaces": true,
"use_tab_stops": false
},
"translation.x": 0.0,
"translation.y": 1275.0,
"zoom_level": 1.0
},
"stack_index": 1,
"type": "text"
},
{
"buffer": 1,
"file": "src/routes.tsx",
"semi_transient": false,
"settings":
{
"buffer_size": 688,
"regions":
{
},
"selection":
[
[
688,
688
]
],
"settings":
{
"color_scheme": "Packages/GitHub Theme/schemes/GitHub Dark.sublime-color-scheme",
"syntax": "Packages/TypeScript/TypeScriptReact.tmLanguage",
"tab_size": 2,
"translate_tabs_to_spaces": true,
"typescript_plugin_format_options":
{
"convertTabsToSpaces": true,
"indentSize": 2,
"tabSize": 2
},
"use_tab_stops": false
},
"translation.x": 0.0,
"translation.y": 0.0,
"zoom_level": 1.0
},
"stack_index": 0,
"type": "text"
}
]
}
],
"incremental_find":
{
"height": 26.0
},
"input":
{
"height": 38.0
},
"layout":
{
"cells":
[
[
0,
0,
1,
1
]
],
"cols":
[
0.0,
1.0
],
"rows":
[
0.0,
1.0
]
},
"menu_visible": true,
"output.diagnostics":
{
"height": 0.0
},
"output.doc":
{
"height": 0.0
},
"output.errorlist":
{
"height": 140.0
},
"output.find_results":
{
"height": 0.0
},
"pinned_build_system": "",
"project": "spotube.sublime-project",
"replace":
{
"height": 48.0
},
"save_all_on_build": true,
"select_file":
{
"height": 0.0,
"last_filter": "",
"selected_items":
[
],
"width": 0.0
},
"select_project":
{
"height": 0.0,
"last_filter": "",
"selected_items":
[
],
"width": 0.0
},
"select_symbol":
{
"height": 0.0,
"last_filter": "",
"selected_items":
[
],
"width": 0.0
},
"selected_group": 0,
"settings":
{
},
"show_minimap": true,
"show_open_files": true,
"show_tabs": true,
"side_bar_visible": true,
"side_bar_width": 225.0,
"status_bar_visible": true,
"template_settings":
{
}
}

View File

@ -139,12 +139,10 @@ function RootApp() {
<authContext.Provider value={{ isLoggedIn, setIsLoggedIn, access_token, expires_in, setAccess_token, setExpires_in: setExpireTime, ...credentials }}> <authContext.Provider value={{ isLoggedIn, setIsLoggedIn, access_token, expires_in, setAccess_token, setExpires_in: setExpireTime, ...credentials }}>
<playerContext.Provider value={{ currentPlaylist, currentTrack, setCurrentPlaylist, setCurrentTrack }}> <playerContext.Provider value={{ currentPlaylist, currentTrack, setCurrentPlaylist, setCurrentTrack }}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
{/* <View style={`flex: 1; flex-direction: 'column'; justify-content: 'center'; align-items: 'stretch'; height: '100%';`}> */} <View style={`flex: 1; flex-direction: 'column'; justify-content: 'center'; align-items: 'stretch'; height: '100%';`}>
<BoxView direction={Direction.TopToBottom}>
<Routes /> <Routes />
{isLoggedIn && <Player />} {isLoggedIn && <Player />}
</BoxView> </View>
{/* </View> */}
</QueryClientProvider> </QueryClientProvider>
</playerContext.Provider> </playerContext.Provider>
</authContext.Provider> </authContext.Provider>

View File

@ -1,38 +1,43 @@
import React, { useContext, useEffect, useState } from "react"; import React from "react";
import { Button, ScrollArea, BoxView } from "@nodegui/react-nodegui"; import { Button, ScrollArea, BoxView, View } from "@nodegui/react-nodegui";
import { useHistory } from "react-router"; import { useHistory } from "react-router";
import CachedImage from "./shared/CachedImage"; import CachedImage from "./shared/CachedImage";
import { CursorShape, Direction } from "@nodegui/nodegui"; import { CursorShape, Direction } from "@nodegui/nodegui";
import useSpotifyApi from "../hooks/useSpotifyApi"; import { QueryCacheKeys } from "../conf";
import showError from "../helpers/showError"; import useSpotifyQuery from "../hooks/useSpotifyQuery";
import authContext from "../context/authContext"; import ErrorApplet from "./shared/ErrorApplet";
import useSpotifyApiError from "../hooks/useSpotifyApiError";
function Home() { function Home() {
const spotifyApi = useSpotifyApi(); const {
const { access_token } = useContext(authContext); data: categories,
const [categories, setCategories] = useState<SpotifyApi.CategoryObject[]>([]); isError,
const handleSpotifyError = useSpotifyApiError(spotifyApi); isRefetchError,
refetch,
useEffect(() => { } = useSpotifyQuery<SpotifyApi.CategoryObject[]>(
if (categories.length === 0) { QueryCacheKeys.categories,
(spotifyApi) =>
spotifyApi spotifyApi
.getCategories({ country: "US" }) .getCategories({ country: "US" })
.then((categoriesReceived) => setCategories(categoriesReceived.body.categories.items)) .then((categoriesReceived) => categoriesReceived.body.categories.items),
.catch((error) => { { initialData: [] }
showError(error, "[Spotify genre loading failed]: "); );
handleSpotifyError(error);
});
}
}, [access_token]);
return ( return (
<ScrollArea style={`flex-grow: 1; border: none;`}> <ScrollArea style={`flex-grow: 1; border: none;`}>
<BoxView direction={Direction.TopToBottom}> <View style={`flex-direction: 'column'; justify-content: 'center'; flex: 1;`}>
{categories.map((category, index) => { {(isError || isRefetchError) && (
return <CategoryCard key={index+category.id} id={category.id} name={category.name} />; <ErrorApplet message="Failed to query genres" reload={refetch} helps />
)}
{categories?.map((category, index) => {
return (
<CategoryCard
key={index + category.id}
id={category.id}
name={category.name}
/>
);
})} })}
</BoxView> </View>
</ScrollArea> </ScrollArea>
); );
} }
@ -46,40 +51,52 @@ interface CategoryCardProps {
const CategoryCard = ({ id, name }: CategoryCardProps) => { const CategoryCard = ({ id, name }: CategoryCardProps) => {
const history = useHistory(); const history = useHistory();
const [playlists, setPlaylists] = useState<SpotifyApi.PlaylistObjectSimplified[]>([]); const { data: playlists, isError } = useSpotifyQuery<
const spotifyApi = useSpotifyApi(); SpotifyApi.PlaylistObjectSimplified[]
const handleSpotifyError = useSpotifyApiError(spotifyApi); >(
[QueryCacheKeys.categoryPlaylists, id],
useEffect(() => { (spotifyApi) =>
if (playlists.length === 0) {
spotifyApi spotifyApi
.getPlaylistsForCategory(id, { limit: 4 }) .getPlaylistsForCategory(id, { limit: 4 })
.then((playlistsRes) => setPlaylists(playlistsRes.body.playlists.items)) .then((playlistsRes) => playlistsRes.body.playlists.items),
.catch((error) => { { initialData: [] }
showError(error, `[Failed to get playlists of category ${name}]: `); );
handleSpotifyError(error);
});
}
}, []);
function goToGenre() { function goToGenre() {
history.push(`/genre/playlists/${id}`, { name }); history.push(`/genre/playlists/${id}`, { name });
} }
if (isError) {
return <></ >;
}
return ( return (
<BoxView id="container" styleSheet={categoryStylesheet} direction={Direction.TopToBottom}> <View id="container" styleSheet={categoryStylesheet}>
<Button id="anchor-heading" cursor={CursorShape.PointingHandCursor} on={{ MouseButtonRelease: goToGenre }} text={name} /> <Button
<BoxView direction={Direction.LeftToRight}> id="anchor-heading"
{playlists.map((playlist, index) => { cursor={CursorShape.PointingHandCursor}
return <PlaylistCard key={index+playlist.id} id={playlist.id} name={playlist.name} thumbnail={playlist.images[0].url} />; on={{ MouseButtonRelease: goToGenre }}
text={name}
/>
<View id="child-view">
{playlists?.map((playlist, index) => {
return (
<PlaylistCard
key={index + playlist.id}
id={playlist.id}
name={playlist.name}
thumbnail={playlist.images[0].url}
/>
);
})} })}
</BoxView> </View>
</BoxView> </View>
); );
}; };
const categoryStylesheet = ` const categoryStylesheet = `
#container{ #container{
flex: 1;
flex-direction: column;
justify-content: 'center';
margin-bottom: 20px; margin-bottom: 20px;
} }
#anchor-heading{ #anchor-heading{
@ -89,8 +106,13 @@ const categoryStylesheet = `
outline: none; outline: none;
font-size: 20px; font-size: 20px;
font-weight: bold; font-weight: bold;
text-align: left; align-self: flex-start;
} }
#child-view{
flex: 1;
justify-content: 'space-around';
align-items: 'center';
}
#anchor-heading:hover{ #anchor-heading:hover{
border: none; border: none;
outline: none; outline: none;
@ -104,18 +126,19 @@ interface PlaylistCardProps {
id: string; id: string;
} }
export const PlaylistCard = React.memo(({ id, name, thumbnail }: PlaylistCardProps) => { export const PlaylistCard = React.memo(
const history = useHistory(); ({ id, name, thumbnail }: PlaylistCardProps) => {
const history = useHistory();
function gotoPlaylist() { function gotoPlaylist() {
history.push(`/playlist/${id}`, { name, thumbnail }); history.push(`/playlist/${id}`, { name, thumbnail });
} }
const playlistStyleSheet = ` const playlistStyleSheet = `
#playlist-container{ #playlist-container{
max-width: 150px; max-width: 250px;
max-height: 150px; flex-direction: column;
min-height: 150px; padding: 2px;
} }
#playlist-container:hover{ #playlist-container:hover{
border: 1px solid green; border: 1px solid green;
@ -125,9 +148,20 @@ export const PlaylistCard = React.memo(({ id, name, thumbnail }: PlaylistCardPro
} }
`; `;
return ( return (
<BoxView size={{height: 150, width: 150, fixed: true}} direction={Direction.TopToBottom} id="playlist-container" cursor={CursorShape.PointingHandCursor} styleSheet={playlistStyleSheet} on={{ MouseButtonRelease: gotoPlaylist }}> <View
<CachedImage src={thumbnail} maxSize={{ height: 150, width: 150 }} scaledContents alt={name} /> id="playlist-container"
</BoxView> cursor={CursorShape.PointingHandCursor}
); styleSheet={playlistStyleSheet}
}); on={{ MouseButtonRelease: gotoPlaylist }}
>
<CachedImage
src={thumbnail}
maxSize={{ height: 150, width: 150 }}
scaledContents
alt={name}
/>
</View>
);
}
);

View File

@ -1,32 +1,21 @@
import { ScrollArea, Text, View } from "@nodegui/react-nodegui"; import { Direction } from "@nodegui/nodegui";
import React, { useContext, useEffect, useState } from "react"; import { BoxView, ScrollArea, Text, View, GridView, GridColumn, GridRow } from "@nodegui/react-nodegui";
import React from "react";
import { useLocation, useParams } from "react-router"; import { useLocation, useParams } from "react-router";
import authContext from "../context/authContext"; import { QueryCacheKeys } from "../conf";
import showError from "../helpers/showError"; import useSpotifyQuery from "../hooks/useSpotifyQuery";
import useSpotifyApi from "../hooks/useSpotifyApi";
import useSpotifyApiError from "../hooks/useSpotifyApiError";
import BackButton from "./BackButton"; import BackButton from "./BackButton";
import { PlaylistCard } from "./Home"; import { PlaylistCard } from "./Home";
function PlaylistGenreView() { function PlaylistGenreView() {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const location = useLocation<{ name: string }>(); const location = useLocation<{ name: string }>();
const [playlists, setPlaylists] = useState<SpotifyApi.PlaylistObjectSimplified[]>([]); const { data: playlists } = useSpotifyQuery<SpotifyApi.PlaylistObjectSimplified[]>(
const { access_token, isLoggedIn } = useContext(authContext); [QueryCacheKeys.genrePlaylists, id],
const spotifyApi = useSpotifyApi(); (spotifyApi) => spotifyApi.getPlaylistsForCategory(id)
const handleSpotifyError = useSpotifyApiError(spotifyApi); .then((playlistsRes) => playlistsRes.body.playlists.items),
{ initialData: [] }
useEffect(() => { )
if (playlists.length === 0 && access_token) {
spotifyApi
.getPlaylistsForCategory(id)
.then((playlistsRes) => setPlaylists(playlistsRes.body.playlists.items))
.catch((error) => {
showError(error, `[Failed to get playlists of category ${location.state.name} for]: `);
handleSpotifyError(error);
});
}
}, [access_token]);
const playlistGenreViewStylesheet = ` const playlistGenreViewStylesheet = `
#genre-container{ #genre-container{
@ -56,10 +45,16 @@ function PlaylistGenreView() {
<Text id="heading">{`<h2>${location.state.name}</h2>`}</Text> <Text id="heading">{`<h2>${location.state.name}</h2>`}</Text>
<ScrollArea id="scroll-view"> <ScrollArea id="scroll-view">
<View id="child-container"> <View id="child-container">
{isLoggedIn && {playlists?.map((playlist, index) => {
playlists.map((playlist, index) => { return (
return <PlaylistCard key={((index * Date.now()) / Math.random()) * 100} id={playlist.id} name={playlist.name} thumbnail={playlist.images[0].url} />; <PlaylistCard
})} key={index + playlist.id}
id={playlist.id}
name={playlist.name}
thumbnail={playlist.images[0].url}
/>
);
})}
</View> </View>
</ScrollArea> </ScrollArea>
</View> </View>

View File

@ -1,5 +1,5 @@
import React, { FC, useContext } from "react"; import React, { FC, useContext } from "react";
import { BoxView, Button, ScrollArea, Text } from "@nodegui/react-nodegui"; import { BoxView, View, Button, ScrollArea, Text } from "@nodegui/react-nodegui";
import BackButton from "./BackButton"; import BackButton from "./BackButton";
import { useLocation, useParams } from "react-router"; import { useLocation, useParams } from "react-router";
import { Direction, QAbstractButtonSignals, QIcon } from "@nodegui/nodegui"; import { Direction, QAbstractButtonSignals, QIcon } from "@nodegui/nodegui";
@ -44,15 +44,15 @@ const PlaylistView: FC = () => {
}; };
return ( return (
<BoxView direction={Direction.TopToBottom}> <View style={`flex: 1; flex-direction: 'column'; flex-grow: 1;`}>
<BoxView style={`max-width: 150px;`}> <View style={`justify-content: 'space-evenly'; max-width: 150px; padding: 10px;`}>
<BackButton /> <BackButton />
<IconButton icon={new QIcon(heartRegular)} /> <IconButton icon={new QIcon(heartRegular)} />
<IconButton style={`background-color: #00be5f; color: white;`} on={{ clicked: handlePlaylistPlayPause }} icon={new QIcon(currentPlaylist?.id === params.id ? stop : play)} /> <IconButton style={`background-color: #00be5f; color: white;`} on={{ clicked: handlePlaylistPlayPause }} icon={new QIcon(currentPlaylist?.id === params.id ? stop : play)} />
</BoxView> </View>
<Text>{`<center><h2>${location.state.name[0].toUpperCase()}${location.state.name.slice(1)}</h2></center>`}</Text> <Text>{`<center><h2>${location.state.name[0].toUpperCase()}${location.state.name.slice(1)}</h2></center>`}</Text>
<ScrollArea style={`flex-grow: 1; border: none;`}> <ScrollArea style={`flx:1; flex-grow: 1; border: none;`}>
<BoxView /* style={`flex-direction:column;`} */ direction={Direction.TopToBottom}> <View style={`flex-direction:column; flex: 1;`}>
{isLoading && <Text>{`Loading Tracks...`}</Text>} {isLoading && <Text>{`Loading Tracks...`}</Text>}
{isError && ( {isError && (
<> <>
@ -78,9 +78,9 @@ const PlaylistView: FC = () => {
/> />
); );
})} })}
</BoxView> </View>
</ScrollArea> </ScrollArea>
</BoxView> </View>
); );
}; };

View File

@ -0,0 +1,47 @@
import React from "react";
import {Route} from "react-router"
import {View, Button} from "@nodegui/react-nodegui"
import {QIcon} from "@nodegui/nodegui"
function TabMenu(){
return (
<View id="tabmenu" styleSheet={tabBarStylesheet}>
<TabMenuItem title="Browse"/>
<TabMenuItem title="Library"/>
<TabMenuItem title="Currently Playing"/>
</View>
)
}
const tabBarStylesheet = `
#tabmenu{
flex-direction: 'column';
align-items: 'center';
max-width: 225px;
}
#tabmenu-item{
background-color: transparent;
}
#tabmenu-item:hover{
color: green;
}
#tabmenu-item:active{
color: #59ff88;
}
`
export default TabMenu;
interface TabMenuItemProps{
title: string;
/**
* path to the icon in string
*/
icon?: string;
}
export function TabMenuItem({icon, title}:TabMenuItemProps){
return (
<Button id="tabmenu-item" text={title}/>
)
}

View File

@ -0,0 +1,24 @@
import { View, Button, Text } from "@nodegui/react-nodegui";
import React from "react"
interface ErrorAppletProps {
message?: string;
reload: Function;
helps?: boolean;
}
function ErrorApplet({ message, reload, helps }: ErrorAppletProps) {
return (
<View style="flex-direction: 'column'; align-items: 'center';">
<Text openExternalLinks>{`
<h3>${message ? message : 'An error occured'}</h3>
${helps ? `<p>Check if you're connected to internet & then try again. If the issue still persists ask for help or create a <a href="https://github.com/krtirtho/spotube/issues">issue</a>
</p>`: ``
}
`}</Text>
<Button on={{ clicked() { reload() } }} text="Reload" />
</View>
)
}
export default ErrorApplet;

View File

@ -9,5 +9,6 @@ export const redirectURI = "http://localhost:4304/auth/spotify/callback"
export enum QueryCacheKeys{ export enum QueryCacheKeys{
categories="categories", categories="categories",
categoryPlaylists = "categoryPlaylists", categoryPlaylists = "categoryPlaylists",
playlistTracks="playlistTracks" genrePlaylists="genrePlaylists",
playlistTracks="playlistTracks",
} }

View File

@ -1,29 +0,0 @@
import { DependencyList, useContext, useEffect } from "react";
import authContext from "../context/authContext";
import { CredentialKeys } from "../app";
import SpotifyWebApi from "spotify-web-api-node";
interface UseAccessTokenResult {
access_token: string;
}
export default (spotifyApi: SpotifyWebApi, deps: DependencyList = []): UseAccessTokenResult => {
const { access_token, expires_in, isLoggedIn, setExpires_in, setAccess_token } = useContext(authContext);
const refreshToken = localStorage.getItem(CredentialKeys.refresh_token);
useEffect(() => {
const isExpiredToken = Date.now() > expires_in;
if (isLoggedIn && isExpiredToken && refreshToken) {
spotifyApi.setRefreshToken(refreshToken);
spotifyApi
.refreshAccessToken()
.then(({ body: { access_token, expires_in } }) => {
setAccess_token(access_token);
setExpires_in(expires_in);
})
.catch();
}
}, deps);
return { access_token };
};

View File

@ -2,19 +2,38 @@ import chalk from "chalk";
import { useContext, useEffect } from "react"; import { useContext, useEffect } from "react";
import { CredentialKeys } from "../app"; import { CredentialKeys } from "../app";
import authContext from "../context/authContext"; import authContext from "../context/authContext";
import showError from "../helpers/showError";
import spotifyApi from "../initializations/spotifyApi"; import spotifyApi from "../initializations/spotifyApi";
function useSpotifyApi() { function useSpotifyApi() {
const { access_token, clientId, clientSecret, isLoggedIn } = useContext(authContext); const {
access_token,
clientId,
clientSecret,
isLoggedIn,
setAccess_token,
} = useContext(authContext);
const refreshToken = localStorage.getItem(CredentialKeys.refresh_token); const refreshToken = localStorage.getItem(CredentialKeys.refresh_token);
useEffect(() => { useEffect(() => {
if (isLoggedIn && clientId && clientSecret && refreshToken) { if (isLoggedIn && clientId && clientSecret && refreshToken) {
console.log(chalk.bgCyan.black("Setting up spotify credentials")) 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);
if (!access_token) {
spotifyApi
.refreshAccessToken()
.then((token) => {
console.log(chalk.bgRedBright.yellow("Refreshing access token from useSpotifyApi"));
setAccess_token(token.body.access_token);
})
.catch((error) => {
showError(error);
});
}
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

@ -1,5 +1,5 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { QueryFunction, QueryKey, useQuery, UseQueryOptions, UseQueryResult } from "react-query"; import { QueryKey, useQuery, UseQueryOptions, UseQueryResult } from "react-query";
import SpotifyWebApi from "spotify-web-api-node"; import SpotifyWebApi from "spotify-web-api-node";
import useSpotifyApi from "./useSpotifyApi"; import useSpotifyApi from "./useSpotifyApi";
import useSpotifyApiError from "./useSpotifyApiError"; import useSpotifyApiError from "./useSpotifyApiError";
@ -15,7 +15,7 @@ function useSpotifyQuery<TQueryData = unknown>(
const handleSpotifyError = useSpotifyApiError(spotifyApi); const handleSpotifyError = useSpotifyApiError(spotifyApi);
const query = useQuery<TQueryData, SpotifyApi.ErrorObject>(queryKey, ()=>queryHandler(spotifyApi), options); const query = useQuery<TQueryData, SpotifyApi.ErrorObject>(queryKey, ()=>queryHandler(spotifyApi), options);
const { isError, error } = query; const { isError, error } = query;
useEffect(() => { useEffect(() => {
if (isError && error) { if (isError && error) {

View File

@ -1,10 +1,12 @@
import React, { useContext } from "react"; import React, { useContext } from "react";
import {View} from "@nodegui/react-nodegui";
import { Route } from "react-router"; import { Route } from "react-router";
import authContext from "./context/authContext"; import authContext from "./context/authContext";
import Home from "./components/Home"; import Home from "./components/Home";
import Login from "./components/Login"; import Login from "./components/Login";
import PlaylistView from "./components/PlaylistView"; import PlaylistView from "./components/PlaylistView";
import PlaylistGenreView from "./components/PlaylistGenreView"; import PlaylistGenreView from "./components/PlaylistGenreView";
import TabMenu from "./components/TabMenu";
function Routes() { function Routes() {
const { const {
@ -12,11 +14,18 @@ function Routes() {
} = useContext(authContext); } = useContext(authContext);
return ( return (
<> <>
<Route exact path="/"> <Route path="/">
{isLoggedIn ? <Home /> : <Login />} { isLoggedIn ?
<View style="background-color: black; flex: 1; flex-direction: 'column';">
<TabMenu />
<Route exact path="/"><Home/></Route>
<Route exact path="/playlist/:id"><PlaylistView /></Route>
<Route exact path="/genre/playlists/:id"><PlaylistGenreView /></Route>
</View>
: <Login/>
}
</Route> </Route>
<Route exact path="/playlist/:id"><PlaylistView/></Route>
<Route exact path="/genre/playlists/:id"><PlaylistGenreView/></Route>
</> </>
); );
} }