From 7a546afa1f88116cbddc182b78a4385c153dffb5 Mon Sep 17 00:00:00 2001 From: nexpid Date: Tue, 3 Mar 2026 21:18:20 +0100 Subject: [PATCH] SongSpotlight: UI updates (#868) * feat(SongSpotlight): mostly UI updates! - new compact settings - css fixes - & more! * fix(SongSpotlight): dont really need the filter * feat(SongSpotlight): update the wording --- .../songSpotlight.desktop/index.tsx | 18 +-- .../songSpotlight.desktop/settings.tsx | 34 ++++++ .../songSpotlight.desktop/style.css | 23 ++-- .../songSpotlight.desktop/ui/common.tsx | 8 ++ .../ui/songs/CollapsedProfileSongs.tsx | 103 ++++++++++++++++++ .../ui/songs/ProfileSongs.tsx | 68 ++++++++++-- .../songSpotlight.desktop/ui/songs/index.tsx | 2 +- 7 files changed, 223 insertions(+), 33 deletions(-) create mode 100644 src/equicordplugins/songSpotlight.desktop/settings.tsx create mode 100644 src/equicordplugins/songSpotlight.desktop/ui/songs/CollapsedProfileSongs.tsx diff --git a/src/equicordplugins/songSpotlight.desktop/index.tsx b/src/equicordplugins/songSpotlight.desktop/index.tsx index 83ecdf203..ed8d84c12 100644 --- a/src/equicordplugins/songSpotlight.desktop/index.tsx +++ b/src/equicordplugins/songSpotlight.desktop/index.tsx @@ -6,15 +6,14 @@ import "./style.css"; -import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { EquicordDevs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin from "@utils/types"; import { useAuthorizationStore } from "./lib/stores/AuthorizationStore"; import { useSongStore } from "./lib/stores/SongStore"; import { Native } from "./service"; -import Settings from "./ui/settings"; +import settings from "./settings"; import ProfileSongs from "./ui/songs/ProfileSongs"; import WidgetSongs from "./ui/songs/WidgetSongs"; @@ -22,12 +21,7 @@ export default definePlugin({ name: "SongSpotlight", description: "Show off songs on your profile", authors: [EquicordDevs.nexpid], - settings: definePluginSettings({ - manager: { - type: OptionType.COMPONENT, - component: () => , - }, - }), + settings, patches: [ // Personal profile popout { @@ -50,7 +44,7 @@ export default definePlugin({ find: ".SIDEBAR}),nicknameIcons:", replacement: { match: /{userId:(\i)\.id}\)}\).{0,100}]}\)(?=\]\}\))/, - replace: "$&,$self.renderProfileSongs({userId:$1.id})", + replace: "$&,$self.renderProfileSongs({userId:$1.id,isSidebar:true})", }, }, // Full profile modal sections (lazy loaded) @@ -58,14 +52,14 @@ export default definePlugin({ find: ".MUTUAL_GUILDS})),", replacement: { match: /(\i).push\({text.{0,50}}\);/, - replace: "$&$1.push({text:\"Song Spotlight\",section:\"SONG_SPOTLIGHT\"});", + replace: '$&$1.push({text:"Song Spotlight",section:"SONG_SPOTLIGHT"});', }, }, { find: ".hasUnsavedChanges()&&", replacement: { match: /({user:(\i),.{0,80}return (\i===))/, - replace: "$1\"SONG_SPOTLIGHT\"?$self.renderWidgetSongs({user:$2}):$3", + replace: '$1"SONG_SPOTLIGHT"?$self.renderWidgetSongs({user:$2}):$3', }, }, ], diff --git a/src/equicordplugins/songSpotlight.desktop/settings.tsx b/src/equicordplugins/songSpotlight.desktop/settings.tsx new file mode 100644 index 000000000..2819a7adb --- /dev/null +++ b/src/equicordplugins/songSpotlight.desktop/settings.tsx @@ -0,0 +1,34 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2026 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { definePluginSettings } from "@api/Settings"; +import { makeRange, OptionType } from "@utils/types"; + +import Settings from "./ui/settings"; + +export default definePluginSettings({ + collapseSongList: { + type: OptionType.BOOLEAN, + description: "Collapses the song list on user profiles to a button which opens a separate menu", + default: false, + }, + profileSongsLimit: { + type: OptionType.SLIDER, + description: "How many songs are shown when initially clicking on a user", + default: 6, + markers: makeRange(1, 6), + }, + manager: { + type: OptionType.COMPONENT, + component: () => , + }, +}, { + profileSongsLimit: { + disabled() { + return this.store.collapseSongList; + }, + }, +}); diff --git a/src/equicordplugins/songSpotlight.desktop/style.css b/src/equicordplugins/songSpotlight.desktop/style.css index 42f8cb293..bb44f9aaa 100644 --- a/src/equicordplugins/songSpotlight.desktop/style.css +++ b/src/equicordplugins/songSpotlight.desktop/style.css @@ -23,7 +23,10 @@ -webkit-line-clamp: 1; line-clamp: 1; -webkit-box-orient: vertical; + min-width: 0; + max-width: 100%; overflow: hidden; + text-overflow: ellipsis; } /* components */ @@ -137,13 +140,13 @@ --list-height: calc(var(--base-height) + 80px); --number-size: 32px; - padding: 8px; - gap: 12px; - overflow: visible; -} + display: flex; + flex-direction: column; + gap: 6px; -.user-profile-sidebar .vc-songspotlight-songs-container { - padding: 12px; + &.vc-songspotlight-songs-container-sidebar { + padding: 12px; + } } .vc-songspotlight-songs-more { @@ -194,6 +197,10 @@ } } +.user-profile-sidebar .vc-songspotlight-song-container { + padding: 8px; +} + .user-profile-popout:not(.custom-user-profile-theme) .vc-songspotlight-song-container { background: var(--opacity-16); } @@ -211,7 +218,6 @@ grid-template-columns: 1fr auto; grid-template-rows: 1fr auto; gap: 8px; - overflow: visible; border-radius: inherit; } @@ -220,7 +226,7 @@ grid-column: 1; grid-row: 1 / 3; border-radius: inherit; - overflow: visible; + min-width: 0; } .vc-songspotlight-song-cover { @@ -232,7 +238,6 @@ flex-shrink: 0; box-shadow: 0 3px 12px 0 rgb(0 0 0 / 16%); transition: 0.18s transform; - overflow: visible; &:hover { transform: scale(1.03); diff --git a/src/equicordplugins/songSpotlight.desktop/ui/common.tsx b/src/equicordplugins/songSpotlight.desktop/ui/common.tsx index 0e5454fad..eda340ce4 100644 --- a/src/equicordplugins/songSpotlight.desktop/ui/common.tsx +++ b/src/equicordplugins/songSpotlight.desktop/ui/common.tsx @@ -12,6 +12,14 @@ import { Tooltip } from "@webpack/common"; import { ComponentType, HTMLAttributes, JSX } from "react"; export const OverlayClasses = findCssClassesLazy("overlay", "inner", "outer"); +export const ContainerClasses = findCssClassesLazy( + "breadcrumb", + "innerContainer", + "icons", + "icon", + "displayCount", + "displayCountText", +); export const CardClasses = findCssClassesLazy("card", "headerText", "headerContextMenu"); export const WidgetClasses = findCssClassesLazy("tabPanelScroller", "tabListScroller"); diff --git a/src/equicordplugins/songSpotlight.desktop/ui/songs/CollapsedProfileSongs.tsx b/src/equicordplugins/songSpotlight.desktop/ui/songs/CollapsedProfileSongs.tsx new file mode 100644 index 000000000..52fa56ab1 --- /dev/null +++ b/src/equicordplugins/songSpotlight.desktop/ui/songs/CollapsedProfileSongs.tsx @@ -0,0 +1,103 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2026 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { BaseText } from "@components/index"; +import { Native } from "@equicordplugins/songSpotlight.desktop/service"; +import { CardClasses, ContainerClasses, OverlayClasses, Spinner } from "@equicordplugins/songSpotlight.desktop/ui/common"; +import { RenderSongInfo } from "@song-spotlight/api/handlers"; +import { UserData } from "@song-spotlight/api/structs"; +import { sid } from "@song-spotlight/api/util"; +import { classes } from "@utils/index"; +import { + SelectedChannelStore, + SelectedGuildStore, + useEffect, + useMemo, + UserProfileActions, + UserStore, + useState, +} from "@webpack/common"; + +import { ProfileSongsProps } from "./ProfileSongs"; + +const shownSongs = 4; + +interface CollapsedProfileSongsProps extends ProfileSongsProps { + data?: UserData; +} + +export default function CollapsedProfileSongs({ data, userId, isSidebar }: CollapsedProfileSongsProps) { + const [renders, setRenders] = useState(new Map()); + const previews = useMemo(() => data?.slice(0, shownSongs), [data]); + + useEffect(() => { + setRenders(new Map()); + if (!previews) return; + + for (const song of previews) { + Native.renderSong(song) + .catch(() => null) + .then(info => { + if (!info) return; + setRenders(renders => new Map(renders).set(sid(song), info)); + }); + } + }, [previews]); + + return ( +
{ + const user = UserStore.getUser(userId); + if (!user) return; + + const guildId = SelectedGuildStore.getGuildId(); + UserProfileActions.openUserProfileModal({ + userId, + guildId, + channelId: SelectedChannelStore.getChannelId(), + analyticsLocation: { + page: guildId ? "Guild Channel" : "DM Channel", + section: "Profile Popout", + }, + tabSection: "SONG_SPOTLIGHT", + }); + }} + > +
+ Song Spotlight +
+ {previews && data + ? previews.map((song, i) => { + const render = renders.get(sid(song)); + const extra = i === shownSongs - 1 && data.length > shownSongs; + + return ( +
+ {render?.thumbnailUrl && ( + {render.label} + )} + {extra && ( +
+ +{data.length - shownSongs} +
+ )} +
+ ); + }) + : } +
+
+
+ ); +} diff --git a/src/equicordplugins/songSpotlight.desktop/ui/songs/ProfileSongs.tsx b/src/equicordplugins/songSpotlight.desktop/ui/songs/ProfileSongs.tsx index 9bc46c95f..8137349c4 100644 --- a/src/equicordplugins/songSpotlight.desktop/ui/songs/ProfileSongs.tsx +++ b/src/equicordplugins/songSpotlight.desktop/ui/songs/ProfileSongs.tsx @@ -7,10 +7,12 @@ import { BaseText } from "@components/BaseText"; import { Flex } from "@components/Flex"; import { LinkIcon, PencilIcon } from "@components/Icons"; +import { Link } from "@components/Link"; import { listData } from "@equicordplugins/songSpotlight.desktop/lib/api"; import { useAuthorizationStore } from "@equicordplugins/songSpotlight.desktop/lib/stores/AuthorizationStore"; import { useSongStore } from "@equicordplugins/songSpotlight.desktop/lib/stores/SongStore"; import { cl } from "@equicordplugins/songSpotlight.desktop/lib/utils"; +import settings from "@equicordplugins/songSpotlight.desktop/settings"; import { CardClasses, MoreHorizontalIcon, @@ -20,40 +22,75 @@ import { import { openSettingsModal } from "@equicordplugins/songSpotlight.desktop/ui/settings"; import { sid } from "@song-spotlight/api/util"; import { copyWithToast } from "@utils/discord"; -import { classes } from "@utils/misc"; -import { ContextMenuApi, FluxDispatcher, Menu, React, Tooltip, useEffect, UserStore, useState } from "@webpack/common"; +import { classes } from "@utils/index"; +import { + ContextMenuApi, + FluxDispatcher, + Menu, + React, + Tooltip, + useEffect, + useMemo, + UserStore, + useState, +} from "@webpack/common"; import Song from "."; +import CollapsedProfileSongs from "./CollapsedProfileSongs"; -interface ProfileSongsProps { +export interface ProfileSongsProps { userId: string; + isSidebar: boolean; } -export default function ProfileSongs({ userId }: ProfileSongsProps) { +export default function ProfileSongs({ userId, isSidebar }: ProfileSongsProps) { const [failed, setFailed] = useState(false); const { isAuthorized } = useAuthorizationStore(); const { users } = useSongStore(); + const { profileSongsLimit, collapseSongList } = settings.use(); - const data = users[userId]?.data, at = users[userId]?.at; + const data = users[userId]?.data; useEffect(() => { if (isAuthorized() && !data) listData(userId).catch(() => setFailed(true)); }, [isAuthorized()]); + const [clamped, setClamped] = useState(true); + const clampedData = useMemo(() => data && (clamped ? data.slice(0, profileSongsLimit) : data), [ + data, + clamped, + profileSongsLimit, + ]); + const owned = UserStore.getCurrentUser().id === userId; - if (isAuthorized() && !data && !failed) { + const pending = isAuthorized() && !clampedData && !data && !failed; + if (!pending && !clampedData?.[0]) return null; + + if (collapseSongList) { + return ( + + ); + } else if (pending) { return ; - } else if (!data?.[0]) { - return null; } return (
- Song Spotlight + + Song Spotlight + {props => ( - {data.map((song, i) => )} + {clampedData.map((song, i) => )} + {clamped && data.length > profileSongsLimit && ( + setClamped(false)} + > + + Show {data.length - profileSongsLimit} more + + + )}
); } diff --git a/src/equicordplugins/songSpotlight.desktop/ui/songs/index.tsx b/src/equicordplugins/songSpotlight.desktop/ui/songs/index.tsx index ee42b87d6..a1fd51434 100644 --- a/src/equicordplugins/songSpotlight.desktop/ui/songs/index.tsx +++ b/src/equicordplugins/songSpotlight.desktop/ui/songs/index.tsx @@ -187,7 +187,7 @@ function SongInfo({ owned, song, render, big }: SongInfoProps) { icon={LinkIcon} action={() => copyWithToast(render.link)} /> - {owned + {!owned ? (