From 9b948728d6e9d960e42ab65a331bda02f4dde9c7 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Sat, 9 Aug 2025 14:20:07 -0400 Subject: [PATCH] Merge Controls & Lyrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MusicControls: Styles SpotifyControls: 👋 --- .../musicControls}/hoverOnly.css | 11 +- .../musicControls}/index.tsx | 69 ++-- .../musicControls/settings.tsx | 187 +++++++++ .../spotify}/PlayerComponent.tsx | 4 +- .../musicControls/spotify}/SeekBar.ts | 0 .../musicControls/spotify}/SpotifyStore.ts | 2 +- .../spotify/lyrics}/api.tsx | 5 +- .../spotify/lyrics}/components/ctxMenu.tsx | 0 .../spotify/lyrics}/components/lyrics.tsx | 6 +- .../spotify/lyrics}/components/modal.tsx | 2 +- .../spotify/lyrics}/components/util.tsx | 5 +- .../lyrics}/providers/SpotifyAPI/index.ts | 0 .../lyrics}/providers/lrclibAPI/index.ts | 3 +- .../spotify/lyrics}/providers/store.ts | 4 +- .../lyrics}/providers/translator/index.ts | 2 +- .../lyrics}/providers/translator/languages.ts | 0 .../spotify/lyrics}/providers/types.ts | 0 .../musicControls/spotify}/spotifyStyles.css | 0 .../{tidalLyrics => musicControls}/styles.css | 85 +++- .../tidal}/TidalPlayer.tsx | 8 +- .../tidal}/TidalStore.ts | 0 .../tidal/lyrics}/api.tsx | 16 +- .../tidal/lyrics}/components/ctxMenu.tsx | 0 .../tidal/lyrics}/components/lyrics.tsx | 6 +- .../tidal/lyrics}/components/modal.tsx | 6 +- .../tidal/lyrics}/components/util.tsx | 4 +- .../tidal/lyrics}/providers/store.ts | 4 +- .../tidal/lyrics}/types.ts | 0 .../tidal}/tidalStyles.css | 0 src/equicordplugins/spotifyLyrics/index.tsx | 61 --- .../spotifyLyrics/settings.tsx | 145 ------- src/equicordplugins/spotifyLyrics/styles.css | 77 ---- .../tidalControls/hoverOnly.css | 16 - src/equicordplugins/tidalControls/index.tsx | 136 ------- src/equicordplugins/tidalLyrics/index.tsx | 69 ---- src/equicordplugins/tidalLyrics/settings.tsx | 91 ----- .../youtubeMusicControls/PlayerComponent.tsx | 369 ------------------ .../youtubeMusicControls/README.md | 31 -- .../youtubeMusicControls/YtmStore.ts | 240 ------------ .../youtubeMusicControls/hoverOnly.css | 16 - .../youtubeMusicControls/index.tsx | 170 -------- .../youtubeMusicControls/ytmStyles.css | 208 ---------- 42 files changed, 346 insertions(+), 1712 deletions(-) rename src/{plugins/spotifyControls => equicordplugins/musicControls}/hoverOnly.css (66%) rename src/{plugins/spotifyControls => equicordplugins/musicControls}/index.tsx (63%) create mode 100644 src/equicordplugins/musicControls/settings.tsx rename src/{plugins/spotifyControls => equicordplugins/musicControls/spotify}/PlayerComponent.tsx (98%) rename src/{plugins/spotifyControls => equicordplugins/musicControls/spotify}/SeekBar.ts (100%) rename src/{plugins/spotifyControls => equicordplugins/musicControls/spotify}/SpotifyStore.ts (97%) rename src/equicordplugins/{spotifyLyrics => musicControls/spotify/lyrics}/api.tsx (97%) rename src/equicordplugins/{spotifyLyrics => musicControls/spotify/lyrics}/components/ctxMenu.tsx (100%) rename src/equicordplugins/{spotifyLyrics => musicControls/spotify/lyrics}/components/lyrics.tsx (94%) rename src/equicordplugins/{spotifyLyrics => musicControls/spotify/lyrics}/components/modal.tsx (97%) rename src/equicordplugins/{spotifyLyrics => musicControls/spotify/lyrics}/components/util.tsx (96%) rename src/equicordplugins/{spotifyLyrics => musicControls/spotify/lyrics}/providers/SpotifyAPI/index.ts (100%) rename src/equicordplugins/{spotifyLyrics => musicControls/spotify/lyrics}/providers/lrclibAPI/index.ts (96%) rename src/equicordplugins/{spotifyLyrics => musicControls/spotify/lyrics}/providers/store.ts (97%) rename src/equicordplugins/{spotifyLyrics => musicControls/spotify/lyrics}/providers/translator/index.ts (98%) rename src/equicordplugins/{spotifyLyrics => musicControls/spotify/lyrics}/providers/translator/languages.ts (100%) rename src/equicordplugins/{spotifyLyrics => musicControls/spotify/lyrics}/providers/types.ts (100%) rename src/{plugins/spotifyControls => equicordplugins/musicControls/spotify}/spotifyStyles.css (100%) rename src/equicordplugins/{tidalLyrics => musicControls}/styles.css (72%) rename src/equicordplugins/{tidalControls => musicControls/tidal}/TidalPlayer.tsx (97%) rename src/equicordplugins/{tidalControls => musicControls/tidal}/TidalStore.ts (100%) rename src/equicordplugins/{tidalLyrics => musicControls/tidal/lyrics}/api.tsx (69%) rename src/equicordplugins/{tidalLyrics => musicControls/tidal/lyrics}/components/ctxMenu.tsx (100%) rename src/equicordplugins/{tidalLyrics => musicControls/tidal/lyrics}/components/lyrics.tsx (97%) rename src/equicordplugins/{tidalLyrics => musicControls/tidal/lyrics}/components/modal.tsx (96%) rename src/equicordplugins/{tidalLyrics => musicControls/tidal/lyrics}/components/util.tsx (96%) rename src/equicordplugins/{tidalLyrics => musicControls/tidal/lyrics}/providers/store.ts (93%) rename src/equicordplugins/{tidalLyrics => musicControls/tidal/lyrics}/types.ts (100%) rename src/equicordplugins/{tidalControls => musicControls/tidal}/tidalStyles.css (100%) delete mode 100644 src/equicordplugins/spotifyLyrics/index.tsx delete mode 100644 src/equicordplugins/spotifyLyrics/settings.tsx delete mode 100644 src/equicordplugins/spotifyLyrics/styles.css delete mode 100644 src/equicordplugins/tidalControls/hoverOnly.css delete mode 100644 src/equicordplugins/tidalControls/index.tsx delete mode 100644 src/equicordplugins/tidalLyrics/index.tsx delete mode 100644 src/equicordplugins/tidalLyrics/settings.tsx delete mode 100644 src/equicordplugins/youtubeMusicControls/PlayerComponent.tsx delete mode 100644 src/equicordplugins/youtubeMusicControls/README.md delete mode 100644 src/equicordplugins/youtubeMusicControls/YtmStore.ts delete mode 100644 src/equicordplugins/youtubeMusicControls/hoverOnly.css delete mode 100644 src/equicordplugins/youtubeMusicControls/index.tsx delete mode 100644 src/equicordplugins/youtubeMusicControls/ytmStyles.css diff --git a/src/plugins/spotifyControls/hoverOnly.css b/src/equicordplugins/musicControls/hoverOnly.css similarity index 66% rename from src/plugins/spotifyControls/hoverOnly.css rename to src/equicordplugins/musicControls/hoverOnly.css index 8051cc42c..9df7594ae 100644 --- a/src/plugins/spotifyControls/hoverOnly.css +++ b/src/equicordplugins/musicControls/hoverOnly.css @@ -6,6 +6,15 @@ transition-property: height; } +.eq-tdl-button-row { + height: 0; + opacity: 0; + pointer-events: none; + transition: 0.2s; + transition-property: height; +} + +#eq-tdl-player:hover .eq-tdl-button-row, #vc-spotify-player:hover .vc-spotify-button-row { opacity: 1; height: 32px; @@ -13,4 +22,4 @@ /* only transition opacity on show to prevent clipping */ transition-property: height, opacity; -} +} \ No newline at end of file diff --git a/src/plugins/spotifyControls/index.tsx b/src/equicordplugins/musicControls/index.tsx similarity index 63% rename from src/plugins/spotifyControls/index.tsx rename to src/equicordplugins/musicControls/index.tsx index add54025e..262ce4c62 100644 --- a/src/plugins/spotifyControls/index.tsx +++ b/src/equicordplugins/musicControls/index.tsx @@ -16,51 +16,34 @@ * along with this program. If not, see . */ +import "./styles.css"; + import { Settings } from "@api/Settings"; -import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; -import { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; +import { Devs, EquicordDevs } from "@utils/constants"; +import definePlugin from "@utils/types"; -import hoverOnlyStyle from "./hoverOnly.css?managed"; -import { Player } from "./PlayerComponent"; - -function toggleHoverControls(value: boolean) { - (value ? enableStyle : disableStyle)(hoverOnlyStyle); -} +import { settings, toggleHoverControls } from "./settings"; +import { migrateOldLyrics } from "./spotify/lyrics/api"; +import { SpotifyLyrics } from "./spotify/lyrics/components/lyrics"; +import { SpotifyPlayer } from "./spotify/PlayerComponent"; +import { TidalLyrics } from "./tidal/lyrics/components/lyrics"; +import { TidalPlayer } from "./tidal/TidalPlayer"; export default definePlugin({ - name: "SpotifyControls", - description: "Adds a Spotify player above the account panel", - authors: [Devs.Ven, Devs.afn, Devs.KraXen72, Devs.Av32000, Devs.nin0dev], - options: { - hoverControls: { - description: "Show controls on hover", - type: OptionType.BOOLEAN, - default: false, - onChange: v => toggleHoverControls(v) - }, - useSpotifyUris: { - type: OptionType.BOOLEAN, - description: "Open Spotify URIs instead of Spotify URLs. Will only work if you have Spotify installed and might not work on all platforms", - default: false - }, - previousButtonRestartsTrack: { - type: OptionType.BOOLEAN, - description: "Restart currently playing track when pressing the previous button if playtime is >3s", - default: true - } - }, + name: "MusicControls", + description: "Music Controls and Lyrics for multiple services ", + authors: [EquicordDevs.thororen, EquicordDevs.vmohammad, Devs.Joona], + settings, patches: [ { - // HEY THOR UPDATE YOUTUBE MUSIC CONTROLS IF THIS SHIT CHANGES TY :) find: "this.isCopiedStreakGodlike", replacement: { // react.jsx)(AccountPanel, { ..., showTaglessAccountPanel: blah }) match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?userTag:\i,hidePrivateData:)/, // react.jsx(WrapperComponent, { VencordOriginal: AccountPanel, ... replace: "$self.PanelWrapper,{VencordOriginal:$1," - } + }, }, { find: ".PLAYER_DEVICES", @@ -92,24 +75,34 @@ export default definePlugin({ }, ], - start: () => toggleHoverControls(Settings.plugins.SpotifyControls.hoverControls), PanelWrapper({ VencordOriginal, ...props }) { + const { showTidalControls, showTidalLyrics, showSpotifyLyrics, showSpotifyControls, LyricsPosition } = settings.store; return ( <> ( -
-

