-
-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(providers): add WebNowPlaying provider and integrate Jotai store
- Loading branch information
Showing
12 changed files
with
222 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { ReactNode } from "@tanstack/react-router"; | ||
import { useEffect, useState } from "react"; | ||
|
||
export default function ClientOnly({ children }: { children: ReactNode }) { | ||
const [hasMounted, setHasMounted] = useState(false); | ||
|
||
useEffect(() => { | ||
setHasMounted(true); | ||
}, []); | ||
|
||
if (!hasMounted) { | ||
return null; | ||
} | ||
|
||
return <>{children}</>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import ClientOnly from "@/components/ClientOnly"; | ||
import { usePlayerProviders } from "@/components/contexts/PlayerProviders"; | ||
import { useMemo } from "react"; | ||
|
||
export default function ProvidersBtns() { | ||
const { providers, lastUsedProvider } = usePlayerProviders(); | ||
|
||
const { defaultProvider, otherProviders } = useMemo(() => { | ||
const defaultProviderId = | ||
lastUsedProvider ? lastUsedProvider.id : "spotify"; | ||
|
||
const defaultProvider = Object.values(providers).find( | ||
(provider) => provider.meta.id === defaultProviderId | ||
)!; | ||
const otherProviders = Object.values(providers).filter( | ||
(provider) => provider.meta.id !== defaultProviderId | ||
); | ||
|
||
return { | ||
defaultProvider, | ||
otherProviders, | ||
}; | ||
}, [providers]); | ||
|
||
return ( | ||
<ClientOnly> | ||
<div className="flex flex-col gap-4"> | ||
<button | ||
onClick={() => defaultProvider.authenticate()} | ||
className="bg-[#15883D] px-12 py-3 rounded-full text-lg tracking-wide active:scale-95 transition mx-auto" | ||
> | ||
Login with {defaultProvider.meta.name} | ||
</button> | ||
|
||
{Object.entries(otherProviders).map(([id, provider]) => { | ||
return ( | ||
<button | ||
key={id} | ||
onClick={() => provider.authenticate()} | ||
className="border-b-2 text-white/70 hover:text-white border-white/50 hover:border-white/70 text-lg tracking-wide active:scale-95 transition mx-auto" | ||
> | ||
{provider.meta.name} | ||
</button> | ||
); | ||
})} | ||
</div> | ||
</ClientOnly> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import { | ||
IProviderClient, | ||
IProviderClientConstructor, | ||
} from "@/types/providers/client"; | ||
import { PlayerState } from "@/types/player"; | ||
import webnowplayingProviderMeta from "@/providers/webnowplaying"; | ||
|
||
export default class WebNowPlayingProvider implements IProviderClient { | ||
readonly meta = webnowplayingProviderMeta; | ||
isAuthenticated = false; | ||
|
||
// API event handlers | ||
private onAuth: () => void; | ||
private onUnregister: () => void; | ||
private sendPlayerState: (playerObj: PlayerState) => void; | ||
|
||
constructor({ | ||
onAuth, | ||
onUnregister, | ||
sendPlayerState, | ||
}: IProviderClientConstructor) { | ||
this.onAuth = onAuth; | ||
this.onUnregister = onUnregister; | ||
this.sendPlayerState = sendPlayerState; | ||
} | ||
|
||
// Private properties and methods | ||
private _lastPlaybackState: PlayerState | null = null; | ||
|
||
private _handleMessage = (msg: MessageEvent) => { | ||
if (msg.data.type != "wnp-info" || !msg.data.player) return; | ||
|
||
this._lastPlaybackState = msg.data.player; | ||
}; | ||
private _beginListening() { | ||
window.addEventListener("message", this._handleMessage); | ||
} | ||
private _endListening() { | ||
window.postMessage( | ||
{ | ||
type: "wnp-info", | ||
subscribe: false, | ||
}, | ||
"*" | ||
); | ||
window.removeEventListener("message", this._handleMessage); | ||
this.isAuthenticated = false; | ||
} | ||
|
||
// Public implemented methods | ||
|
||
async authenticate() { | ||
window.postMessage( | ||
{ | ||
type: "wnp-info", | ||
subscribe: true, | ||
}, | ||
"*" | ||
); | ||
this.isAuthenticated = true; | ||
|
||
this.onAuth(); | ||
} | ||
|
||
async callback() { | ||
await this.authenticate(); | ||
} | ||
|
||
async registerPlayer() { | ||
this._beginListening(); | ||
} | ||
|
||
async unregisterPlayer() { | ||
this._endListening(); | ||
|
||
this.onUnregister(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import type { ProviderMeta } from "@/types/providers/meta"; | ||
|
||
const webnowplayingProviderMeta: ProviderMeta = { | ||
name: "WebNowPlaying", | ||
id: "webnowplaying", | ||
}; | ||
|
||
export default webnowplayingProviderMeta; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,28 @@ | ||
import { PlayerState } from "@/types/player"; | ||
import { IProviderClient } from "@/types/providers/client"; | ||
import { atom } from "jotai"; | ||
import { atomWithStorage } from "jotai/utils"; | ||
|
||
type LastUsedProvider = { | ||
id: string; | ||
date: number; | ||
} | null; | ||
|
||
export const providersAtom = atom<Record<string, IProviderClient>>({}); | ||
export const activePlayerAtom = atom<null | string>(null); | ||
export const lastUsedProviderAtom = atomWithStorage<LastUsedProvider>( | ||
"lastUsedProvider", | ||
null | ||
); | ||
|
||
export const activeProviderAtom = atom((get) => { | ||
const activePlayer = get(activePlayerAtom); | ||
const providers = get(providersAtom); | ||
|
||
if (!activePlayer) return null; | ||
if (!providers[activePlayer]) return null; | ||
|
||
return providers[activePlayer]; | ||
}); | ||
|
||
export const playerStateAtom = atom<PlayerState>(null); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { createStore, Provider } from "jotai"; | ||
import { ReactNode } from "react"; | ||
|
||
export const store = createStore(); | ||
|
||
export function JotaiStoreProvider({ | ||
children, | ||
}: Readonly<{ children: ReactNode }>) { | ||
return <Provider store={store}>{children}</Provider>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters