diff --git a/src/app.tsx b/src/app.tsx index 462d8619..c6517ed1 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -12,14 +12,16 @@ import express from "express"; import open from "open"; import spotifyApi from "./initializations/spotifyApi"; import showError from "./helpers/showError"; -import fs from "fs" +import fs from "fs"; import path from "path"; import { confDir } from "./conf"; import spotubeIcon from "../assets/icon.svg"; +import preferencesContext, { PreferencesContextProperties } from "./context/preferencesContext"; -export enum CredentialKeys { +export enum LocalStorageKeys { credentials = "credentials", refresh_token = "refresh_token", + preferences = "user-preferences", } export interface Credentials { @@ -30,7 +32,7 @@ export interface Credentials { const minSize = { width: 700, height: 750 }; const winIcon = new QIcon(spotubeIcon); const localStorageDir = path.join(confDir, "local"); -fs.mkdirSync(localStorageDir, {recursive: true}); +fs.mkdirSync(localStorageDir, { recursive: true }); global.localStorage = new LocalStorage(localStorageDir); const queryClient = new QueryClient({ defaultOptions: { @@ -42,6 +44,11 @@ const queryClient = new QueryClient({ }, }); +const initialPreferences: PreferencesContextProperties = { + playlistImages: false, +}; + +//* Application start function RootApp() { const windowRef = useRef(); const [currentTrack, setCurrentTrack] = useState(); @@ -75,16 +82,28 @@ function RootApp() { }, [currentTrack] ); - + // cache + const cachedPreferences = localStorage.getItem(LocalStorageKeys.preferences); + const cachedCredentials = localStorage.getItem(LocalStorageKeys.credentials); + // state const [isLoggedIn, setIsLoggedIn] = useState(false); const [credentials, setCredentials] = useState({ clientId: "", clientSecret: "" }); + const [preferences, setPreferences] = useState(() => { + if (cachedPreferences) { + return JSON.parse(cachedPreferences); + } + return initialPreferences; + }); const [access_token, setAccess_token] = useState(""); const [currentPlaylist, setCurrentPlaylist] = useState(); - const cachedCredentials = localStorage.getItem(CredentialKeys.credentials); useEffect(() => { setIsLoggedIn(!!cachedCredentials); }, []); + // just saves the preferences + useEffect(() => { + localStorage.setItem(LocalStorageKeys.preferences, JSON.stringify(preferences)); + }, [preferences]); useEffect(() => { const onWindowClose = () => { @@ -100,7 +119,7 @@ function RootApp() { }); // for user code login useEffect(() => { - if (isLoggedIn && credentials && !localStorage.getItem(CredentialKeys.refresh_token)) { + if (isLoggedIn && credentials && !localStorage.getItem(LocalStorageKeys.refresh_token)) { const app = express(); app.use(express.json()); @@ -110,7 +129,7 @@ function RootApp() { spotifyApi.setClientSecret(credentials.clientSecret); const { body: authRes } = await spotifyApi.authorizationCodeGrant(req.query.code); setAccess_token(authRes.access_token); - localStorage.setItem(CredentialKeys.refresh_token, authRes.refresh_token); + localStorage.setItem(LocalStorageKeys.refresh_token, authRes.refresh_token); return res.end(); } catch (error) { console.error("Failed to fullfil code grant flow: ", error); @@ -121,7 +140,7 @@ function RootApp() { console.log("Server is running"); spotifyApi.setClientId(credentials.clientId); spotifyApi.setClientSecret(credentials.clientSecret); - open(spotifyApi.createAuthorizeURL(["user-library-read", "playlist-read-private", "user-library-modify","playlist-modify-private", "playlist-modify-public"], "xxxyyysssddd")).catch((e) => + open(spotifyApi.createAuthorizeURL(["user-library-read", "playlist-read-private", "user-library-modify", "playlist-modify-private", "playlist-modify-public"], "xxxyyysssddd")).catch((e) => console.error("Opening IPC connection with browser failed: ", e) ); }); @@ -141,14 +160,16 @@ function RootApp() { - - - - - {isLoggedIn && } - - - + + + + + + {isLoggedIn && } + + + + diff --git a/src/components/Login.tsx b/src/components/Login.tsx index 6f7a80a4..d7b04f00 100644 --- a/src/components/Login.tsx +++ b/src/components/Login.tsx @@ -1,7 +1,7 @@ import React, { useContext, useState } from "react"; import { LineEdit, Text, Button, View } from "@nodegui/react-nodegui"; import authContext from "../context/authContext"; -import { CredentialKeys, Credentials } from "../app"; +import { LocalStorageKeys, Credentials } from "../app"; function Login() { const { setIsLoggedIn } = useContext(authContext); @@ -55,7 +55,7 @@ function Login() { on={{ clicked: () => { localStorage.setItem( - CredentialKeys.credentials, + LocalStorageKeys.credentials, JSON.stringify({ clientId: credentials.clientId, clientSecret: credentials.clientSecret, diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index b838d1ab..d0d3953d 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -1,14 +1,20 @@ import { Text, View } from "@nodegui/react-nodegui"; -import React from "react"; -import Switch from "./shared/Switch"; +import React, { useContext } from "react"; +import preferencesContext from "../context/preferencesContext"; +import Switch, { SwitchProps } from "./shared/Switch"; function Settings() { + const { setPreferences, ...preferences } = useContext(preferencesContext) return ( {`

Settings

`}
- - + setPreferences({ ...preferences, playlistImages: checked })} + />
); @@ -19,9 +25,11 @@ export default Settings; interface SettingsCheckTileProps { title: string; subtitle?: string; + checked: boolean; + onChange?: SwitchProps["onChange"]; } -export function SettingsCheckTile({ title, subtitle = "" }: SettingsCheckTileProps) { +export function SettingsCheckTile({ title, subtitle = "", onChange, checked }: SettingsCheckTileProps) { return ( @@ -30,7 +38,7 @@ export function SettingsCheckTile({ title, subtitle = "" }: SettingsCheckTilePro

${subtitle}

`}
- +
); } diff --git a/src/components/shared/PlaylistCard.tsx b/src/components/shared/PlaylistCard.tsx index 821c0a4c..f2fcc945 100644 --- a/src/components/shared/PlaylistCard.tsx +++ b/src/components/shared/PlaylistCard.tsx @@ -1,22 +1,26 @@ -import { CursorShape, QIcon, QMouseEvent } from '@nodegui/nodegui'; -import { Text, View } from '@nodegui/react-nodegui'; -import React, { useContext, useMemo, useState } from 'react' -import { useHistory } from 'react-router'; -import { QueryCacheKeys } from '../../conf'; -import playerContext from '../../context/playerContext'; -import { generateRandomColor, getDarkenForeground } from '../../helpers/RandomColor'; -import showError from '../../helpers/showError'; -import usePlaylistReaction from '../../hooks/usePlaylistReaction'; -import useSpotifyQuery from '../../hooks/useSpotifyQuery'; -import { heart, heartRegular, pause, play } from '../../icons'; -import { audioPlayer } from '../Player'; -import IconButton from './IconButton'; +import { CursorShape, QIcon, QMouseEvent } from "@nodegui/nodegui"; +import { Text, View } from "@nodegui/react-nodegui"; +import React, { useContext, useMemo, useState } from "react"; +import { useHistory } from "react-router"; +import { QueryCacheKeys } from "../../conf"; +import playerContext from "../../context/playerContext"; +import preferencesContext from "../../context/preferencesContext"; +import { generateRandomColor, getDarkenForeground } from "../../helpers/RandomColor"; +import showError from "../../helpers/showError"; +import usePlaylistReaction from "../../hooks/usePlaylistReaction"; +import useSpotifyQuery from "../../hooks/useSpotifyQuery"; +import { heart, heartRegular, pause, play } from "../../icons"; +import { audioPlayer } from "../Player"; +import CachedImage from "./CachedImage"; +import IconButton from "./IconButton"; interface PlaylistCardProps { playlist: SpotifyApi.PlaylistObjectSimplified; } const PlaylistCard = ({ playlist }: PlaylistCardProps) => { + const preferences = useContext(preferencesContext); + const thumbnail = playlist.images[0].url; const { id, description, name, images } = playlist; const history = useHistory(); const [hovered, setHovered] = useState(false); @@ -31,7 +35,7 @@ const PlaylistCard = ({ playlist }: PlaylistCardProps) => { try { const { data: tracks, isSuccess } = await refetch(); if (currentPlaylist?.id !== id && isSuccess && tracks) { - setCurrentPlaylist({ tracks, id, name, thumbnail: images[0].url }); + setCurrentPlaylist({ tracks, id, name, thumbnail }); setCurrentTrack(tracks[0].track); } else { await audioPlayer.stop(); @@ -46,7 +50,7 @@ const PlaylistCard = ({ playlist }: PlaylistCardProps) => { function gotoPlaylist(native?: any) { const key = new QMouseEvent(native); if (key.button() === 1) { - history.push(`/playlist/${id}`, { name, thumbnail: images[0].url }); + history.push(`/playlist/${id}`, { name, thumbnail }); } } @@ -54,14 +58,16 @@ const PlaylistCard = ({ playlist }: PlaylistCardProps) => { const color = useMemo(() => getDarkenForeground(bgColor1), [bgColor1]); const playlistStyleSheet = ` - #playlist-container{ + #playlist-container, #img-container{ width: 150px; - flex-direction: column; padding: 10px; - min-height: 150px; - background-color: ${bgColor1}; - border-radius: 5px; margin: 5px; + flex-direction: column; + background-color: ${bgColor1}; + } + #playlist-container{ + border-radius: 5px; + min-height: 150px; } #playlist-container:hover{ border: 1px solid green; @@ -70,23 +76,55 @@ const PlaylistCard = ({ playlist }: PlaylistCardProps) => { border: 5px solid green; } `; + const playlistAction = ` + position: absolute; + bottom: 30px; + background-color: ${color}; + `; + + const playlistActions = ( + <> + + + + ); + const hovers = { + HoverEnter() { + setHovered(true); + }, + HoverLeave() { + setHovered(false); + }, + }; return ( - {/* */} - + {preferences.playlistImages && } + + {`

${name}

@@ -94,30 +132,14 @@ const PlaylistCard = ({ playlist }: PlaylistCardProps) => {
`}
- {(hovered || currentPlaylist?.id === id) && ( - <> - - - - )} + + {(hovered || currentPlaylist?.id === id) && !preferences.playlistImages && playlistActions} + {preferences.playlistImages && + {playlistActions} + + }
); }; -export default PlaylistCard; \ No newline at end of file +export default PlaylistCard; diff --git a/src/components/shared/Switch.tsx b/src/components/shared/Switch.tsx index 78434fde..0e042766 100644 --- a/src/components/shared/Switch.tsx +++ b/src/components/shared/Switch.tsx @@ -3,20 +3,22 @@ import { Slider } from "@nodegui/react-nodegui"; import { CheckBoxProps } from "@nodegui/react-nodegui/dist/components/CheckBox/RNCheckBox"; import React, { useEffect, useState } from "react"; -interface SwitchProps extends Omit{ - onChange?(checked:boolean): void +export interface SwitchProps extends Omit { + onChange?(checked: boolean): void; } -function Switch({ checked, onChange, ...props }: SwitchProps) { - const [value, setValue] = useState<0|1>(0); +function Switch({ checked: derivedChecked, onChange, ...props }: SwitchProps) { + const [checked, setChecked] = useState(false); useEffect(() => { - setValue(checked ? 1 : 0); - }, [checked]) - + if (derivedChecked) { + setChecked(derivedChecked); + } + }, []); + return ( ); + /> + ); } export default Switch; diff --git a/src/context/preferencesContext.ts b/src/context/preferencesContext.ts new file mode 100644 index 00000000..d2f3030e --- /dev/null +++ b/src/context/preferencesContext.ts @@ -0,0 +1,15 @@ +import React, { Dispatch, SetStateAction } from "react"; + +export interface PreferencesContextProperties { + playlistImages: boolean; +} +export interface PreferencesContext extends PreferencesContextProperties { + setPreferences: Dispatch>; +} + +const preferencesContext = React.createContext({ + playlistImages: false, + setPreferences() { } +}); + +export default preferencesContext; diff --git a/src/hooks/useSpotifyApi.ts b/src/hooks/useSpotifyApi.ts index 19ec7f2b..ae672268 100644 --- a/src/hooks/useSpotifyApi.ts +++ b/src/hooks/useSpotifyApi.ts @@ -1,6 +1,6 @@ import chalk from "chalk"; import { useContext, useEffect } from "react"; -import { CredentialKeys } from "../app"; +import { LocalStorageKeys } from "../app"; import authContext from "../context/authContext"; import showError from "../helpers/showError"; import spotifyApi from "../initializations/spotifyApi"; @@ -13,7 +13,7 @@ function useSpotifyApi() { isLoggedIn, setAccess_token, } = useContext(authContext); - const refreshToken = localStorage.getItem(CredentialKeys.refresh_token); + const refreshToken = localStorage.getItem(LocalStorageKeys.refresh_token); useEffect(() => { if (isLoggedIn && clientId && clientSecret && refreshToken) {