ProfileCollectionsAPI: Add & Migrate
This commit is contained in:
parent
9e12799d1d
commit
f00ddd41e5
13 changed files with 227 additions and 155 deletions
|
|
@ -42,6 +42,7 @@ export { Plugins as plugins };
|
|||
import { traceFunction } from "../debug/Tracer";
|
||||
import { addAudioProcessor, removeAudioProcessor } from "./AudioPlayer";
|
||||
import { addChannelToolbarButton, addHeaderBarButton, removeChannelToolbarButton, removeHeaderBarButton } from "./HeaderBar";
|
||||
import { addProfileCollection, removeProfileCollection } from "./ProfileCollections";
|
||||
import { addUserAreaButton, removeUserAreaButton } from "./UserArea";
|
||||
|
||||
const logger = new Logger("PluginManager", "#a6d189");
|
||||
|
|
@ -196,7 +197,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
|||
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
|
||||
renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton,
|
||||
// Custom
|
||||
renderNicknameIcon, headerBarButton, audioProcessor, userAreaButton
|
||||
renderNicknameIcon, headerBarButton, audioProcessor, userAreaButton, renderProfileCollections
|
||||
} = p;
|
||||
|
||||
if (p.start) {
|
||||
|
|
@ -267,6 +268,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
|||
}
|
||||
if (audioProcessor) addAudioProcessor(name, audioProcessor);
|
||||
if (userAreaButton) addUserAreaButton(name, userAreaButton.render, userAreaButton.priority);
|
||||
if (renderProfileCollections) addProfileCollection(name, renderProfileCollections, userAreaButton?.priority);
|
||||
|
||||
return true;
|
||||
}, p => `startPlugin ${p.name}`);
|
||||
|
|
@ -277,7 +279,7 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
|
|||
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
|
||||
renderChatBarButton, chatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton, messagePopoverButton,
|
||||
// Custom
|
||||
renderNicknameIcon, headerBarButton, audioProcessor, userAreaButton
|
||||
renderNicknameIcon, headerBarButton, audioProcessor, userAreaButton, renderProfileCollections
|
||||
} = p;
|
||||
|
||||
if (p.stop) {
|
||||
|
|
@ -342,6 +344,7 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
|
|||
}
|
||||
if (audioProcessor) removeAudioProcessor(name);
|
||||
if (userAreaButton) removeUserAreaButton(name);
|
||||
if (renderProfileCollections) removeProfileCollection(name);
|
||||
|
||||
return true;
|
||||
}, p => `stopPlugin ${p.name}`);
|
||||
|
|
@ -354,7 +357,7 @@ export const initPluginManager = onlyOnce(function init() {
|
|||
"onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick",
|
||||
"renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton",
|
||||
// Custom
|
||||
"renderNicknameIcon"
|
||||
"renderNicknameIcon", "renderProfileCollections"
|
||||
];
|
||||
|
||||
const neededApiPlugins = new Set<string>();
|
||||
|
|
@ -396,6 +399,7 @@ export const initPluginManager = onlyOnce(function init() {
|
|||
if (p.headerBarButton) neededApiPlugins.add("HeaderBarAPI");
|
||||
if (p.audioProcessor) neededApiPlugins.add("AudioPlayerAPI");
|
||||
if (p.userAreaButton) neededApiPlugins.add("UserAreaAPI");
|
||||
if (p.renderProfileCollections) neededApiPlugins.add("ProfileCollectionsAPI");
|
||||
|
||||
for (const key of pluginKeysToBind) {
|
||||
p[key] &&= p[key].bind(p) as any;
|
||||
|
|
|
|||
79
src/api/ProfileCollections.tsx
Normal file
79
src/api/ProfileCollections.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { useEffect, useState } from "@webpack/common";
|
||||
import type { JSX } from "react";
|
||||
|
||||
const logger = new Logger("ProfileCollectionAPI");
|
||||
|
||||
export type ProfileCollectionFactory = Function;
|
||||
|
||||
export interface ProfileCollectionData {
|
||||
/** Function that renders the collection component, receives the full profile props */
|
||||
render: ProfileCollectionFactory;
|
||||
/** Higher priority collections appear first in the list. Default: 0 */
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
interface CollectionEntry {
|
||||
render: ProfileCollectionFactory;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
const profileCollections = new Map<string, CollectionEntry>();
|
||||
const profileCollectionListeners = new Set<() => void>();
|
||||
|
||||
/**
|
||||
* Adds a collection to the user profile panel.
|
||||
*
|
||||
* @param id - Unique identifier for the collection (e.g., "my-plugin-profile-collection")
|
||||
* @param render - Function that returns the collection JSX. Receives the full profile props object.
|
||||
* @param priority - Higher values appear first. Default: 0
|
||||
*
|
||||
* @example
|
||||
* addProfileCollection("my-collection", (props) => (
|
||||
* <div>Custom content for {props.user.id}</div>
|
||||
* ));
|
||||
*/
|
||||
export function addProfileCollection(id: string, render: ProfileCollectionFactory, priority = 0) {
|
||||
profileCollections.set(id, { render, priority });
|
||||
profileCollectionListeners.forEach(listener => listener());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a collection from the user profile panel.
|
||||
*
|
||||
* @param id - The identifier used when adding the collection
|
||||
*/
|
||||
export function removeProfileCollection(id: string) {
|
||||
profileCollections.delete(id);
|
||||
profileCollectionListeners.forEach(listener => listener());
|
||||
}
|
||||
|
||||
function ProfileCollections({ props }: { props: any; }) {
|
||||
const [, forceUpdate] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = () => forceUpdate(n => n + 1);
|
||||
profileCollectionListeners.add(listener);
|
||||
return () => { profileCollectionListeners.delete(listener); };
|
||||
}, []);
|
||||
|
||||
return Array.from(profileCollections)
|
||||
.sort(([, a], [, b]) => b.priority - a.priority)
|
||||
.map(([id, { render: Collection }]) => (
|
||||
<ErrorBoundary noop key={id} onError={e => logger.error(`Failed to render profile collection: ${id}`, e.error)}>
|
||||
{Collection({ ...props, user: props.user ?? props.currentUser })}
|
||||
</ErrorBoundary>
|
||||
));
|
||||
}
|
||||
|
||||
/** @internal Injected by ProfileCollectionAPI patch (do NOT call directly) */
|
||||
export function renderProfileCollections(props: any) {
|
||||
return <ProfileCollections props={props} />;
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@ import * as $Notices from "./Notices";
|
|||
import * as $Notifications from "./Notifications";
|
||||
import * as $UserArea from "./UserArea";
|
||||
export * as PluginManager from "./PluginManager";
|
||||
import * as $ProfileCollections from "./ProfileCollections";
|
||||
import * as $ServerList from "./ServerList";
|
||||
import * as $Settings from "./Settings";
|
||||
import * as $Styles from "./Styles";
|
||||
|
|
@ -170,3 +171,8 @@ export const UserArea = $UserArea;
|
|||
* Just used to identify if user is on Equicord as Vencord doesn't have this.
|
||||
*/
|
||||
export const isEquicord = true;
|
||||
|
||||
/**
|
||||
* An API allowing you to add other collections where discords game collection is.
|
||||
*/
|
||||
export const ProfileCollections = $ProfileCollections;
|
||||
|
|
|
|||
37
src/equicordplugins/_api/profileCollections.ts
Normal file
37
src/equicordplugins/_api/profileCollections.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2025 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "ProfileCollectionsAPI",
|
||||
description: "API to add collections to the user profile panel like discords game collection.",
|
||||
authors: [Devs.thororen],
|
||||
patches: [
|
||||
{
|
||||
find: /\.POPOUT,onClose:\i}\),nicknameIcons:.+?\.isProvisional/,
|
||||
replacement: {
|
||||
match: /user:\i,widgets:.{0,100}?\}\),/,
|
||||
replace: "$&Vencord.Api.ProfileCollections.renderProfileCollections(arguments[0]),",
|
||||
}
|
||||
},
|
||||
{
|
||||
find: '"UserProfileAccountPopout"',
|
||||
replacement: {
|
||||
match: /user:\i,widgets:.{0,100}}\),/,
|
||||
replace: "$&Vencord.Api.ProfileCollections.renderProfileCollections(arguments[0]),",
|
||||
},
|
||||
},
|
||||
{
|
||||
find: ".SIDEBAR,disableToolbar:",
|
||||
replacement: {
|
||||
match: /user:(\i),widgets:.{0,100}?\}\),/,
|
||||
replace: "$&Vencord.Api.ProfileCollections.renderProfileCollections({...arguments[0],isSideBar:true}),"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
@ -15,6 +15,7 @@ import { Clickable, React, useEffect, UserProfileStore, useState } from "@webpac
|
|||
|
||||
import { ReposModal } from "./ReposModal";
|
||||
|
||||
const DMSideBarClasses = findCssClassesLazy("widgetPreviews");
|
||||
const ProfileCardClasses = findCssClassesLazy("cardsList", "firstCardContainer", "card", "container");
|
||||
const ProfileCardContainerClasses = findCssClassesLazy("innerContainer", "icons", "icon", "displayCount", "displayCountText", "displayCountTextColor", "breadcrumb");
|
||||
const ProfileCardOverlayClasses = findCssClassesLazy("overlay", "isPrivate", "outer");
|
||||
|
|
@ -41,7 +42,7 @@ function getLanguageIconUrl(language: string | null): string {
|
|||
return `https://cdn.jsdelivr.net/gh/devicons/devicon@develop/icons/${normalized}/${normalized}-original.svg`;
|
||||
}
|
||||
|
||||
export function ProfilePopoutComponent({ id, theme }: { id: string, theme: string; }) {
|
||||
export function ProfilePopoutComponent({ id, isSideBar = false }: { id: string, isSideBar?: boolean; }) {
|
||||
const [repos, setRepos] = useState<GitHubRepo[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
|
@ -92,13 +93,13 @@ export function ProfilePopoutComponent({ id, theme }: { id: string, theme: strin
|
|||
|
||||
const topRepos = repos.slice(0, 4);
|
||||
|
||||
return (
|
||||
const reposSection = (
|
||||
<section className={ProfileCardClasses.container}>
|
||||
<ul className={ProfileCardClasses.cardsList} tabIndex={-1}>
|
||||
<li className={ProfileCardClasses.firstCardContainer}>
|
||||
<Clickable className={ProfileCardContainerClasses.breadcrumb} onClick={openReposModal}>
|
||||
<div className={classes(ProfileCardOverlayClasses.overlay, ProfileCardContainerClasses.innerContainer, ProfileCardClasses.card)}>
|
||||
<Paragraph size="xs" weight="medium">
|
||||
<Paragraph size={isSideBar ? "sm" : "xs"} weight="medium">
|
||||
GitHub Repositories
|
||||
</Paragraph>
|
||||
{!!repos.length && (
|
||||
|
|
@ -115,8 +116,8 @@ export function ProfilePopoutComponent({ id, theme }: { id: string, theme: strin
|
|||
/>
|
||||
{showCount && (
|
||||
<div className={ProfileCardContainerClasses.displayCountText}>
|
||||
<Span className={ProfileCardContainerClasses.displayCountTextColor} size="xs" weight="medium">
|
||||
+{repos.length - 3}
|
||||
<Span className={ProfileCardContainerClasses.displayCountTextColor} size={isSideBar ? "sm" : "xs"} weight="medium">
|
||||
+{repos.length - 4}
|
||||
</Span>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -131,4 +132,8 @@ export function ProfilePopoutComponent({ id, theme }: { id: string, theme: strin
|
|||
</ul>
|
||||
</section>
|
||||
);
|
||||
|
||||
return isSideBar
|
||||
? <div className={DMSideBarClasses.widgetPreviews}>{reposSection}</div>
|
||||
: reposSection;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,11 +33,6 @@ export const settings = definePluginSettings({
|
|||
description: "Show repository language",
|
||||
default: true
|
||||
},
|
||||
showRepositoryTab: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show repositories tab in profile modal (hides button in connections when enabled)",
|
||||
default: true
|
||||
},
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
|
|
@ -47,30 +42,12 @@ export default definePlugin({
|
|||
settings,
|
||||
|
||||
patches: [
|
||||
// User Popout
|
||||
{
|
||||
find: /onOpenUserProfileModal:\i\}\),\i/,
|
||||
replacement: {
|
||||
match: /user:\i,widgets:.{0,100}?\}\),/,
|
||||
replace: "$&$self.ProfileRepositoriesPopout(arguments[0]),"
|
||||
}
|
||||
},
|
||||
// User Profile Modal v2
|
||||
{
|
||||
find: ".MODAL_V2,onClose:",
|
||||
replacement: {
|
||||
match: /displayProfile:(\i).*?connections:\i.{0,25}\i.\i\}\)\}\)/,
|
||||
replace: "$&,$self.ProfileRepositoriesPopout({ user: arguments[0].user, displayProfile: $1 }),",
|
||||
predicate: () => !settings.store.showRepositoryTab,
|
||||
}
|
||||
},
|
||||
// User Profile Modal v2 tab bar
|
||||
{
|
||||
find: "#{intl::USER_PROFILE_ACTIVITY}",
|
||||
replacement: {
|
||||
match: /\.MUTUAL_GUILDS\}\)\)(?=,(\i))/,
|
||||
replace: '$&,$1.push({text:"GitHub",section:"GITHUB"})',
|
||||
predicate: () => settings.store.showRepositoryTab,
|
||||
}
|
||||
},
|
||||
// User Profile Modal v2 tab content
|
||||
|
|
@ -78,24 +55,19 @@ export default definePlugin({
|
|||
find: ".WIDGETS?",
|
||||
replacement: {
|
||||
match: /(\i)===\i\.\i\.WISHLIST/,
|
||||
replace: '$1==="GITHUB"?$self.ProfileRepositoriesTab(arguments[0]):$&'
|
||||
replace: '$1==="GITHUB"?$self.renderProfileRepositoriesTab(arguments[0]):$&',
|
||||
}
|
||||
}
|
||||
],
|
||||
ProfileRepositoriesPopout: ErrorBoundary.wrap((props: { user: User; displayProfile?: any; }) => {
|
||||
renderProfileCollections: ErrorBoundary.wrap((props: { user: User; displayProfile?: any; }) => {
|
||||
return (
|
||||
<ProfilePopoutComponent
|
||||
{...props}
|
||||
id={props.user.id}
|
||||
theme={getProfileThemeProps(props).theme}
|
||||
/>
|
||||
);
|
||||
},
|
||||
{
|
||||
noop: true
|
||||
}
|
||||
),
|
||||
ProfileRepositoriesTab: ErrorBoundary.wrap((props: { user: User; displayProfile?: any; }) => {
|
||||
}, { noop: true }),
|
||||
renderProfileRepositoriesTab: ErrorBoundary.wrap((props: { user: User; displayProfile?: any; }) => {
|
||||
return (
|
||||
<ProfileTabComponent
|
||||
{...props}
|
||||
|
|
@ -103,9 +75,5 @@ export default definePlugin({
|
|||
theme={getProfileThemeProps(props).theme}
|
||||
/>
|
||||
);
|
||||
},
|
||||
{
|
||||
noop: true
|
||||
}
|
||||
)
|
||||
}, { noop: true }),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,30 +23,6 @@ export default definePlugin({
|
|||
authors: [EquicordDevs.nexpid],
|
||||
settings,
|
||||
patches: [
|
||||
// Personal profile popout
|
||||
{
|
||||
find: '"UserProfileAccountPopout"',
|
||||
replacement: {
|
||||
match: /user:(\i),bio:.{0,60}}\)/,
|
||||
replace: "$&,$self.renderProfileSongs({userId:$1.id})",
|
||||
},
|
||||
},
|
||||
// Message user popout
|
||||
{
|
||||
find: ".isProvisional?(",
|
||||
replacement: {
|
||||
match: /user:(\i),bio:.{0,60}}\)/,
|
||||
replace: "$&,$self.renderProfileSongs({userId:$1.id})",
|
||||
},
|
||||
},
|
||||
// DM sidebar profile
|
||||
{
|
||||
find: ".SIDEBAR}),nicknameIcons:",
|
||||
replacement: {
|
||||
match: /{userId:(\i)\.id}\)}\).{0,100}]}\)(?=\]\}\))/,
|
||||
replace: "$&,$self.renderProfileSongs({userId:$1.id,isSidebar:true})",
|
||||
},
|
||||
},
|
||||
// Full profile modal sections (lazy loaded)
|
||||
{
|
||||
find: ".MUTUAL_GUILDS})),",
|
||||
|
|
@ -80,6 +56,6 @@ export default definePlugin({
|
|||
useAuthorizationStore.persist.rehydrate();
|
||||
},
|
||||
|
||||
renderProfileSongs: ErrorBoundary.wrap(ProfileSongs, { noop: true }),
|
||||
renderProfileCollections: ErrorBoundary.wrap(ProfileSongs, { noop: true }),
|
||||
renderWidgetSongs: ErrorBoundary.wrap(WidgetSongs, { noop: true }),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default definePluginSettings({
|
|||
type: OptionType.SLIDER,
|
||||
description: "How many songs are shown when initially clicking on a user",
|
||||
default: apiConstants.songLimit,
|
||||
markers: makeRange(1, apiConstants.songLimit),
|
||||
markers: makeRange(1, 3),
|
||||
},
|
||||
manager: {
|
||||
type: OptionType.COMPONENT,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ export const ContainerClasses = findCssClassesLazy(
|
|||
);
|
||||
export const CardClasses = findCssClassesLazy("card", "headerText", "headerContextMenu");
|
||||
export const WidgetClasses = findCssClassesLazy("tabPanelScroller", "tabListScroller");
|
||||
export const DMSideBarClasses = findCssClassesLazy("widgetPreviews");
|
||||
export const ProfileCardClasses = findCssClassesLazy("cardsList", "firstCardContainer", "card", "container");
|
||||
|
||||
export enum SpinnerTypes {
|
||||
CHASING_DOTS = "chasingDots",
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@
|
|||
import { BaseText } from "@components/index";
|
||||
import { Native } from "@equicordplugins/songSpotlight.desktop/service";
|
||||
import {
|
||||
CardClasses,
|
||||
ContainerClasses,
|
||||
DMSideBarClasses,
|
||||
OverlayClasses,
|
||||
ProfileCardClasses,
|
||||
Spinner,
|
||||
} from "@equicordplugins/songSpotlight.desktop/ui/common";
|
||||
import { RenderSongInfo } from "@song-spotlight/api/handlers";
|
||||
|
|
@ -34,9 +35,10 @@ interface CollapsedProfileSongsProps extends ProfileSongsProps {
|
|||
data?: UserData;
|
||||
}
|
||||
|
||||
export default function CollapsedProfileSongs({ data, userId, isSidebar }: CollapsedProfileSongsProps) {
|
||||
export default function CollapsedProfileSongs({ data, user, isSideBar }: CollapsedProfileSongsProps) {
|
||||
const [renders, setRenders] = useState(new Map<string, RenderSongInfo>());
|
||||
const previews = useMemo(() => data?.slice(0, shownSongs), [data]);
|
||||
const userId = user?.id;
|
||||
|
||||
useEffect(() => {
|
||||
setRenders(new Map());
|
||||
|
|
@ -52,57 +54,67 @@ export default function CollapsedProfileSongs({ data, userId, isSidebar }: Colla
|
|||
}
|
||||
}, [previews]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ContainerClasses.breadcrumb}
|
||||
aria-label="Song Spotlight"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
const user = UserStore.getUser(userId);
|
||||
if (!user) return;
|
||||
const songsSection = (
|
||||
<section className={ProfileCardClasses.container}>
|
||||
<ul className={ProfileCardClasses.cardsList} tabIndex={-1}>
|
||||
<li className={ProfileCardClasses.firstCardContainer}>
|
||||
<div
|
||||
className={ContainerClasses.breadcrumb}
|
||||
aria-label="Song Spotlight"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
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",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className={classes(OverlayClasses.overlay, ContainerClasses.innerContainer, CardClasses.card)}>
|
||||
<BaseText size={isSidebar ? "sm" : "xs"} weight="medium">Song Spotlight</BaseText>
|
||||
<div className={ContainerClasses.icons}>
|
||||
{previews && data
|
||||
? previews.map((song, i) => {
|
||||
const render = renders.get(sid(song));
|
||||
const extra = i === shownSongs - 1 && data.length > shownSongs;
|
||||
const guildId = SelectedGuildStore.getGuildId();
|
||||
UserProfileActions.openUserProfileModal({
|
||||
userId,
|
||||
guildId,
|
||||
channelId: SelectedChannelStore.getChannelId(),
|
||||
analyticsLocation: {
|
||||
page: guildId ? "Guild Channel" : "DM Channel",
|
||||
section: "Profile Popout",
|
||||
},
|
||||
tabSection: "SONG_SPOTLIGHT",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className={classes(OverlayClasses.overlay, ContainerClasses.innerContainer, ProfileCardClasses.card)}>
|
||||
<BaseText size={isSideBar ? "sm" : "xs"} weight="medium">Song Spotlight</BaseText>
|
||||
<div className={ContainerClasses.icons}>
|
||||
{previews && data
|
||||
? previews.map((song, i) => {
|
||||
const render = renders.get(sid(song));
|
||||
const extra = i === shownSongs - 1 && data.length > shownSongs;
|
||||
|
||||
return (
|
||||
<div className={ContainerClasses.icon} key={i}>
|
||||
{render?.thumbnailUrl && (
|
||||
<img
|
||||
src={render.thumbnailUrl}
|
||||
alt={render.label}
|
||||
className={classes(extra && ContainerClasses.displayCount)}
|
||||
/>
|
||||
)}
|
||||
{extra && (
|
||||
<div className={ContainerClasses.displayCountText}>
|
||||
<BaseText size="xs" weight="medium">+{data.length - shownSongs}</BaseText>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: <Spinner type={Spinner.Type.SPINNING_CIRCLE} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className={ContainerClasses.icon} key={i}>
|
||||
{render?.thumbnailUrl && (
|
||||
<img
|
||||
src={render.thumbnailUrl}
|
||||
alt={render.label}
|
||||
className={classes(extra && ContainerClasses.displayCount)}
|
||||
/>
|
||||
)}
|
||||
{extra && (
|
||||
<div className={ContainerClasses.displayCountText}>
|
||||
<BaseText size={isSideBar ? "sm" : "xs"} weight="medium">+{data.length - shownSongs}</BaseText>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})
|
||||
: <Spinner type={Spinner.Type.SPINNING_CIRCLE} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
|
||||
return isSideBar
|
||||
? <div className={DMSideBarClasses.widgetPreviews}>{songsSection}</div>
|
||||
: songsSection;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { openSettingsModal } from "@equicordplugins/songSpotlight.desktop/ui/set
|
|||
import { sid } from "@song-spotlight/api/util";
|
||||
import { copyWithToast } from "@utils/discord";
|
||||
import { classes } from "@utils/index";
|
||||
import { User } from "@vencord/discord-types";
|
||||
import {
|
||||
ContextMenuApi,
|
||||
FluxDispatcher,
|
||||
|
|
@ -39,16 +40,16 @@ import Song from ".";
|
|||
import CollapsedProfileSongs from "./CollapsedProfileSongs";
|
||||
|
||||
export interface ProfileSongsProps {
|
||||
userId: string;
|
||||
isSidebar: boolean;
|
||||
user: User;
|
||||
isSideBar: boolean;
|
||||
}
|
||||
|
||||
export default function ProfileSongs({ userId, isSidebar }: ProfileSongsProps) {
|
||||
export default function ProfileSongs({ user, isSideBar }: ProfileSongsProps) {
|
||||
const [failed, setFailed] = useState(false);
|
||||
const { isAuthorized } = useAuthorizationStore();
|
||||
const { users } = useSongStore();
|
||||
const { profileSongsLimit, collapseSongList } = settings.use();
|
||||
|
||||
const userId = user?.id;
|
||||
const data = users[userId]?.data;
|
||||
useEffect(() => {
|
||||
if (isAuthorized() && !data) listData(userId).catch(() => setFailed(true));
|
||||
|
|
@ -70,8 +71,8 @@ export default function ProfileSongs({ userId, isSidebar }: ProfileSongsProps) {
|
|||
return (
|
||||
<CollapsedProfileSongs
|
||||
data={data}
|
||||
userId={userId}
|
||||
isSidebar={isSidebar}
|
||||
user={user}
|
||||
isSideBar={isSideBar}
|
||||
/>
|
||||
);
|
||||
} else if (pending) {
|
||||
|
|
@ -81,14 +82,14 @@ export default function ProfileSongs({ userId, isSidebar }: ProfileSongsProps) {
|
|||
return (
|
||||
<div
|
||||
className={classes(
|
||||
isSidebar && OverlayClasses.overlay,
|
||||
isSidebar && CardClasses.card,
|
||||
cl("songs-container", isSidebar && "songs-container-sidebar"),
|
||||
isSideBar && OverlayClasses.overlay,
|
||||
isSideBar && CardClasses.card,
|
||||
cl("songs-container", isSideBar && "songs-container-sidebar"),
|
||||
)}
|
||||
key="song-spotlight-profile-songs"
|
||||
>
|
||||
<Flex justifyContent="space-between">
|
||||
<BaseText size="xs" weight={isSidebar ? "semibold" : "medium"} className={cl("header")}>
|
||||
<BaseText size="xs" weight={isSideBar ? "semibold" : "medium"} className={cl("header")}>
|
||||
Song Spotlight
|
||||
</BaseText>
|
||||
<Tooltip text="More">
|
||||
|
|
|
|||
|
|
@ -82,26 +82,6 @@ export default definePlugin({
|
|||
"user-profile-overflow-menu": userContextPatch
|
||||
},
|
||||
|
||||
patches: [
|
||||
// In the user popout. eg: when clicking the name in chat
|
||||
{
|
||||
// DM profile sidebar
|
||||
find: ".SIDEBAR,disableToolbar:",
|
||||
replacement: {
|
||||
match: /user:(\i),widgets:.{0,100}?\}\),/,
|
||||
replace: "$&$self.renderProfileComponent({user:$1,isSideBar:true}),"
|
||||
}
|
||||
},
|
||||
{
|
||||
// User popout
|
||||
find: /\.POPOUT,onClose:\i}\),nicknameIcons:.+?\.isProvisional/,
|
||||
replacement: {
|
||||
match: /user:(\i),widgets:.{0,100}?\}\),/,
|
||||
replace: "$&$self.renderProfileComponent({user:$1}),"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
flux: {
|
||||
CONNECTION_OPEN: initAuth,
|
||||
},
|
||||
|
|
@ -156,7 +136,7 @@ export default definePlugin({
|
|||
}, 4000);
|
||||
},
|
||||
|
||||
renderProfileComponent: ErrorBoundary.wrap(({ user, isSideBar = false }: { user: User; isSideBar?: boolean; }) => {
|
||||
renderProfileCollections: ErrorBoundary.wrap(({ user, isSideBar = false }: { user: User; isSideBar?: boolean; }) => {
|
||||
const [reviewData] = useAwaiter(() => getReviews(user.id, { limit: 4 }), { deps: [user.id], fallbackValue: null });
|
||||
|
||||
// Discord are masters at using a crap ton of html elements and css classes to create a simple ui that could have
|
||||
|
|
@ -192,7 +172,7 @@ export default definePlugin({
|
|||
{showCount && (
|
||||
<div className={ProfileCardContainerClasses.displayCountText}>
|
||||
<Span className={ProfileCardContainerClasses.displayCountTextColor} size="xs" weight="medium" defaultColor={false}>
|
||||
+{reviewData.reviewCount - 3}
|
||||
+{reviewData.reviewCount - 4}
|
||||
</Span>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import type { MessageDecorationFactory } from "@api/MessageDecorations";
|
|||
import type { MessageClickListener, MessageEditListener, MessageSendListener } from "@api/MessageEvents";
|
||||
import type { MessagePopoverButtonData, MessagePopoverButtonFactory } from "@api/MessagePopover";
|
||||
import type { NicknameIconFactory } from "@api/NicknameIcons";
|
||||
import { ProfileCollectionFactory } from "@api/ProfileCollections";
|
||||
import type { UserAreaButtonData } from "@api/UserArea";
|
||||
import type { Command, FluxEvents } from "@vencord/discord-types";
|
||||
import type { ReactNode } from "react";
|
||||
|
|
@ -208,6 +209,7 @@ export interface PluginDef {
|
|||
headerBarButton?: HeaderBarButtonData;
|
||||
audioProcessor?: AudioProcessor;
|
||||
userAreaButton?: UserAreaButtonData;
|
||||
renderProfileCollections?: ProfileCollectionFactory;
|
||||
|
||||
// TODO: Remove eventually
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue