mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
playlist play, optimized layout & better player style implemented
This commit is contained in:
parent
23394e437b
commit
b97a8edc5b
@ -1,3 +0,0 @@
|
|||||||
[Dolphin]
|
|
||||||
Timestamp=2021,2,13,23,13,52
|
|
||||||
Version=4
|
|
26
src/app.tsx
26
src/app.tsx
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { Window, hot, BoxView, View } from "@nodegui/react-nodegui";
|
import { Window, hot, View } from "@nodegui/react-nodegui";
|
||||||
import { Direction, QIcon, QMainWindow, WidgetEventTypes, WindowState } from "@nodegui/nodegui";
|
import { QIcon, QMainWindow, WidgetEventTypes, WindowState } from "@nodegui/nodegui";
|
||||||
import nodeguiIcon from "../assets/nodegui.jpg";
|
import nodeguiIcon from "../assets/nodegui.jpg";
|
||||||
import { MemoryRouter } from "react-router";
|
import { MemoryRouter } from "react-router";
|
||||||
import Routes from "./routes";
|
import Routes from "./routes";
|
||||||
@ -19,7 +19,7 @@ export interface Credentials {
|
|||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const minSize = { width: 700, height: 520 };
|
const minSize = { width: 700, height: 750 };
|
||||||
const winIcon = new QIcon(nodeguiIcon);
|
const winIcon = new QIcon(nodeguiIcon);
|
||||||
global.localStorage = new LocalStorage("./local");
|
global.localStorage = new LocalStorage("./local");
|
||||||
|
|
||||||
@ -35,13 +35,21 @@ function RootApp() {
|
|||||||
const credentialStr = localStorage.getItem(CredentialKeys.credentials);
|
const credentialStr = localStorage.getItem(CredentialKeys.credentials);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
windowRef.current?.addEventListener(WidgetEventTypes.Close, () => {
|
setIsLoggedIn(!!credentialStr);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onWindowClose = () => {
|
||||||
if (audioPlayer.isRunning()) {
|
if (audioPlayer.isRunning()) {
|
||||||
audioPlayer.stop().catch((e) => console.error("Failed to quit MPV player: ", e));
|
audioPlayer.stop().catch((e) => console.error("Failed to quit MPV player: ", e));
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
setIsLoggedIn(!!credentialStr);
|
|
||||||
}, []);
|
windowRef.current?.addEventListener(WidgetEventTypes.Close, onWindowClose);
|
||||||
|
return () => {
|
||||||
|
windowRef.current?.removeEventListener(WidgetEventTypes.Close, onWindowClose);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
@ -67,10 +75,10 @@ function RootApp() {
|
|||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<authContext.Provider value={{ isLoggedIn, setIsLoggedIn, access_token }}>
|
<authContext.Provider value={{ isLoggedIn, setIsLoggedIn, access_token }}>
|
||||||
<playerContext.Provider value={{ spotifyApi, currentPlaylist, currentTrack, setCurrentPlaylist, setCurrentTrack }}>
|
<playerContext.Provider value={{ spotifyApi, currentPlaylist, currentTrack, setCurrentPlaylist, setCurrentTrack }}>
|
||||||
<BoxView direction={Direction.TopToBottom}>
|
<View style={`flex: 1; flex-direction: 'column'; justify-content: 'center'; align-items: 'stretch'; height: '100%';`}>
|
||||||
<Routes />
|
<Routes />
|
||||||
{isLoggedIn && <Player />}
|
{isLoggedIn && <Player />}
|
||||||
</BoxView>
|
</View>
|
||||||
</playerContext.Provider>
|
</playerContext.Provider>
|
||||||
</authContext.Provider>
|
</authContext.Provider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useContext, useEffect, useState } from "react";
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
import { Button, Text, View, ScrollArea } from "@nodegui/react-nodegui";
|
import { Button, View, ScrollArea } from "@nodegui/react-nodegui";
|
||||||
import playerContext from "../context/playerContext";
|
import playerContext from "../context/playerContext";
|
||||||
import authContext from "../context/authContext";
|
import authContext from "../context/authContext";
|
||||||
import { useHistory } from "react-router";
|
import { useHistory } from "react-router";
|
||||||
@ -7,7 +7,7 @@ import CachedImage from "./shared/CachedImage";
|
|||||||
import { CursorShape } from "@nodegui/nodegui";
|
import { CursorShape } from "@nodegui/nodegui";
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
const { spotifyApi, currentPlaylist, currentTrack } = useContext(playerContext);
|
const { spotifyApi } = useContext(playerContext);
|
||||||
const { isLoggedIn, access_token } = useContext(authContext);
|
const { isLoggedIn, access_token } = useContext(authContext);
|
||||||
const [categories, setCategories] = useState<SpotifyApi.CategoryObject[]>([]);
|
const [categories, setCategories] = useState<SpotifyApi.CategoryObject[]>([]);
|
||||||
|
|
||||||
@ -26,8 +26,8 @@ function Home() {
|
|||||||
}, [access_token]);
|
}, [access_token]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollArea style={`flex-grow: 1; border: none;`}>
|
<ScrollArea style={`flex-grow: 1; border: none; flex: 1;`}>
|
||||||
<View style={`flex-direction: 'column'; justify-content: 'center'; align-items: 'stretch';`}>
|
<View style={`flex-direction: 'column'; justify-content: 'center'; flex: 1;`}>
|
||||||
<CategoryCard key={((Math.random() * Date.now()) / Math.random()) * 100} id="current" name="Currently Playing" />
|
<CategoryCard key={((Math.random() * Date.now()) / Math.random()) * 100} id="current" name="Currently Playing" />
|
||||||
{isLoggedIn &&
|
{isLoggedIn &&
|
||||||
categories.map(({ id, name }, index) => {
|
categories.map(({ id, name }, index) => {
|
||||||
@ -75,6 +75,7 @@ function CategoryCard({ id, name }: CategoryCardProps) {
|
|||||||
|
|
||||||
const categoryStylesheet = `
|
const categoryStylesheet = `
|
||||||
#container{
|
#container{
|
||||||
|
flex: 1;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: 'center';
|
justify-content: 'center';
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@ -94,7 +95,10 @@ function CategoryCard({ id, name }: CategoryCardProps) {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
#child-view{
|
#child-view{
|
||||||
|
flex: 1;
|
||||||
justify-content: 'space-evenly';
|
justify-content: 'space-evenly';
|
||||||
|
align-items: 'center';
|
||||||
|
flex-wrap: 'wrap';
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ export const audioPlayer = new NodeMpv(
|
|||||||
|
|
||||||
function Player(): ReactElement {
|
function Player(): ReactElement {
|
||||||
const { currentTrack, currentPlaylist, setCurrentTrack, setCurrentPlaylist } = useContext(playerContext);
|
const { currentTrack, currentPlaylist, setCurrentTrack, setCurrentPlaylist } = useContext(playerContext);
|
||||||
const [volume, setVolume] = useState(55);
|
const [volume, setVolume] = useState<number>(parseFloat(localStorage.getItem("volume") ?? "55"));
|
||||||
const [totalDuration, setTotalDuration] = useState(0);
|
const [totalDuration, setTotalDuration] = useState(0);
|
||||||
const [shuffle, setShuffle] = useState<boolean>(false);
|
const [shuffle, setShuffle] = useState<boolean>(false);
|
||||||
const [realPlaylist, setRealPlaylist] = useState<CurrentPlaylist["tracks"]>([]);
|
const [realPlaylist, setRealPlaylist] = useState<CurrentPlaylist["tracks"]>([]);
|
||||||
@ -34,6 +34,9 @@ function Player(): ReactElement {
|
|||||||
sliderMoved: (value) => {
|
sliderMoved: (value) => {
|
||||||
setVolume(value);
|
setVolume(value);
|
||||||
},
|
},
|
||||||
|
sliderReleased: () => {
|
||||||
|
localStorage.setItem("volume", volume.toString());
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@ -47,10 +50,8 @@ function Player(): ReactElement {
|
|||||||
if (!playerRunning) {
|
if (!playerRunning) {
|
||||||
await audioPlayer.start();
|
await audioPlayer.start();
|
||||||
}
|
}
|
||||||
await audioPlayer.volume(55);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to start audio player");
|
console.error("Failed to start audio player", error);
|
||||||
console.error(error);
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@ -157,9 +158,9 @@ function Player(): ReactElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const artistsNames = currentTrack?.artists.map((x) => x.name);
|
const artistsNames = currentTrack?.artists?.map((x) => x.name);
|
||||||
return (
|
return (
|
||||||
<GridView style="flex: 1; max-height: 100px;">
|
<GridView enabled={!!currentTrack} style="flex: 1; max-height: 100px;">
|
||||||
<GridRow>
|
<GridRow>
|
||||||
<GridColumn width={2}>
|
<GridColumn width={2}>
|
||||||
<Text ref={titleRef} wordWrap>
|
<Text ref={titleRef} wordWrap>
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import React, { FC, useContext, useEffect, useState } from "react";
|
import React, { FC, useContext, useEffect, useState } from "react";
|
||||||
import { BoxView, Button, ScrollArea, Text, View } from "@nodegui/react-nodegui";
|
import { BoxView, Button, GridView, ScrollArea, Text, View } 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 } from "@nodegui/nodegui";
|
import { Direction, QAbstractButtonSignals, QIcon } from "@nodegui/nodegui";
|
||||||
import { WidgetEventListeners } from "@nodegui/react-nodegui/dist/components/View/RNView";
|
import { WidgetEventListeners } from "@nodegui/react-nodegui/dist/components/View/RNView";
|
||||||
import authContext from "../context/authContext";
|
import authContext from "../context/authContext";
|
||||||
import playerContext from "../context/playerContext";
|
import playerContext from "../context/playerContext";
|
||||||
|
import IconButton from "./shared/IconButton";
|
||||||
|
import { heartRegular, play, stop } from "../icons";
|
||||||
|
import { audioPlayer } from "./Player";
|
||||||
|
|
||||||
export interface PlaylistTrackRes {
|
export interface PlaylistTrackRes {
|
||||||
name: string;
|
name: string;
|
||||||
@ -21,20 +24,9 @@ const PlaylistView: FC<PlaylistViewProps> = () => {
|
|||||||
const { isLoggedIn, access_token } = useContext(authContext);
|
const { isLoggedIn, access_token } = useContext(authContext);
|
||||||
const { spotifyApi, setCurrentTrack, currentPlaylist, currentTrack, setCurrentPlaylist } = useContext(playerContext);
|
const { spotifyApi, setCurrentTrack, currentPlaylist, currentTrack, setCurrentPlaylist } = useContext(playerContext);
|
||||||
const params = useParams<{ id: string }>();
|
const params = useParams<{ id: string }>();
|
||||||
const location = useLocation<{ name: string, thumbnail: string }>();
|
const location = useLocation<{ name: string; thumbnail: string }>();
|
||||||
const [tracks, setTracks] = useState<SpotifyApi.PlaylistTrackObject[]>([]);
|
const [tracks, setTracks] = useState<SpotifyApi.PlaylistTrackObject[]>([]);
|
||||||
|
|
||||||
const trackClickHandler = async (track: SpotifyApi.TrackObjectFull) => {
|
|
||||||
try {
|
|
||||||
setCurrentTrack(track);
|
|
||||||
if (currentPlaylist?.id !== params.id) {
|
|
||||||
setCurrentPlaylist({...params, ...location.state, tracks});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to resolve track's youtube url: ", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
(async () => {
|
(async () => {
|
||||||
@ -49,9 +41,34 @@ const PlaylistView: FC<PlaylistViewProps> = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handlePlaylistPlayPause = () => {
|
||||||
|
if (currentPlaylist?.id !== params.id) {
|
||||||
|
setCurrentPlaylist({ ...params, ...location.state, tracks });
|
||||||
|
setCurrentTrack(tracks[0].track);
|
||||||
|
} else {
|
||||||
|
audioPlayer.stop().catch((error) => console.error("Failed to stop audio player: ", error));
|
||||||
|
setCurrentTrack(undefined);
|
||||||
|
setCurrentPlaylist(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const trackClickHandler = async (track: SpotifyApi.TrackObjectFull) => {
|
||||||
|
try {
|
||||||
|
setCurrentTrack(track);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to resolve track's youtube url: ", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BoxView direction={Direction.TopToBottom}>
|
<View style={`flex-direction: 'column'; flex-grow: 1;`}>
|
||||||
<BackButton />
|
<View style={`justify-content: 'space-between'; padding-bottom: 10px; padding-left: 10px;`}>
|
||||||
|
<BackButton />
|
||||||
|
<View style={`height: 50px; justify-content: 'space-between'; width: 100px; padding-right: 20px;`}>
|
||||||
|
<IconButton icon={new QIcon(heartRegular)} />
|
||||||
|
<IconButton style={`background-color: #00be5f; color: white;`} on={{ clicked: handlePlaylistPlayPause }} icon={new QIcon(currentPlaylist?.id === params.id ? stop : play)} />
|
||||||
|
</View>
|
||||||
|
</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={`flex-grow: 1; border: none;`}>
|
||||||
<View style={`flex-direction:column;`}>
|
<View style={`flex-direction:column;`}>
|
||||||
@ -70,7 +87,7 @@ const PlaylistView: FC<PlaylistViewProps> = () => {
|
|||||||
})}
|
})}
|
||||||
</View>
|
</View>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</BoxView>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user