aboutsummaryrefslogtreecommitdiff
path: root/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/spotify.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/spotify.jsx')
-rwxr-xr-xusers/ryan/modules/simple-bar/simple-bar-source/lib/components/data/spotify.jsx189
1 files changed, 189 insertions, 0 deletions
diff --git a/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/spotify.jsx b/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/spotify.jsx
new file mode 100755
index 0000000..bc9f6e6
--- /dev/null
+++ b/users/ryan/modules/simple-bar/simple-bar-source/lib/components/data/spotify.jsx
@@ -0,0 +1,189 @@
1import * as Uebersicht from "uebersicht";
2import * as DataWidget from "./data-widget.jsx";
3import * as DataWidgetLoader from "./data-widget-loader.jsx";
4import useWidgetRefresh from "../../hooks/use-widget-refresh";
5import useServerSocket from "../../hooks/use-server-socket";
6import { useSimpleBarContext } from "../simple-bar-context.jsx";
7import * as Icons from "../icons/icons.jsx";
8import * as Utils from "../../utils";
9
10export { spotifyStyles as styles } from "../../styles/components/data/spotify";
11
12const { React } = Uebersicht;
13
14const DEFAULT_REFRESH_FREQUENCY = 10000;
15
16/**
17 * Spotify widget component.
18 * @returns {JSX.Element|null} The Spotify widget.
19 */
20export const Widget = React.memo(() => {
21 const { displayIndex, settings } = useSimpleBarContext();
22 const { widgets, spotifyWidgetOptions } = settings;
23 const { spotifyWidget } = widgets;
24 const {
25 refreshFrequency,
26 showSpecter,
27 showOnDisplay,
28 showIcon,
29 showSpotifyMetadata,
30 } = spotifyWidgetOptions;
31
32 // Determine the refresh frequency for the widget
33 const refresh = React.useMemo(
34 () =>
35 Utils.getRefreshFrequency(refreshFrequency, DEFAULT_REFRESH_FREQUENCY),
36 [refreshFrequency],
37 );
38
39 // Determine if the widget should be visible
40 const visible =
41 Utils.isVisibleOnDisplay(displayIndex, showOnDisplay) && spotifyWidget;
42
43 const [state, setState] = React.useState();
44 const [loading, setLoading] = React.useState(visible);
45 const [isSpotifyActive, setIsSpotifyActive] = React.useState(false);
46
47 /**
48 * Resets the widget state.
49 */
50 const resetWidget = () => {
51 setState(undefined);
52 setLoading(false);
53 setIsSpotifyActive(false);
54 };
55
56 /**
57 * Fetches the current Spotify state.
58 */
59 const getSpotify = React.useCallback(async () => {
60 if (!visible) return;
61 const isRunning = await Utils.cachedRun(
62 `pgrep -fq '[S]potify Helper' && echo "true" || echo "false"`,
63 refresh,
64 );
65 if (Utils.cleanupOutput(isRunning) === "false") {
66 setLoading(false);
67 setIsSpotifyActive(false);
68 setState({
69 playerState: "",
70 trackName: "",
71 artistName: "",
72 });
73 return;
74 }
75 // if showSpotifyMetadata is enabled, retrieves all information
76 if (showSpotifyMetadata) {
77 const output = await Utils.cachedRun(
78 `osascript -e 'tell application "Spotify"' -e 'set output to (player state as string) & "|" & (name of current track as string) & "|" & (artist of current track as string)' -e 'end tell' 2>/dev/null || echo "stopped||"`,
79 refresh,
80 );
81 const [playerState, trackName, artistName] =
82 Utils.cleanupOutput(output).split("|");
83 setState({
84 playerState,
85 trackName,
86 artistName,
87 });
88 // else, only playerState
89 } else {
90 const playerState = await Utils.cachedRun(
91 `osascript -e 'tell application "Spotify" to player state as string' 2>/dev/null || echo "stopped"`,
92 refresh,
93 );
94 setState({
95 playerState: Utils.cleanupOutput(playerState),
96 trackName: "",
97 artistName: "",
98 });
99 }
100
101 setIsSpotifyActive(true);
102 setLoading(false);
103 }, [visible, showSpotifyMetadata, refresh]);
104
105 // Set up server socket and widget refresh hooks
106 useServerSocket("spotify", visible, getSpotify, resetWidget, setLoading);
107 useWidgetRefresh(visible, getSpotify, refresh);
108
109 if (loading) return <DataWidgetLoader.Widget className="spotify" />;
110 if (!state || !isSpotifyActive) return null;
111 const { playerState, trackName, artistName } = state;
112
113 if (!trackName.length && showSpotifyMetadata) return null;
114
115 const label = artistName.length ? `${trackName} - ${artistName}` : trackName;
116 const isPlaying = playerState === "playing";
117 const Icon = getIcon(playerState);
118
119 /**
120 * Handles click event to toggle play/pause.
121 * @param {React.MouseEvent} e - The click event.
122 */
123 const onClick = (e) => {
124 Utils.clickEffect(e);
125 togglePlay(!isPlaying);
126 getSpotify();
127 };
128
129 /**
130 * Handles right-click event to skip to the next track.
131 * @param {React.MouseEvent} e - The right-click event.
132 */
133 const onRightClick = (e) => {
134 Utils.clickEffect(e);
135 Uebersicht.run(`osascript -e 'tell application "Spotify" to Next Track'`);
136 getSpotify();
137 };
138
139 /**
140 * Handles middle-click event to open Spotify.
141 * @param {React.MouseEvent} e - The middle-click event.
142 */
143 const onMiddleClick = (e) => {
144 Utils.clickEffect(e);
145 Uebersicht.run(`open -a 'Spotify'`);
146 getSpotify();
147 };
148
149 const classes = Utils.classNames("spotify", {
150 "spotify--hidden-metadata": !showSpotifyMetadata,
151 "spotify--playing": isPlaying,
152 });
153
154 return (
155 <DataWidget.Widget
156 classes={classes}
157 Icon={showIcon ? Icon : null}
158 onClick={onClick}
159 onRightClick={onRightClick}
160 onMiddleClick={onMiddleClick}
161 showSpecter={showSpecter && isPlaying}
162 disableSlider={!showSpotifyMetadata}
163 >
164 {showSpotifyMetadata && label}
165 </DataWidget.Widget>
166 );
167});
168
169Widget.displayName = "Spotify";
170
171/**
172 * Toggles play/pause state of Spotify.
173 * @param {boolean} isPaused - Whether the player is paused.
174 */
175function togglePlay(isPaused) {
176 const state = isPaused ? "play" : "pause";
177 Uebersicht.run(`osascript -e 'tell application "Spotify" to ${state}'`);
178}
179
180/**
181 * Gets the appropriate icon based on the player state.
182 * @param {string} playerState - The current state of the player.
183 * @returns {JSX.Element} The icon component.
184 */
185function getIcon(playerState) {
186 if (playerState === "stopped") return Icons.Stopped;
187 if (playerState === "playing") return Icons.Playing;
188 return Icons.Paused;
189}