80 lines
2.7 KiB
TypeScript
80 lines
2.7 KiB
TypeScript
|
|
/*
|
||
|
|
* 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} />;
|
||
|
|
}
|