Failed to render Spotify Modal :(

-

Check the console for errors

+
+

Failed to render Modal :(

+

Check the console for errors

)} > - + {showTidalLyrics && LyricsPosition === "above" && } + {showTidalControls && } + {showTidalLyrics && LyricsPosition === "below" && } + {showSpotifyLyrics && LyricsPosition === "above" && } + {showSpotifyControls && } + {showSpotifyLyrics && LyricsPosition === "below" && } ); - } + }, + + async start() { + await migrateOldLyrics(); + toggleHoverControls(Settings.plugins.MusicControls.hoverControls); + }, }); diff --git a/src/equicordplugins/musicControls/settings.tsx b/src/equicordplugins/musicControls/settings.tsx new file mode 100644 index 000000000..6b3c3cae5 --- /dev/null +++ b/src/equicordplugins/musicControls/settings.tsx @@ -0,0 +1,187 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { disableStyle, enableStyle } from "@api/Styles"; +import { useAwaiter } from "@utils/react"; +import { makeRange, OptionType } from "@utils/types"; +import { Button, Forms, MaskedLink, showToast, Text, Toasts, useMemo } from "@webpack/common"; + +import hoverOnlyStyle from "./hoverOnly.css?managed"; +import { clearLyricsCache, getLyricsCount } from "./spotify/lyrics/api"; +import { useLyrics } from "./spotify/lyrics/components/util"; +import { Provider } from "./spotify/lyrics/providers/types"; + +const sliderOptions = { + markers: makeRange(-2500, 2500, 250), + stickToMarkers: true, +}; + +export function toggleHoverControls(value: boolean) { + (value ? enableStyle : disableStyle)(hoverOnlyStyle); +} + +function Details() { + const { lyricsInfo } = useLyrics(); + + const [count, error, loading] = useAwaiter( + useMemo(() => getLyricsCount, []), + { + onError: () => console.error("Failed to get lyrics count"), + fallbackValue: null, + } + ); + + return ( + <> + Current lyrics provider: {lyricsInfo?.useLyric || "None"} + {loading ? Loading lyrics count... : error ? Failed to get lyrics count : Lyrics count: {count}} + + ); +} + + +function InstallInstructions() { + return ( + + How to install + + Install TidaLuna from here, then go to TidalLuna settings → Plugin stores → Install @vmohammad/api + + + ); +} + +export const settings = definePluginSettings({ + showSpotifyControls: { + description: "Show Spotify Controls", + type: OptionType.BOOLEAN, + default: false, + }, + showSpotifyLyrics: { + description: "Show Spotify Lyrics", + type: OptionType.BOOLEAN, + default: false, + }, + installTidalWithWS: { + type: OptionType.COMPONENT, + component: () => + }, + showTidalControls: { + description: "Show Tidal Player", + type: OptionType.BOOLEAN, + default: false, + }, + showTidalLyrics: { + description: "Show Tidal Controls", + type: OptionType.BOOLEAN, + default: false, + }, + hoverControls: { + description: "Show controls on hover", + type: OptionType.BOOLEAN, + default: false, + onChange: v => toggleHoverControls(v) + }, + useSpotifyUris: { + type: OptionType.BOOLEAN, + description: "Open Spotify URIs instead of Spotify URLs. Will only work if you have Spotify installed and might not work on all platforms", + default: false + }, + previousButtonRestartsTrack: { + type: OptionType.BOOLEAN, + description: "Restart currently playing track when pressing the previous button if playtime is >3s", + default: true + }, + ShowMusicNoteOnNoLyrics: { + description: "Show a music note icon when no lyrics are found", + type: OptionType.BOOLEAN, + default: true, + }, + LyricsPosition: { + description: "Position of the lyrics", + type: OptionType.SELECT, + options: [ + { value: "above", label: "Above Player(s)" }, + { value: "below", label: "Below Player(s)", default: true }, + ], + }, + LyricsProvider: { + description: "Where lyrics are fetched from", + type: OptionType.SELECT, + options: [ + { value: Provider.Spotify, label: "Spotify (Musixmatch)", default: true }, + { value: Provider.Lrclib, label: "LRCLIB" }, + ], + }, + LyricsConversion: { + description: "Automatically translate or romanize lyrics", + type: OptionType.SELECT, + options: [ + { value: Provider.None, label: "None", default: true }, + { value: Provider.Translated, label: "Translate" }, + { value: Provider.Romanized, label: "Romanize" }, + ] + }, + FallbackProvider: { + description: "When a lyrics provider fails, try other providers", + type: OptionType.BOOLEAN, + default: true, + }, + ShowFailedToasts: { + description: "Hide toasts when lyrics fail to fetch", + type: OptionType.BOOLEAN, + default: true, + }, + TidalLyricFetch: { + description: "Custom URL for fetching lyrics", + type: OptionType.STRING, + default: "https://api.vmohammad.dev/", + placeholder: "https://api.vmohammad.dev/", + onChange: (value: string) => { + if (!value.endsWith("/")) { + value += "/"; + } + if (URL.canParse(value)) { + settings.store.TidalLyricFetch = value; + } else { + showToast("Invalid URL format for CustomUrl: " + value, Toasts.Type.FAILURE); + settings.store.TidalLyricFetch = "https://api.vmohammad.dev/"; + } + } + }, + LyricDelay: { + description: "", + type: OptionType.SLIDER, + default: 0, + ...sliderOptions + }, + SyncMode: { + description: "Lyrics sync mode", + type: OptionType.SELECT, + options: [ + { value: "line", label: "Line" }, + { value: "word", label: "Word", default: true }, + { value: "character", label: "Character" }, + ], + default: "word", + }, + PurgeLyricsCache: { + description: "Purge the lyrics cache", + type: OptionType.COMPONENT, + component: () => ( + + ), + }, +}); diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/equicordplugins/musicControls/spotify/PlayerComponent.tsx similarity index 98% rename from src/plugins/spotifyControls/PlayerComponent.tsx rename to src/equicordplugins/musicControls/spotify/PlayerComponent.tsx index 32c78c364..1c4a2d0a9 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/equicordplugins/musicControls/spotify/PlayerComponent.tsx @@ -134,7 +134,7 @@ function Controls() { @@ -347,7 +347,7 @@ function Info({ track }: { track: Track; }) { ); } -export function Player() { +export function SpotifyPlayer() { const track = useStateFromStores( [SpotifyStore], () => SpotifyStore.track, diff --git a/src/plugins/spotifyControls/SeekBar.ts b/src/equicordplugins/musicControls/spotify/SeekBar.ts similarity index 100% rename from src/plugins/spotifyControls/SeekBar.ts rename to src/equicordplugins/musicControls/spotify/SeekBar.ts diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/equicordplugins/musicControls/spotify/SpotifyStore.ts similarity index 97% rename from src/plugins/spotifyControls/SpotifyStore.ts rename to src/equicordplugins/musicControls/spotify/SpotifyStore.ts index 65ee2c154..efdfa176f 100644 --- a/src/plugins/spotifyControls/SpotifyStore.ts +++ b/src/equicordplugins/musicControls/spotify/SpotifyStore.ts @@ -89,7 +89,7 @@ export const SpotifyStore = proxyLazyWebpack(() => { public isSettingPosition = false; public openExternal(path: string) { - const url = Settings.plugins.SpotifyControls.useSpotifyUris || Vencord.Plugins.isPluginEnabled("OpenInApp") + const url = Settings.plugins.MusicControls.useSpotifyUris || Vencord.Plugins.isPluginEnabled("OpenInApp") ? "spotify:" + path.replaceAll("/", (_, idx) => idx === 0 ? "" : ":") : "https://open.spotify.com" + path; diff --git a/src/equicordplugins/spotifyLyrics/api.tsx b/src/equicordplugins/musicControls/spotify/lyrics/api.tsx similarity index 97% rename from src/equicordplugins/spotifyLyrics/api.tsx rename to src/equicordplugins/musicControls/spotify/lyrics/api.tsx index 379a564d1..a92381d77 100644 --- a/src/equicordplugins/spotifyLyrics/api.tsx +++ b/src/equicordplugins/musicControls/spotify/lyrics/api.tsx @@ -5,13 +5,12 @@ */ import { DataStore } from "@api/index"; -import { Track } from "plugins/spotifyControls/SpotifyStore"; +import { settings } from "../../settings"; +import { Track } from "../SpotifyStore"; import { getLyricsLrclib } from "./providers/lrclibAPI"; import { getLyricsSpotify } from "./providers/SpotifyAPI"; import { LyricsData, Provider, SyncedLyric } from "./providers/types"; -import settings from "./settings"; - const LyricsCacheKey = "SpotifyLyricsCacheNew"; diff --git a/src/equicordplugins/spotifyLyrics/components/ctxMenu.tsx b/src/equicordplugins/musicControls/spotify/lyrics/components/ctxMenu.tsx similarity index 100% rename from src/equicordplugins/spotifyLyrics/components/ctxMenu.tsx rename to src/equicordplugins/musicControls/spotify/lyrics/components/ctxMenu.tsx diff --git a/src/equicordplugins/spotifyLyrics/components/lyrics.tsx b/src/equicordplugins/musicControls/spotify/lyrics/components/lyrics.tsx similarity index 94% rename from src/equicordplugins/spotifyLyrics/components/lyrics.tsx rename to src/equicordplugins/musicControls/spotify/lyrics/components/lyrics.tsx index f323ab2ef..7a3770093 100644 --- a/src/equicordplugins/spotifyLyrics/components/lyrics.tsx +++ b/src/equicordplugins/musicControls/spotify/lyrics/components/lyrics.tsx @@ -6,10 +6,10 @@ import { openModal } from "@utils/modal"; import { ContextMenuApi, React, Text, TooltipContainer, useEffect, useState, useStateFromStores } from "@webpack/common"; -import { SpotifyStore } from "plugins/spotifyControls/SpotifyStore"; +import { settings } from "../../../settings"; +import { SpotifyStore } from "../../SpotifyStore"; import { SpotifyLrcStore } from "../providers/store"; -import settings from "../settings"; import { LyricsContextMenu } from "./ctxMenu"; import { LyricsModal } from "./modal"; import { cl, NoteSvg, useLyrics } from "./util"; @@ -63,7 +63,7 @@ function LyricsDisplay({ scroll = true }: { scroll?: boolean; }) { ); } -export function Lyrics({ scroll = true }: { scroll?: boolean; } = {}) { +export function SpotifyLyrics({ scroll = true }: { scroll?: boolean; } = {}) { SpotifyLrcStore.init(); const track = useStateFromStores( [SpotifyStore], diff --git a/src/equicordplugins/spotifyLyrics/components/modal.tsx b/src/equicordplugins/musicControls/spotify/lyrics/components/modal.tsx similarity index 97% rename from src/equicordplugins/spotifyLyrics/components/modal.tsx rename to src/equicordplugins/musicControls/spotify/lyrics/components/modal.tsx index 855bcf967..6a3423fec 100644 --- a/src/equicordplugins/spotifyLyrics/components/modal.tsx +++ b/src/equicordplugins/musicControls/spotify/lyrics/components/modal.tsx @@ -7,8 +7,8 @@ import { openImageModal } from "@utils/discord"; import { ModalContent, ModalHeader, ModalProps, ModalRoot } from "@utils/modal"; import { React, Text } from "@webpack/common"; -import { SpotifyStore, Track } from "plugins/spotifyControls/SpotifyStore"; +import { SpotifyStore, Track } from "../../SpotifyStore"; import { cl, NoteSvg, scrollClasses, useLyrics } from "./util"; const formatTime = (time: number) => { diff --git a/src/equicordplugins/spotifyLyrics/components/util.tsx b/src/equicordplugins/musicControls/spotify/lyrics/components/util.tsx similarity index 96% rename from src/equicordplugins/spotifyLyrics/components/util.tsx rename to src/equicordplugins/musicControls/spotify/lyrics/components/util.tsx index a3ed7884b..176df03a7 100644 --- a/src/equicordplugins/spotifyLyrics/components/util.tsx +++ b/src/equicordplugins/musicControls/spotify/lyrics/components/util.tsx @@ -7,12 +7,11 @@ import { classNameFactory } from "@api/Styles"; import { findByPropsLazy } from "@webpack"; import { React, useEffect, useState, useStateFromStores } from "@webpack/common"; -import { SpotifyStore } from "plugins/spotifyControls/SpotifyStore"; +import { settings } from "../../../settings"; +import { SpotifyStore } from "../../SpotifyStore"; import { SpotifyLrcStore } from "../providers/store"; import { SyncedLyric } from "../providers/types"; -import settings from "../settings"; - export const scrollClasses = findByPropsLazy("auto", "customTheme"); export const cl = classNameFactory("vc-spotify-lyrics-"); diff --git a/src/equicordplugins/spotifyLyrics/providers/SpotifyAPI/index.ts b/src/equicordplugins/musicControls/spotify/lyrics/providers/SpotifyAPI/index.ts similarity index 100% rename from src/equicordplugins/spotifyLyrics/providers/SpotifyAPI/index.ts rename to src/equicordplugins/musicControls/spotify/lyrics/providers/SpotifyAPI/index.ts diff --git a/src/equicordplugins/spotifyLyrics/providers/lrclibAPI/index.ts b/src/equicordplugins/musicControls/spotify/lyrics/providers/lrclibAPI/index.ts similarity index 96% rename from src/equicordplugins/spotifyLyrics/providers/lrclibAPI/index.ts rename to src/equicordplugins/musicControls/spotify/lyrics/providers/lrclibAPI/index.ts index e38be95b4..5ee570476 100644 --- a/src/equicordplugins/spotifyLyrics/providers/lrclibAPI/index.ts +++ b/src/equicordplugins/musicControls/spotify/lyrics/providers/lrclibAPI/index.ts @@ -4,8 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import { Track } from "plugins/spotifyControls/SpotifyStore"; - +import { Track } from "../../../SpotifyStore"; import { LyricsData, Provider } from "../types"; const baseUrlLrclib = "https://lrclib.net/api/get"; diff --git a/src/equicordplugins/spotifyLyrics/providers/store.ts b/src/equicordplugins/musicControls/spotify/lyrics/providers/store.ts similarity index 97% rename from src/equicordplugins/spotifyLyrics/providers/store.ts rename to src/equicordplugins/musicControls/spotify/lyrics/providers/store.ts index 34c8a321c..5f9049891 100644 --- a/src/equicordplugins/spotifyLyrics/providers/store.ts +++ b/src/equicordplugins/musicControls/spotify/lyrics/providers/store.ts @@ -7,10 +7,10 @@ import { showNotification } from "@api/Notifications"; import { proxyLazyWebpack } from "@webpack"; import { Flux, FluxDispatcher } from "@webpack/common"; -import { SpotifyStore, type Track } from "plugins/spotifyControls/SpotifyStore"; +import { settings } from "../../../settings"; +import { SpotifyStore, type Track } from "../../SpotifyStore"; import { getLyrics, lyricFetchers, providers, updateLyrics } from "../api"; -import settings from "../settings"; import { lyricsAlternativeFetchers } from "./translator"; import { LyricsData, Provider } from "./types"; diff --git a/src/equicordplugins/spotifyLyrics/providers/translator/index.ts b/src/equicordplugins/musicControls/spotify/lyrics/providers/translator/index.ts similarity index 98% rename from src/equicordplugins/spotifyLyrics/providers/translator/index.ts rename to src/equicordplugins/musicControls/spotify/lyrics/providers/translator/index.ts index 37555103a..02d7308ec 100644 --- a/src/equicordplugins/spotifyLyrics/providers/translator/index.ts +++ b/src/equicordplugins/musicControls/spotify/lyrics/providers/translator/index.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -import settings from "../../settings"; +import settings from "../../../.."; import { LyricsData, Provider, SyncedLyric } from "../types"; // stolen from src\plugins\translate\utils.ts diff --git a/src/equicordplugins/spotifyLyrics/providers/translator/languages.ts b/src/equicordplugins/musicControls/spotify/lyrics/providers/translator/languages.ts similarity index 100% rename from src/equicordplugins/spotifyLyrics/providers/translator/languages.ts rename to src/equicordplugins/musicControls/spotify/lyrics/providers/translator/languages.ts diff --git a/src/equicordplugins/spotifyLyrics/providers/types.ts b/src/equicordplugins/musicControls/spotify/lyrics/providers/types.ts similarity index 100% rename from src/equicordplugins/spotifyLyrics/providers/types.ts rename to src/equicordplugins/musicControls/spotify/lyrics/providers/types.ts diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/equicordplugins/musicControls/spotify/spotifyStyles.css similarity index 100% rename from src/plugins/spotifyControls/spotifyStyles.css rename to src/equicordplugins/musicControls/spotify/spotifyStyles.css diff --git a/src/equicordplugins/tidalLyrics/styles.css b/src/equicordplugins/musicControls/styles.css similarity index 72% rename from src/equicordplugins/tidalLyrics/styles.css rename to src/equicordplugins/musicControls/styles.css index d22c2718a..b0a42927f 100644 --- a/src/equicordplugins/tidalLyrics/styles.css +++ b/src/equicordplugins/musicControls/styles.css @@ -1,4 +1,82 @@ -/* Rework: polish container and sync mode visuals; modernize color notation; fix selector order; merge reduced-motion rules */ +/* stylelint-disable rule-empty-line-before */ +.vc-spotify-lyrics { + padding: 0.375rem 0.5rem; + text-align: center; + overflow: hidden; + max-height: 60px; + cursor: pointer; +} + +.vc-spotify-lyrics-music-note { + font-size: 16px; + text-align: center; + animation: side-to-side 2s ease-in-out infinite; + color: var(--text-muted); + height: 18px; + margin: 3px; +} + +.vc-spotify-lyrics-next, +.vc-spotify-lyrics-prev { + color: var(--text-muted); + margin-bottom: 1%; +} + +@keyframes side-to-side { + + 0%, + 100% { + transform: translateX(0); + } + + 50% { + transform: translateX(-10px); + } +} + +.vc-spotify-lyrics-header-content { + display: flex; + align-items: center; + width: 100%; +} + +.vc-spotify-lyrics-album-image { + width: 100px; + height: 100px; + margin-right: 5%; + border-radius: 5px; + cursor: pointer; +} + +.vc-spotify-lyrics-modal-line-current, +.vc-spotify-lyrics-modal-line { + margin: 4px 0; + padding: 4px 8px; + position: relative; +} + +.vc-spotify-lyrics-modal-line-current { + font-weight: bold; +} + +.vc-spotify-lyrics-modal-note { + height: 1lh; +} + +.vc-spotify-lyrics-modal-timestamp { + color: var(--text-muted); + margin-right: 0.5em; + cursor: pointer; +} + +.vc-spotify-lyrics-modal-timestamp:hover { + text-decoration: underline; +} + +.vc-spotify-lyrics-modal-no-lyrics { + text-align: center; + padding: 1rem; +} .eq-tidal-lyrics { padding: 0.375rem 0.5rem; @@ -95,7 +173,6 @@ color: var(--brand-experiment); font-weight: 600; text-shadow: 0 0 .25em currentcolor; - transform: scale(1.04); } .eq-tidal-lyrics-word { @@ -118,7 +195,6 @@ background: none; box-shadow: none; text-shadow: 0 0 .25em currentcolor; - transform: scale(1.04); } .eq-tidal-lyrics-current, @@ -154,6 +230,7 @@ @media (prefers-reduced-motion: reduce) { + .eq-tidal-lyrics-current, .eq-tidal-lyrics-next, .eq-tidal-lyrics-prev, @@ -167,4 +244,4 @@ .eq-tidal-lyrics-word-active { transform: none; } -} +} \ No newline at end of file diff --git a/src/equicordplugins/tidalControls/TidalPlayer.tsx b/src/equicordplugins/musicControls/tidal/TidalPlayer.tsx similarity index 97% rename from src/equicordplugins/tidalControls/TidalPlayer.tsx rename to src/equicordplugins/musicControls/tidal/TidalPlayer.tsx index ca4326c2f..e4651707e 100644 --- a/src/equicordplugins/tidalControls/TidalPlayer.tsx +++ b/src/equicordplugins/musicControls/tidal/TidalPlayer.tsx @@ -6,13 +6,14 @@ import "./tidalStyles.css"; +import { Settings } from "@api/Settings"; import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons"; -import { SeekBar } from "@plugins/spotifyControls/SeekBar"; import { debounce } from "@shared/debounce"; import { openImageModal } from "@utils/discord"; import { classes, copyWithToast } from "@utils/misc"; import { ContextMenuApi, Flex, FluxDispatcher, Forms, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common"; +import { SeekBar } from "../spotify/SeekBar"; import { type PlayerState, type Repeat, TidalStore } from "./TidalStore"; const cl = (className: string) => `eq-tdl-${className}`; @@ -120,7 +121,10 @@ function Controls() { > - - ), - }, - TestingCache: { - description: "Save songs to a testing cache instead", - type: OptionType.BOOLEAN, - default: false, - hidden: true, - } -}); - -export default settings; diff --git a/src/equicordplugins/spotifyLyrics/styles.css b/src/equicordplugins/spotifyLyrics/styles.css deleted file mode 100644 index 64518c826..000000000 --- a/src/equicordplugins/spotifyLyrics/styles.css +++ /dev/null @@ -1,77 +0,0 @@ -.vc-spotify-lyrics { - padding: 0.375rem 0.5rem; - text-align: center; - overflow: hidden; - max-height: 60px; - cursor: pointer; -} - -.vc-spotify-lyrics-music-note { - font-size: 16px; - text-align: center; - animation: side-to-side 2s ease-in-out infinite; - color: var(--text-muted); - height: 18px; - margin: 3px; -} - -.vc-spotify-lyrics-next, -.vc-spotify-lyrics-prev { - color: var(--text-muted); - margin-bottom: 1%; -} - -@keyframes side-to-side { - 0%, - 100% { - transform: translateX(0); - } - - 50% { - transform: translateX(-10px); - } -} - -.vc-spotify-lyrics-header-content { - display: flex; - align-items: center; - width: 100%; -} - -.vc-spotify-lyrics-album-image { - width: 100px; - height: 100px; - margin-right: 5%; - border-radius: 5px; - cursor: pointer; -} - -.vc-spotify-lyrics-modal-line-current, -.vc-spotify-lyrics-modal-line { - margin: 4px 0; - padding: 4px 8px; - position: relative; -} - -.vc-spotify-lyrics-modal-line-current { - font-weight: bold; -} - -.vc-spotify-lyrics-modal-note { - height: 1lh; -} - -.vc-spotify-lyrics-modal-timestamp { - color: var(--text-muted); - margin-right: 0.5em; - cursor: pointer; -} - -.vc-spotify-lyrics-modal-timestamp:hover { - text-decoration: underline; -} - -.vc-spotify-lyrics-modal-no-lyrics { - text-align: center; - padding: 1rem; -} diff --git a/src/equicordplugins/tidalControls/hoverOnly.css b/src/equicordplugins/tidalControls/hoverOnly.css deleted file mode 100644 index b5bed59db..000000000 --- a/src/equicordplugins/tidalControls/hoverOnly.css +++ /dev/null @@ -1,16 +0,0 @@ -.eq-tdl-button-row { - height: 0; - opacity: 0; - pointer-events: none; - transition: 0.2s; - transition-property: height; -} - -#eq-tdl-player:hover .eq-tdl-button-row { - opacity: 1; - height: 32px; - pointer-events: auto; - - /* only transition opacity on show to prevent clipping */ - transition-property: height, opacity; -} diff --git a/src/equicordplugins/tidalControls/index.tsx b/src/equicordplugins/tidalControls/index.tsx deleted file mode 100644 index 4c5a502ec..000000000 --- a/src/equicordplugins/tidalControls/index.tsx +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { definePluginSettings, Settings } from "@api/Settings"; -import { disableStyle, enableStyle } from "@api/Styles"; -import ErrorBoundary from "@components/ErrorBoundary"; -import { Lyrics as SpotifyLyrics } from "@equicordplugins/spotifyLyrics/components/lyrics"; -import { Player as SpotifyPlayer } from "@plugins/spotifyControls/PlayerComponent"; -import { Player as YTMPlayer } from "@equicordplugins/youtubeMusicControls/PlayerComponent"; -import { EquicordDevs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; -import { Forms, MaskedLink } from "@webpack/common"; - -import hoverOnlyStyle from "./hoverOnly.css?managed"; -import { TidalPlayer } from "./TidalPlayer"; - -function toggleHoverControls(value: boolean) { - (value ? enableStyle : disableStyle)(hoverOnlyStyle); -} - - -function InstallInstructions() { - return ( - - How to install - - Install TidaLuna from here, then go to TidalLuna settings → Plugin stores → Install @vmohammad/api - - - ); -} - -const settings = definePluginSettings({ - installYtmWithWebsocket: { - type: OptionType.COMPONENT, - component: () => - }, - hoverControls: { - description: "Show controls on hover", - type: OptionType.BOOLEAN, - default: false, - onChange: v => toggleHoverControls(v) - }, - // SOON™️™️™️ - // lyricsProvider: { - // description: "Which provider should be used for lyrics", - // type: OptionType.SELECT, - // options: [ - // { label: "Tidal", value: "tidal", default: true }, - // { label: "vMohammad's Synced API", value: "synced" } - // ] - // } -}); - -export default definePlugin({ - name: "TidalControls", - description: "Adds a TIDAL player above the account panel", - authors: [EquicordDevs.vmohammad], - settings, - patches: [ - { - find: "this.isCopiedStreakGodlike", - replacement: [ - { - match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?userTag:\i,hidePrivateData:)/, - replace: "$self.PanelWrapper,{VencordOriginal:$1,", - predicate: () => !Settings.plugins.SpotifyControls.enabled, - noWarn: true, - }, - { - match: /Vencord\.Plugins\.plugins\["SpotifyLyrics"\]\.FakePanelWrapper/, - replace: "$self.PanelWrapper", - predicate: () => Settings.plugins.SpotifyLyrics.enabled, - noWarn: true, - }, - { - match: /Vencord\.Plugins\.plugins\["SpotifyControls"\]\.PanelWrapper/, - replace: "$self.PanelWrapper", - predicate: () => Settings.plugins.SpotifyControls.enabled && !Settings.plugins.SpotifyLyrics.enabled, - noWarn: true, - }, - { - match: /Vencord\.Plugins\.plugins\["YouTubeMusicControls"\]\.FakePanelWrapper/, - replace: "$self.PanelWrapper", - predicate: () => Settings.plugins.YouTubeMusicControls.enabled, - noWarn: true, - }, - ], - }, - ], - - start: () => toggleHoverControls(Settings.plugins.YouTubeMusicControls.hoverControls), - - PanelWrapper({ VencordOriginal, ...props }) { - const showYTMPlayer = Settings.plugins.YouTubeMusicControls.enabled; - const showSpotifyControls = Settings.plugins.SpotifyControls.enabled; - const showSpotifyLyrics = Settings.plugins.SpotifyLyrics.enabled; - const LyricsPosition = showSpotifyLyrics ? Settings.plugins.SpotifyLyrics.LyricsPosition : null; - return ( - <> - ( -
-

Failed to render YouTube Music Modal :(

-

Check the console for errors

-
- )} - > - - - {showYTMPlayer && } - {showSpotifyLyrics && LyricsPosition === "above" && } - {showSpotifyControls && } - {showSpotifyLyrics && LyricsPosition === "below" && } -
- - - - ); - } -}); diff --git a/src/equicordplugins/tidalLyrics/index.tsx b/src/equicordplugins/tidalLyrics/index.tsx deleted file mode 100644 index 8a89896e2..000000000 --- a/src/equicordplugins/tidalLyrics/index.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import "./styles.css"; - -import { Settings } from "@api/Settings"; -import ErrorBoundary from "@components/ErrorBoundary"; -import { Lyrics as SpotifyLyrics } from "@equicordplugins/spotifyLyrics/components/lyrics"; -import { TidalPlayer } from "@equicordplugins/tidalControls/TidalPlayer"; -import { Player as SpotifyPlayer } from "@plugins/spotifyControls/PlayerComponent"; -import { Player as YTMPlayer } from "@equicordplugins/youtubeMusicControls/PlayerComponent"; -import { EquicordDevs } from "@utils/constants"; -import definePlugin from "@utils/types"; - -import { Lyrics } from "./components/lyrics"; -import settings from "./settings"; - - -export default definePlugin({ - name: "TidalLyrics", - authors: [EquicordDevs.vmohammad], - description: "Adds lyrics to TidalControls", - dependencies: ["TidalControls"], - patches: [ - { - find: "this.isCopiedStreakGodlike", - replacement: { - match: /Vencord\.Plugins\.plugins\["TidalControls"\]\.PanelWrapper/, - replace: "$self.FakePanelWrapper", - }, - predicate: () => Settings.plugins.TidalControls.enabled, - noWarn: true, - }, - ], - FakePanelWrapper({ VencordOriginal, ...props }) { - const { LyricsPosition } = settings.use(["LyricsPosition"]); - const showYTMPlayer = Settings.plugins.YouTubeMusicControls.enabled; - const showSpotifyControls = Settings.plugins.SpotifyControls.enabled; - const showSpotifyLyrics = Settings.plugins.SpotifyLyrics.enabled; - const SpotifyLyricsPosition = showSpotifyLyrics ? Settings.plugins.SpotifyLyrics.LyricsPosition : null; - return ( - <> - ( -
-

Failed to render Tidal Lyrics Modal :(

-

Check the console for errors

-
- )} - > - {LyricsPosition === "above" && } - - {LyricsPosition === "below" && } - - {showYTMPlayer && } - {showSpotifyLyrics && SpotifyLyricsPosition === "above" && } - {showSpotifyControls && } - {showSpotifyLyrics && SpotifyLyricsPosition === "below" && } -
- - - - ); - }, - settings, -}); diff --git a/src/equicordplugins/tidalLyrics/settings.tsx b/src/equicordplugins/tidalLyrics/settings.tsx deleted file mode 100644 index ca52a1334..000000000 --- a/src/equicordplugins/tidalLyrics/settings.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { definePluginSettings } from "@api/Settings"; -import { SliderSetting } from "@components/settings/tabs/plugins/components/SliderSetting"; -import { makeRange, OptionType } from "@utils/types"; -import { showToast, Toasts } from "@webpack/common"; - -import { Lyrics } from "./components/lyrics"; - -const sliderOptions = { - markers: makeRange(-2500, 2500, 250), - stickToMarkers: true, -}; - -const settings = definePluginSettings({ - ShowMusicNoteOnNoLyrics: { - description: "Show a music note icon when no lyrics are found", - type: OptionType.BOOLEAN, - default: true, - }, - LyricsPosition: { - description: "Position of the lyrics", - type: OptionType.SELECT, - options: [ - { value: "above", label: "Above TidalControls" }, - { value: "below", label: "Below TidalControls", default: true }, - ], - }, - ShowFailedToasts: { - description: "Hide toasts when lyrics fail to fetch", - type: OptionType.BOOLEAN, - default: true, - }, - CustomUrl: { - description: "Custom URL for fetching lyrics", - type: OptionType.STRING, - default: "https://api.vmohammad.dev/", - placeholder: "https://api.vmohammad.dev/", - onChange: (value: string) => { - if (!value.endsWith("/")) { - value += "/"; - } - if (URL.canParse(value)) { - settings.store.CustomUrl = value; - } else { - showToast("Invalid URL format for CustomUrl: " + value, Toasts.Type.FAILURE); - settings.store.CustomUrl = "https://api.vmohammad.dev/"; - } - } - }, - LyricDelay: { - description: "", - type: OptionType.SLIDER, - default: 0, - hidden: true, - ...sliderOptions - }, - SyncMode: { - description: "Lyrics sync mode", - type: OptionType.SELECT, - options: [ - { value: "line", label: "Line" }, - { value: "word", label: "Word", default: true }, - { value: "character", label: "Character" }, - ], - default: "word", - }, - Display: { - description: "", - type: OptionType.COMPONENT, - component: () => ( - <> - { - settings.store.LyricDelay = v; - }} - pluginSettings={Vencord.Settings.plugins.TidalLyrics} - id={"LyricDelay"} - /> - - - ) - }, -}); - -export default settings; diff --git a/src/equicordplugins/youtubeMusicControls/PlayerComponent.tsx b/src/equicordplugins/youtubeMusicControls/PlayerComponent.tsx deleted file mode 100644 index 8ebc7fc77..000000000 --- a/src/equicordplugins/youtubeMusicControls/PlayerComponent.tsx +++ /dev/null @@ -1,369 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import "./ytmStyles.css"; - -import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons"; -import { SeekBar } from "@plugins/spotifyControls/SeekBar"; -import { debounce } from "@shared/debounce"; -import { openImageModal } from "@utils/discord"; -import { classes, copyWithToast } from "@utils/misc"; -import { ContextMenuApi, Flex, FluxDispatcher, Forms, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common"; - -import { type PlayerState, type Repeat, YoutubeMusicStore } from "./YtmStore"; - -const cl = (className: string) => `vc-ytm-${className}`; - -function msToHuman(ms: number) { - const minutes = ms / 1000 / 60; - const m = Math.floor(minutes); - const s = Math.floor((minutes - m) * 60); - return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; -} - -function Svg(path: string, label: string) { - return () => ( - - {label} - - - ); -} - -// KraXen's icons :yesyes: -// from https://fonts.google.com/icons?icon.style=Rounded&icon.set=Material+Icons -// older material icon style, but still really good -const PlayButton = Svg("M8 6.82v10.36c0 .79.87 1.27 1.54.84l8.14-5.18c.62-.39.62-1.29 0-1.69L9.54 5.98C8.87 5.55 8 6.03 8 6.82z", "play"); -const PauseButton = Svg("M8 19c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2v10c0 1.1.9 2 2 2zm6-12v10c0 1.1.9 2 2 2s2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2z", "pause"); -const SkipPrev = Svg("M7 6c.55 0 1 .45 1 1v10c0 .55-.45 1-1 1s-1-.45-1-1V7c0-.55.45-1 1-1zm3.66 6.82l5.77 4.07c.66.47 1.58-.01 1.58-.82V7.93c0-.81-.91-1.28-1.58-.82l-5.77 4.07c-.57.4-.57 1.24 0 1.64z", "previous"); -const SkipNext = Svg("M7.58 16.89l5.77-4.07c.56-.4.56-1.24 0-1.63L7.58 7.11C6.91 6.65 6 7.12 6 7.93v8.14c0 .81.91 1.28 1.58.82zM16 7v10c0 .55.45 1 1 1s1-.45 1-1V7c0-.55-.45-1-1-1s-1 .45-1 1z", "next"); -const Repeat = Svg("M7 7h10v1.79c0 .45.54.67.85.35l2.79-2.79c.2-.2.2-.51 0-.71l-2.79-2.79c-.31-.31-.85-.09-.85.36V5H6c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1s1-.45 1-1V7zm10 10H7v-1.79c0-.45-.54-.67-.85-.35l-2.79 2.79c-.2.2-.2.51 0 .71l2.79 2.79c.31.31.85.09.85-.36V19h11c.55 0 1-.45 1-1v-4c0-.55-.45-1-1-1s-1 .45-1 1v3z", "repeat"); -const Shuffle = Svg("M10.59 9.17L6.12 4.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41l4.46 4.46 1.42-1.4zm4.76-4.32l1.19 1.19L4.7 17.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L17.96 7.46l1.19 1.19c.31.31.85.09.85-.36V4.5c0-.28-.22-.5-.5-.5h-3.79c-.45 0-.67.54-.36.85zm-.52 8.56l-1.41 1.41 3.13 3.13-1.2 1.2c-.31.31-.09.85.36.85h3.79c.28 0 .5-.22.5-.5v-3.79c0-.45-.54-.67-.85-.35l-1.19 1.19-3.13-3.14z", "shuffle"); - -function Button(props: React.ButtonHTMLAttributes) { - return ( - - ); -} - -function CopyContextMenu({ name, path }: { name: string; path: string; }) { - const copyId = `ytm-copy-${name}`; - const openId = `ytm-open-${name}`; - - return ( - FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })} - aria-label={`YouTube Music ${name} Menu`} - > - copyWithToast(`https://music.youtube.com${path}`)} - icon={LinkIcon} - /> - YoutubeMusicStore.openExternal(path)} - icon={OpenExternalIcon} - /> - - ); -} - -function makeContextMenu(name: string, path: string) { - return (e: React.MouseEvent) => - ContextMenuApi.openContextMenu(e, () => ); -} - -function Controls() { - const [isPlaying, shuffle, repeat] = useStateFromStores( - [YoutubeMusicStore], - () => [YoutubeMusicStore.isPlaying, YoutubeMusicStore.shuffle, YoutubeMusicStore.repeat] - ); - - const [nextRepeat, repeatClassName] = (() => { - switch (repeat) { - case "NONE": return ["ALL", "repeat-off"] as const; - case "ALL": return ["ONE", "repeat-context"] as const; - case "ONE": return ["NONE", "repeat-track"] as const; - default: throw new Error(`Invalid repeat state ${repeat}`); - } - })(); - - // the 1 is using position absolute so it does not make the button jump around - return ( - - - - - - - - ); -} - -const seek = debounce((v: number) => { - YoutubeMusicStore.seek(v); -}); - -function YtmSeekBar() { - const { songDuration, videoId } = YoutubeMusicStore.song ?? { songDuration: 0, videoId: 0 }; - - const [storePosition, isPlaying] = useStateFromStores( - [YoutubeMusicStore], - () => [YoutubeMusicStore.mPosition, YoutubeMusicStore.isPlaying] - ); - - const [position, setPosition] = useState(storePosition); - - useEffect(() => { - setPosition(storePosition); - }, [videoId, songDuration, storePosition]); - - useEffect(() => { - if (isPlaying) { - setPosition(YoutubeMusicStore.position); - const interval = setInterval(() => { - setPosition(p => p + 1000); - }, 1000); - return () => clearInterval(interval); - } - }, [storePosition, isPlaying]); - - const onChange = (v: number) => { - setPosition(v); - seek(v); - }; - - return ( -
- - {msToHuman(position)} - - - - {msToHuman(songDuration * 1000)} - -
- ); -} - -function AlbumContextMenu({ track }: { track: PlayerState["song"]; }) { - const volume = useStateFromStores([YoutubeMusicStore], () => YoutubeMusicStore.volume); - - return ( - FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })} - aria-label="YouTube Music Album Menu" - > - {/* YoutubeMusicStore.openExternal(`/album/${track?.album}`)} - icon={OpenExternalIcon} - /> */} - track?.imageSrc && openImageModal({ url: track.imageSrc })} - icon={ImageIcon} - /> - ( - YoutubeMusicStore.setVolume(v))} - /> - )} - /> - - ); -} - -function makeLinkProps(name: string, condition: unknown, path: string) { - if (!condition) return {}; - - return { - role: "link", - onClick: () => YoutubeMusicStore.openExternal(path), - onContextMenu: makeContextMenu(name, path) - } satisfies React.HTMLAttributes; -} - -function Info({ track }: { track: NonNullable; }) { - const img = track?.imageSrc; - - const [coverExpanded, setCoverExpanded] = useState(false); - - const i = ( - <> - {img && ( - Album Image setCoverExpanded(!coverExpanded)} - onContextMenu={e => { - ContextMenuApi.openContextMenu(e, () => ); - }} - /> - )} - - ); - - if (coverExpanded && img) return ( -
- {i} -
- ); - - return ( -
- {i} -
- - {track?.title} - - {track.artist && ( - - by  - - {track.artist} - - - )} - {track.album && ( - - on  - - {track.album} - - - )} -
-
- ); -} - -export function Player() { - const track = useStateFromStores( - [YoutubeMusicStore], - () => YoutubeMusicStore.song, - null, - (prev, next) => prev?.videoId ? (prev.videoId === next?.videoId) : prev?.title === next?.title - ); - - const isPlaying = useStateFromStores([YoutubeMusicStore], () => YoutubeMusicStore.isPlaying); - const [shouldHide, setShouldHide] = useState(false); - - - React.useEffect(() => { - setShouldHide(false); - if (!isPlaying) { - const timeout = setTimeout(() => setShouldHide(true), 1000 * 60 * 5); - return () => clearTimeout(timeout); - } - }, [isPlaying]); - - if (!track || shouldHide) return; - - const exportTrackImageStyle = { - "--vc-ytm-track-image": `url(${track?.imageSrc || ""})`, - } as React.CSSProperties; - - return ( -
- - - -
- ); -} - diff --git a/src/equicordplugins/youtubeMusicControls/README.md b/src/equicordplugins/youtubeMusicControls/README.md deleted file mode 100644 index 8ad40cd85..000000000 --- a/src/equicordplugins/youtubeMusicControls/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# YouTubeMusicControls - -Adds a YouTube Music player above the account panel. -Shows the current playing playing track and adds controls same, -basicly the same as SpotifyControls - -# Prerequirements - -This only works with [th-ch's YouTube Music](https://github.com/th-ch/youtube-music) -and my plugin [ApiWebsocket Plugin](https://github.com/Johannes7k75/youtube-music/tree/feat/api-websocket) installed (Currently not included in [th-ch's YouTube Music](https://github.com/th-ch/youtube-music)) -Plugins that have to be enabled -- API Server -- API Websocket - -# Installation -- [git](https://git-scm.com/downloads) -- [node.js](https://nodejs.org/en/download) >= 22.12.0 -- [pnpm](https://pnpm.io/installation) - -```bash -git clone https://github.com/th-ch/youtube-music -cd .\youtube-music\ -git remote add johannes https://github.com/Johannes7k75/youtube-music.git -git fetch johannes -git merge johannes/feat/api-websocket -pnpm install --frozen-lockfile -pnpm dist:win -``` - - -See the [forum thread](https://discord.com/channels/1015060230222131221/1257038407503446176/1257038407503446176) / [Vencord docs](https://docs.vencord.dev/installing/custom-plugins/) \ No newline at end of file diff --git a/src/equicordplugins/youtubeMusicControls/YtmStore.ts b/src/equicordplugins/youtubeMusicControls/YtmStore.ts deleted file mode 100644 index ee6ff1388..000000000 --- a/src/equicordplugins/youtubeMusicControls/YtmStore.ts +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { Logger } from "@utils/Logger"; -import { proxyLazyWebpack } from "@webpack"; -import { Flux, FluxDispatcher } from "@webpack/common"; -import { Settings } from "Vencord"; - -enum MediaType { - /** - * Audio uploaded by the original artist - */ - Audio = "AUDIO", - /** - * Official music video uploaded by the original artist - */ - OriginalMusicVideo = "ORIGINAL_MUSIC_VIDEO", - /** - * Normal YouTube video uploaded by a user - */ - UserGeneratedContent = "USER_GENERATED_CONTENT", - /** - * Podcast episode - */ - PodcastEpisode = "PODCAST_EPISODE", - OtherVideo = "OTHER_VIDEO", -} - -interface Song { - title: string; - artist: string; - views: number; - uploadDate?: string; - imageSrc?: string | null; - isPaused?: boolean; - songDuration: number; - elapsedSeconds?: number; - url?: string; - album?: string | null; - videoId: string; - playlistId?: string; - mediaType: MediaType; -} - -export interface PlayerState { - song: Song | null; - isPlaying: boolean, - position: number, - repeat: Repeat, - volume: number, -} - -export type Repeat = "NONE" | "ONE" | "ALL"; - -const logger = new Logger("YoutubeMusicControls"); - -type Message = { type: "PLAYER_STATE"; } & PlayerState; - -class YoutubeMusicSocket { - public onChange: (e: Message) => void; - public ready = false; - - public socket: WebSocket | undefined; - - constructor(onChange: typeof this.onChange) { - this.reconnect(); - this.onChange = onChange; - } - - public reconnect() { - if (this.ready) return; - try { - this.initWs(); - } catch (e) { - logger.error("Failed to connect to YouTube Music WebSocket", e); - return; - } - this.ready = true; - } - - get routes() { - return { - "play": () => this.socket?.send(JSON.stringify({ type: "ACTION", action: "play" })), - "pause": () => this.socket?.send(JSON.stringify({ type: "ACTION", action: "pause" })), - "previous": () => this.socket?.send(JSON.stringify({ type: "ACTION", action: "previous" })), - "next": () => this.socket?.send(JSON.stringify({ type: "ACTION", action: "next" })), - "seek": (seconds: number) => this.socket?.send(JSON.stringify({ type: "ACTION", action: "seek", data: seconds })), - "shuffle": () => this.socket?.send(JSON.stringify({ type: "ACTION", action: "shuffle" })), - "setVolume": (percent: number) => this.socket?.send(JSON.stringify({ type: "ACTION", action: "setVolume", data: percent })), - "getVolume": () => this.socket?.send(JSON.stringify({ type: "ACTION", action: "getVolume" })), - "repeat": (repeat: Repeat) => this.socket?.send(JSON.stringify({ type: "ACTION", action: "repeat", data: repeat })), - }; - } - - private initWs() { - const url = Settings.plugins.YouTubeMusicControls.websocketUrl; - if (!url) { - return; - } - this.socket = new WebSocket(url); - - this.socket.addEventListener("open", () => { - this.ready = true; - this.routes.pause(); - this.routes.play(); - }); - - this.socket.addEventListener("error", e => { - // logger.error("YouTube Music Socket Error:", e); - if (!this.ready) setTimeout(() => this.reconnect(), 5_000); - this.onChange({ type: "PLAYER_STATE", song: null, isPlaying: false, position: 0, repeat: "NONE", volume: 0 }); - - }); - - this.socket.addEventListener("close", e => { - this.ready = false; - if (!this.ready) setTimeout(() => this.reconnect(), 10_000); - - this.onChange({ type: "PLAYER_STATE", song: null, isPlaying: false, position: 0, repeat: "NONE", volume: 0 }); - }); - - - this.socket.addEventListener("message", e => { - let message: Message; - try { - message = JSON.parse(e.data) as Message; - - switch (message.type) { - case "PLAYER_STATE": - this.onChange(message); - break; - } - } catch (err) { - logger.error("Invalid JSON:", err, `\n${e.data}`); - return; - } - }); - } -} - -export const YoutubeMusicStore = proxyLazyWebpack(() => { - const { Store } = Flux; - - class YoutubeMusicStore extends Store { - public mPosition = 0; - public start = 0; - - public song: Song | null = null; - public isPlaying = false; - public repeat: Repeat = "NONE"; - public shuffle = false; - public volume = 0; - - public socket = new YoutubeMusicSocket((message: Message) => { - if (message.song) { - store.song = message.song; - store.position = message.song.elapsedSeconds ?? 0; - } - if (message.isPlaying != null) store.isPlaying = message.isPlaying; - if (message.position && message.position !== store.position) store.position = message.position ?? 0; - if (message.volume) store.volume = message.volume ?? 0; - if (message.repeat) store.repeat = message.repeat; - - store.emitChange(); - }); - - public openExternal(path: string) { - const videoId = path.match(/watch\?v=([\w-]+)/); - - const url = (Settings.plugins.YouTubeMusicControls.useYoutubeMusicUri || Vencord.Plugins.isPluginEnabled("OpenInApp")) && videoId - ? encodeURI("youtubemusic://openVideo " + videoId[1]) - : "https://music.youtube.com" + path; - - VencordNative.native.openExternal(url); - } - - set position(p: number) { - this.mPosition = p * 1000; - this.start = Date.now(); - } - - get position(): number { - let pos = this.mPosition; - if (this.isPlaying) { - pos += Date.now() - this.start; - } - return pos; - } - - previous() { - if (!this.ensureSocketReady()) return; - this.socket.routes.previous(); - } - next() { - if (!this.ensureSocketReady()) return; - this.socket.routes.next(); - } - setVolume(percent: number) { - if (!this.ensureSocketReady()) return; - this.socket.routes.setVolume(Math.round(percent)); - this.volume = percent; - this.emitChange(); - } - setPlaying(playing: boolean) { - if (!this.ensureSocketReady()) return; - this.socket.routes[playing ? "play" : "pause"](); - this.isPlaying = playing; - } - setRepeat(state: Repeat) { - if (!this.ensureSocketReady()) return; - this.socket.routes.repeat(state); - this.repeat = state; - this.emitChange(); - } - setShuffle(state: boolean) { - if (!this.ensureSocketReady()) return; - this.socket.routes.shuffle(); - this.shuffle = state; - this.emitChange(); - } - seek(ms: number) { - if (!this.ensureSocketReady()) return; - this.socket.routes.seek(Math.round(ms / 1000)); - } - - public ensureSocketReady(): boolean { - if (!this.socket || !this.socket.ready) { - return false; - } - return true; - } - } - - const store = new YoutubeMusicStore(FluxDispatcher); - - return store; -}); diff --git a/src/equicordplugins/youtubeMusicControls/hoverOnly.css b/src/equicordplugins/youtubeMusicControls/hoverOnly.css deleted file mode 100644 index 29b14da0b..000000000 --- a/src/equicordplugins/youtubeMusicControls/hoverOnly.css +++ /dev/null @@ -1,16 +0,0 @@ -.vc-ytm-button-row { - height: 0; - opacity: 0; - pointer-events: none; - transition: 0.2s; - transition-property: height; -} - -#vc-ytm-player:hover .vc-ytm-button-row { - opacity: 1; - height: 32px; - pointer-events: auto; - - /* only transition opacity on show to prevent clipping */ - transition-property: height, opacity; -} diff --git a/src/equicordplugins/youtubeMusicControls/index.tsx b/src/equicordplugins/youtubeMusicControls/index.tsx deleted file mode 100644 index 4e746e5b3..000000000 --- a/src/equicordplugins/youtubeMusicControls/index.tsx +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { definePluginSettings, Settings } from "@api/Settings"; -import { disableStyle, enableStyle } from "@api/Styles"; -import ErrorBoundary from "@components/ErrorBoundary"; -import { Lyrics } from "@equicordplugins/spotifyLyrics/components/lyrics"; -import { EquicordDevs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; -import { Forms, MaskedLink } from "@webpack/common"; -import { Player as SpotifyPlayer } from "plugins/spotifyControls/PlayerComponent"; - -import hoverOnlyStyle from "./hoverOnly.css?managed"; -import { Player } from "./PlayerComponent"; - -function toggleHoverControls(value: boolean) { - (value ? enableStyle : disableStyle)(hoverOnlyStyle); -} - -function isUrlValid(value: string) { - try { - const url = new URL(value); - return url.protocol !== "" && url.host !== ""; - } catch (e) { - return false; - } -} - -/* -function InstallInstructions() { - return ( - - How to install - Instructions on how to install YouTube music with websocket plugin - - - - Software needed: git,node.js >= 22.12.0,pnpm - - - git clone https://github.com/th-ch/youtube-music
- cd .\youtube-music\
- git remote add johannes https://github.com/Johannes7k75/youtube-music.git
- git fetch johannes
- git merge johannes/feat/api-websocket
- pnpm install --frozen-lockfile
- pnpm dist:win
-
-
-
- ); -} -*/ - -function InstallInstructions() { - return ( - - How to install - Instructions on how to install YouTube music with websocket plugin - - - - Software needed: git,node.js >= 22.12.0,pnpm - - - git clone https://github.com/thororen1234/youtube-music.git
- cd .\youtube-music\
- pnpm install --frozen-lockfile
- pnpm dist:win
-
-
-
- ); -} - -const settings = definePluginSettings({ - installYtmWithWebsocket: { - type: OptionType.COMPONENT, - component: () => - }, - hoverControls: { - description: "Show controls on hover", - type: OptionType.BOOLEAN, - default: false, - onChange: v => toggleHoverControls(v) - }, - websocketUrl: { - description: "The websocket url", - type: OptionType.STRING, - placeholder: "ws://localhost:26539", - default: "ws://localhost:26539", - isValid: isUrlValid, - } -}); - -export default definePlugin({ - name: "YouTubeMusicControls", - description: "Adds a YouTube Music player above the account panel, requires `https://github.com/th-ch/youtube-music` with an custom websocket plugin. See Readme of ths Plugin for instructions", - authors: [EquicordDevs.Johannes7k75], - settings, - patches: [ - { - find: "this.isCopiedStreakGodlike", - replacement: [ - { - match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?userTag:\i,hidePrivateData:)/, - replace: "$self.PanelWrapper,{VencordOriginal:$1,", - predicate: () => !Settings.plugins.SpotifyControls.enabled, - noWarn: true, - }, - { - match: /Vencord\.Plugins\.plugins\["SpotifyLyrics"\]\.FakePanelWrapper/, - replace: "$self.PanelWrapper", - predicate: () => Settings.plugins.SpotifyLyrics.enabled, - noWarn: true, - }, - { - match: /Vencord\.Plugins\.plugins\["SpotifyControls"\]\.PanelWrapper/, - replace: "$self.PanelWrapper", - predicate: () => Settings.plugins.SpotifyControls.enabled && !Settings.plugins.SpotifyLyrics.enabled, - noWarn: true, - } - ], - }, - ], - - start: () => toggleHoverControls(Settings.plugins.YouTubeMusicControls.hoverControls), - - PanelWrapper({ VencordOriginal, ...props }) { - const showSpotifyControls = Settings.plugins.SpotifyControls.enabled; - const showSpotifyLyrics = Settings.plugins.SpotifyLyrics.enabled; - const LyricsPosition = showSpotifyLyrics ? Settings.plugins.SpotifyLyrics.LyricsPosition : null; - - return ( - <> - ( -
-

Failed to render YouTube Music Modal :(

-

Check the console for errors

-
- )} - > - - - {showSpotifyLyrics && LyricsPosition === "above" && } - {showSpotifyControls && } - {showSpotifyLyrics && LyricsPosition === "below" && } -
- - - - ); - } -}); diff --git a/src/equicordplugins/youtubeMusicControls/ytmStyles.css b/src/equicordplugins/youtubeMusicControls/ytmStyles.css deleted file mode 100644 index 652f90bf3..000000000 --- a/src/equicordplugins/youtubeMusicControls/ytmStyles.css +++ /dev/null @@ -1,208 +0,0 @@ -#vc-ytm-player { - padding: 12px; - background: var(--bg-overlay-floating, var(--background-base-low, var(--background-base-lower-alt))); - margin: 0; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - - --vc-ytm-slider-gradient: linear-gradient(to right, #f03 80%, #ff2791 100%); - --vc-ytm-slider-grabber: #f03; - - /* so custom themes can easily change it */ - --vc-ytm-red-90: color-mix(in hsl, var(--vc-ytm-slider-grabber), transparent 90%); - --vc-ytm-red-80: color-mix(in hsl, var(--vc-ytm-slider-grabber), transparent 80%); -} - -.vc-ytm-song-info-prefix { - display: none; -} - -.vc-ytm-artist, -.vc-ytm-album { - color: var(--header-primary); -} - -.vc-ytm-secondary-song-info { - font-size: 12px; -} - -#vc-ytm-progress-bar { - position: relative; - color: var(--text-default); - width: 100%; - margin: 0.5em 0; - margin-bottom: 5px; -} - -#vc-ytm-progress-bar > [class^="slider"] { - flex-grow: 1; - width: 100%; - padding: 0 !important; -} - -#vc-ytm-progress-bar > [class^="slider"] [class^="bar"] { - height: 3px !important; - top: calc(12px - 4px / 2 + var(--bar-offset)); -} - -#vc-ytm-progress-bar > [class^="slider"] [class^="barFill"] { - background-image: var(--vc-ytm-slider-gradient); -} - -#vc-ytm-progress-bar > [class^="slider"]:hover [class^="barFill"] { - background-image: var(--vc-ytm-slider-gradient); -} - -#vc-ytm-progress-bar > [class^="slider"] [class^="grabber"] { - background-color: var(--vc-ytm-slider-grabber); - width: 12px !important; - height: 12px !important; - margin-top: calc(13px/-2 + var(--bar-offset)/2); - margin-left: -0.5px; - border: unset; - color: var(--interactive-normal); - opacity: 0; - transition: opacity 0.1s; -} - -#vc-ytm-progress-bar:hover > [class^="slider"] [class^="grabber"] { - opacity: 1; -} - -.vc-ytm-progress-time { - margin-top: 8px; - font-family: var(--font-code); - font-size: 12px; - top: 10px; - position: absolute; -} - -.vc-ytm-button-row { - justify-content: center; - margin-top: 14px; -} - -.vc-ytm-button { - margin: 0 2px; - border-radius: var(--radius-sm); - background: none; - color: var(--interactive-normal); - padding: 0; - width: 32px; - height: 32px; - display: flex; - justify-content: center; - align-items: center; - position: relative; -} - -.vc-ytm-button:hover { - color: var(--interactive-hover); - background-color: var(--background-modifier-selected); -} - -.vc-ytm-button svg { - height: 24px; - width: 24px; -} - -[class*="shuffle"] > svg, -[class*="repeat"] > svg { - width: 22px; - height: 22px; -} - -.vc-ytm-button svg path { - width: 100%; - height: 100%; -} - - -.vc-ytm-repeat-context, -.vc-ytm-repeat-track, -.vc-ytm-shuffle-on { - background-color: var(--vc-ytm-red-90); -} - -.vc-ytm-repeat-context:hover, -.vc-ytm-repeat-track:hover, -.vc-ytm-shuffle-on:hover { - background-color: var(--vc-ytm-red-80); -} - -.vc-ytm-repeat-1 { - font-size: 70%; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -#vc-ytm-info-wrapper { - display: flex; - flex-direction: row; - align-items: center; - height: 3em; - gap: 0.5em; -} - -#vc-ytm-info-wrapper img { - height: 90%; - object-fit: contain; -} - -#vc-ytm-album-expanded-wrapper img { - width: 100%; - object-fit: contain; -} - -#vc-ytm-titles { - display: flex; - flex-direction: column; - padding: 0.2rem; - align-items: flex-start; - place-content: flex-start center; - overflow: hidden; -} - -#vc-ytm-song-title { - color: var(--header-primary); - font-size: 14px; - font-weight: 600; -} - -.vc-ytm-ellipoverflow { - white-space: nowrap; - overflow: hidden; - width: 100%; - text-overflow: ellipsis; -} - -.vc-ytm-artist[role="link"]:hover, -#vc-ytm-album-title[role="link"]:hover, -#vc-ytm-song-title[role="link"]:hover { - text-decoration: underline; - cursor: pointer; -} - -#vc-ytm-album-image { - border-radius: 3px; - transition: filter 0.2s; -} - -#vc-ytm-album-image:hover { - filter: brightness(1.2); - cursor: pointer; -} - -#vc-ytm-progress-text { - margin: 0; -} - -.vc-ytm-time-left { - left: 0; -} - -.vc-ytm-time-right { - right: 0; -} \ No newline at end of file