import React, { useState, useEffect, useRef } from "react"; import { Window, hot, View } from "@nodegui/react-nodegui"; import { QIcon, QMainWindow, WidgetEventTypes, WindowState, QShortcut, QKeySequence } from "@nodegui/nodegui"; import { MemoryRouter } from "react-router"; import Routes from "./routes"; import { LocalStorage } from "node-localstorage"; import authContext from "./context/authContext"; import playerContext, { CurrentPlaylist, CurrentTrack } from "./context/playerContext"; import Player, { audioPlayer } from "./components/Player"; import { QueryClient, QueryClientProvider } from "react-query"; import express from "express"; import open from "open"; import spotifyApi from "./initializations/spotifyApi"; import showError from "./helpers/showError"; import fs from "fs"; import path from "path"; import { confDir, LocalStorageKeys } from "./conf"; import spotubeIcon from "../assets/icon.svg"; import preferencesContext, { PreferencesContextProperties } from "./context/preferencesContext"; export interface Credentials { clientId: string; clientSecret: string; } const minSize = { width: 700, height: 750 }; const winIcon = new QIcon(spotubeIcon); const localStorageDir = path.join(confDir, "local"); fs.mkdirSync(localStorageDir, { recursive: true }); global.localStorage = new LocalStorage(localStorageDir); const queryClient = new QueryClient({ defaultOptions: { queries: { onError(error) { showError(error); }, }, }, }); const initialPreferences: PreferencesContextProperties = { playlistImages: false, }; const initialCredentials: Credentials = { clientId: "", clientSecret: "" }; //* Application start function RootApp() { const windowRef = useRef(); const [currentTrack, setCurrentTrack] = useState(); // cache const cachedPreferences = localStorage.getItem(LocalStorageKeys.preferences); const cachedCredentials = localStorage.getItem(LocalStorageKeys.credentials); // state const [isLoggedIn, setIsLoggedIn] = useState(false); const [credentials, setCredentials] = useState(() => { if (cachedCredentials) { return JSON.parse(cachedCredentials); } return initialCredentials; }); const [preferences, setPreferences] = useState(() => { if (cachedPreferences) { return JSON.parse(cachedPreferences); } return initialPreferences; }); const [access_token, setAccess_token] = useState(""); const [currentPlaylist, setCurrentPlaylist] = useState(); useEffect(() => { const parsedCredentials: Credentials = JSON.parse(cachedCredentials ?? "{}"); setIsLoggedIn(!!(parsedCredentials.clientId && parsedCredentials.clientSecret)); }, []); // for user code login useEffect(() => { // saving changed credentials to storage localStorage.setItem(LocalStorageKeys.credentials, JSON.stringify(credentials)); if (credentials.clientId && credentials.clientSecret && !localStorage.getItem(LocalStorageKeys.refresh_token)) { const app = express(); app.use(express.json()); app.get("/auth/spotify/callback", async (req, res) => { try { spotifyApi.setClientId(credentials.clientId); spotifyApi.setClientSecret(credentials.clientSecret); const { body: authRes } = await spotifyApi.authorizationCodeGrant(req.query.code); setAccess_token(authRes.access_token); localStorage.setItem(LocalStorageKeys.refresh_token, authRes.refresh_token); setIsLoggedIn(true); return res.end(); } catch (error) { console.error("Failed to fullfil code grant flow: ", error); } }); const server = app.listen(4304, () => { 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) => console.error("Opening IPC connection with browser failed: ", e) ); }); return () => { server.close(() => console.log("Closed server")); }; } }, [credentials]); // just saves the preferences useEffect(() => { localStorage.setItem(LocalStorageKeys.preferences, JSON.stringify(preferences)); }, [preferences]); // window event listeners useEffect(() => { const onWindowClose = () => { if (audioPlayer.isRunning()) { audioPlayer.stop().catch((e) => console.error("Failed to quit MPV player: ", e)); } }; windowRef.current?.addEventListener(WidgetEventTypes.Close, onWindowClose); return () => { windowRef.current?.removeEventListener(WidgetEventTypes.Close, onWindowClose); }; }); let spaceShortcut: QShortcut | null; let rightShortcut: QShortcut | null; let leftShortcut: QShortcut | null; // short cut effect useEffect(() => { if (windowRef.current) { spaceShortcut = new QShortcut(windowRef.current); rightShortcut = new QShortcut(windowRef.current); leftShortcut = new QShortcut(windowRef.current); spaceShortcut.setKey(new QKeySequence("SPACE")); rightShortcut.setKey(new QKeySequence("RIGHT")); leftShortcut.setKey(new QKeySequence("LEFT")); async function spaceAction() { try { currentTrack && audioPlayer.isRunning() && (await audioPlayer.isPaused()) ? await audioPlayer.play() : await audioPlayer.pause(); console.log("You pressed SPACE"); } catch (error) { showError(error, "[Failed to play/pause audioPlayer]: "); } } async function rightAction() { try { currentTrack && audioPlayer.isRunning() && (await audioPlayer.isSeekable()) && (await audioPlayer.seek(+5)); console.log("You pressed RIGHT"); } catch (error) { showError(error, "[Failed to seek audioPlayer]: "); } } async function leftAction() { try { currentTrack && audioPlayer.isRunning() && (await audioPlayer.isSeekable()) && (await audioPlayer.seek(-5)); console.log("You pressed LEFT"); } catch (error) { showError(error, "[Failed to seek audioPlayer]: "); } } spaceShortcut.addEventListener("activated", spaceAction); rightShortcut.addEventListener("activated", rightAction); leftShortcut.addEventListener("activated", leftAction); return () => { spaceShortcut?.removeEventListener("activated", spaceAction); rightShortcut?.removeEventListener("activated", rightAction); leftShortcut?.removeEventListener("activated", leftAction); spaceShortcut = null; rightShortcut = null; leftShortcut = null; }; } }); return ( {isLoggedIn && } ); } class App extends React.Component { render() { return ; } } export default hot(App);