mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-01-10 18:06:22 +00:00
feat(betterActivities): rename and add show all activities functionality
This commit is contained in:
parent
957639be6c
commit
4e430c4252
11 changed files with 304 additions and 87 deletions
6
src/plugins/betterActivities/README.md
Normal file
6
src/plugins/betterActivities/README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# BetterActivities
|
||||
|
||||
Shows activity icons in the member list and allows showing all activities
|
||||
|
||||
![Screenshot](screenshot.png)
|
||||
![Popout](popout.png)
|
13
src/plugins/betterActivities/components/Caret.tsx
Normal file
13
src/plugins/betterActivities/components/Caret.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export function Caret({ disabled, direction }: { disabled: boolean; direction: "left" | "right"; }) {
|
||||
return (
|
||||
<svg className={`vc-bactivities-caret-${direction.toLowerCase()} ${disabled && "disabled"}`} width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" fillRule="evenodd" clipRule="evenodd" d="M16.59 8.59004L12 13.17L7.41 8.59004L6 10L12 16L18 10L16.59 8.59004Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
|
@ -23,15 +23,34 @@ import { classNameFactory } from "@api/Styles";
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||
import { moment, React, Tooltip, useMemo } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
import { findByCodeLazy, findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||
import { moment, PresenceStore, React, Tooltip, useMemo, useStateFromStores } from "@webpack/common";
|
||||
import { Guild, User } from "discord-types/general";
|
||||
|
||||
import { Caret } from "./components/Caret";
|
||||
import { SpotifyIcon } from "./components/SpotifyIcon";
|
||||
import { TwitchIcon } from "./components/TwitchIcon";
|
||||
import { Activity, ActivityListIcon, Application, ApplicationIcon, Timestamp } from "./types";
|
||||
import { Activity, ActivityListIcon, Application, ApplicationIcon, IconCSSProperties, Timestamp } from "./types";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
memberList: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show activity icons in the member list",
|
||||
default: true,
|
||||
restartNeeded: true,
|
||||
},
|
||||
profileSidebar: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show all activities in the profile sidebar",
|
||||
default: true,
|
||||
restartNeeded: true,
|
||||
},
|
||||
userPopout: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show all activities in the user popout",
|
||||
default: true,
|
||||
restartNeeded: true,
|
||||
},
|
||||
iconSize: {
|
||||
type: OptionType.SLIDER,
|
||||
description: "Size of the activity icons",
|
||||
|
@ -47,7 +66,7 @@ const settings = definePluginSettings({
|
|||
},
|
||||
});
|
||||
|
||||
const cl = classNameFactory("vc-mla-");
|
||||
const cl = classNameFactory("vc-bactivities-");
|
||||
|
||||
const ApplicationStore: {
|
||||
getApplication: (id: string) => Application | null;
|
||||
|
@ -57,12 +76,14 @@ const { fetchApplication }: {
|
|||
fetchApplication: (id: string) => Promise<Application | null>;
|
||||
} = findByPropsLazy("fetchApplication");
|
||||
|
||||
const TimeBar: React.ComponentType<React.PropsWithChildren<{
|
||||
const TimeBar = findComponentByCodeLazy<{
|
||||
start: number;
|
||||
end: number;
|
||||
themed: boolean;
|
||||
className: string;
|
||||
}>> = findComponentByCodeLazy("isSingleLine");
|
||||
}>("isSingleLine");
|
||||
|
||||
const ActivityView = findByCodeLazy("onOpenGameProfile:");
|
||||
|
||||
// if discord one day decides to change their icon this needs to be updated
|
||||
const DefaultActivityIcon = findComponentByCodeLazy("M6,7 L2,7 L2,6 L6,6 L6,7 Z M8,5 L2,5 L2,4 L8,4 L8,5 Z M8,3 L2,3 L2,2 L8,2 L8,3 Z M8.88888889,0 L1.11111111,0 C0.494444444,0 0,0.494444444 0,1.11111111 L0,8.88888889 C0,9.50253861 0.497461389,10 1.11111111,10 L8.88888889,10 C9.50253861,10 10,9.50253861 10,8.88888889 L10,1.11111111 C10,0.494444444 9.5,0 8.88888889,0 Z");
|
||||
|
@ -117,7 +138,7 @@ function formatElapsedTime(startTime: moment.Moment, endTime: moment.Moment): st
|
|||
return `${customFormat(moment.utc(duration.asMilliseconds()))} elapsed`;
|
||||
}
|
||||
|
||||
const ActivityTooltip = ({ activity, application }: Readonly<{ activity: Activity, application?: Application }>) => {
|
||||
const ActivityTooltip = ({ activity, application, user }: Readonly<{ activity: Activity, application?: Application, user: User; }>) => {
|
||||
const image = useMemo(() => {
|
||||
const activityImage = getActivityImage(activity, application);
|
||||
if (activityImage) {
|
||||
|
@ -145,7 +166,7 @@ const ActivityTooltip = ({ activity, application }: Readonly<{ activity: Activit
|
|||
</div>
|
||||
}
|
||||
</div>
|
||||
{timestamps && <TimeBar start={timestamps.start} end={timestamps.end} themed={false} className={cl("activity-time-bar")}/> }
|
||||
{timestamps && <TimeBar start={timestamps.start} end={timestamps.end} themed={false} className={cl("activity-time-bar")} />}
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
@ -240,28 +261,28 @@ function getApplicationIcons(activities: Activity[], preferSmall = false) {
|
|||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "MemberListActivities",
|
||||
description: "Shows activity icons in the member list",
|
||||
authors: [Devs.D3SOX],
|
||||
name: "BetterActivities",
|
||||
description: "Shows activity icons in the member list and allows showing all activities",
|
||||
authors: [Devs.D3SOX, Devs.Arjix, Devs.AutumnVN],
|
||||
tags: ["activity"],
|
||||
|
||||
settings,
|
||||
|
||||
patchActivityList: ({ activities, user }: { activities: Activity[], user: User }): JSX.Element | null => {
|
||||
patchActivityList: ({ activities, user }: { activities: Activity[], user: User; }): JSX.Element | null => {
|
||||
const icons: ActivityListIcon[] = [];
|
||||
|
||||
const spotifyActivity = activities.find(({ name }) => name === "Spotify");
|
||||
if (spotifyActivity) {
|
||||
icons.push({
|
||||
iconElement: <SpotifyIcon />,
|
||||
tooltip: <ActivityTooltip activity={spotifyActivity} />
|
||||
tooltip: <ActivityTooltip activity={spotifyActivity} user={user} />
|
||||
});
|
||||
}
|
||||
const twitchActivity = activities.find(({ name }) => name === "Twitch");
|
||||
if (twitchActivity) {
|
||||
icons.push({
|
||||
iconElement: <TwitchIcon />,
|
||||
tooltip: <ActivityTooltip activity={twitchActivity} />
|
||||
tooltip: <ActivityTooltip activity={twitchActivity} user={user} />
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -276,19 +297,20 @@ export default definePlugin({
|
|||
for (const appIcon of uniqueIcons) {
|
||||
icons.push({
|
||||
iconElement: <img {...appIcon.image} />,
|
||||
tooltip: <ActivityTooltip activity={appIcon.activity} application={appIcon.application} />
|
||||
tooltip: <ActivityTooltip activity={appIcon.activity} application={appIcon.application} user={user} />
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (icons.length) {
|
||||
const iconStyle: IconCSSProperties = {
|
||||
"--icon-size": `${settings.store.iconSize}px`,
|
||||
};
|
||||
|
||||
return <ErrorBoundary noop>
|
||||
<div className={cl("row")}>
|
||||
{icons.map(({ iconElement, tooltip }, i) => (
|
||||
<div key={i} className={cl("icon")} style={{
|
||||
width: `${settings.store.iconSize}px`,
|
||||
height: `${settings.store.iconSize}px`
|
||||
}}>
|
||||
<div key={i} className={cl("icon")} style={iconStyle}>
|
||||
{tooltip ? <Tooltip text={tooltip}>
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
<div
|
||||
|
@ -314,6 +336,93 @@ export default definePlugin({
|
|||
return null;
|
||||
},
|
||||
|
||||
showAllActivitiesComponent({ activity, user, guild, channelId, onClose }: { activity: Activity; user: User, guild: Guild, channelId: string, onClose: () => void; }) {
|
||||
const [currentActivity, setCurrentActivity] = React.useState<Activity | null>(
|
||||
activity?.type !== 4 ? activity! : null
|
||||
);
|
||||
|
||||
const activities = useStateFromStores<Activity[]>(
|
||||
[PresenceStore], () => PresenceStore.getActivities(user.id).filter((activity: Activity) => activity.type !== 4)
|
||||
) ?? [];
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!activities.length) {
|
||||
setCurrentActivity(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentActivity || !activities.includes(currentActivity))
|
||||
setCurrentActivity(activities[0]);
|
||||
|
||||
}, [activities]);
|
||||
|
||||
if (!activities.length) return null;
|
||||
|
||||
return (
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
<ActivityView
|
||||
activity={currentActivity}
|
||||
user={user}
|
||||
guild={guild}
|
||||
channelId={channelId}
|
||||
onClose={onClose}
|
||||
/>
|
||||
<div
|
||||
className={cl("controls")}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Tooltip text="Left" tooltipClassName={cl("controls-tooltip")}>{({ onMouseEnter, onMouseLeave }) => {
|
||||
return <span
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onClick={() => {
|
||||
const index = activities.indexOf(currentActivity!);
|
||||
if (index - 1 >= 0)
|
||||
setCurrentActivity(activities[index - 1]);
|
||||
}}
|
||||
>
|
||||
<Caret
|
||||
disabled={activities.indexOf(currentActivity!) < 1}
|
||||
direction="left"
|
||||
/>
|
||||
</span>;
|
||||
}}</Tooltip>
|
||||
|
||||
<div className="carousell">
|
||||
{activities.map((activity, index) => (
|
||||
<div
|
||||
key={"dot--" + index}
|
||||
onClick={() => setCurrentActivity(activity)}
|
||||
className={`dot ${currentActivity === activity ? "selected" : ""}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Tooltip text="Right" tooltipClassName={cl("controls-tooltip")}>{({ onMouseEnter, onMouseLeave }) => {
|
||||
return <span
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onClick={() => {
|
||||
const index = activities.indexOf(currentActivity!);
|
||||
if (index + 1 < activities.length)
|
||||
setCurrentActivity(activities[index + 1]);
|
||||
}}
|
||||
>
|
||||
<Caret
|
||||
disabled={activities.indexOf(currentActivity!) >= activities.length - 1}
|
||||
direction="right"
|
||||
/>
|
||||
</span>;
|
||||
}}</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
patches: [
|
||||
{
|
||||
// Patch activity icons
|
||||
|
@ -321,7 +430,26 @@ export default definePlugin({
|
|||
replacement: {
|
||||
match: /null!=(\i)&&\i.some\(\i=>\(0,\i.default\)\(\i,\i\)\)\?/,
|
||||
replace: "$self.patchActivityList(e),false?"
|
||||
}
|
||||
},
|
||||
predicate: () => settings.store.memberList,
|
||||
},
|
||||
{
|
||||
// Show all activities in the profile panel
|
||||
find: "Profile Panel: user cannot be undefined",
|
||||
replacement: {
|
||||
match: /(?<=\(0,\i\.jsx\)\()\i\.\i(?=,{activity:.+?,user:\i,channelId:\i.id,)/,
|
||||
replace: "$self.showAllActivitiesComponent"
|
||||
},
|
||||
predicate: () => settings.store.profileSidebar,
|
||||
},
|
||||
{
|
||||
// Show all activities in the user popout
|
||||
find: "customStatusSection,",
|
||||
replacement: {
|
||||
match: /(?<=\(0,\i\.jsx\)\()\i\.\i(?=,{activity:\i,user:\i,guild:\i,channelId:\i,onClose:\i,)/,
|
||||
replace: "$self.showAllActivitiesComponent"
|
||||
},
|
||||
predicate: () => settings.store.userPopout
|
||||
}
|
||||
],
|
||||
});
|
BIN
src/plugins/betterActivities/popout.png
Normal file
BIN
src/plugins/betterActivities/popout.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 7 KiB |
131
src/plugins/betterActivities/styles.css
Normal file
131
src/plugins/betterActivities/styles.css
Normal file
|
@ -0,0 +1,131 @@
|
|||
.vc-bactivities-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
margin-left: 5px;
|
||||
text-align: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.vc-bactivities-icon {
|
||||
height: var(--icon-size);
|
||||
width: var(--icon-size);
|
||||
}
|
||||
|
||||
.vc-bactivities-icon img {
|
||||
width: var(--icon-size);
|
||||
height: var(--icon-size);
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.vc-bactivities-activity {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.vc-bactivities-activity-title {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vc-bactivities-activity-image {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.vc-bactivities-activity-divider {
|
||||
width: 100%;
|
||||
border-top: 1px dotted rgb(255 255 255 / 20%);
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.vc-bactivities-activity-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--text-muted);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.vc-bactivities-activity-time-bar {
|
||||
width: 100%;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.vc-bactivities-caret-left,
|
||||
.vc-bactivities-caret-right {
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.vc-bactivities-caret-left {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.vc-bactivities-caret-right {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.vc-bactivities-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 5px;
|
||||
background: var(--background-secondary-alt);
|
||||
border-radius: 3px;
|
||||
flex: 1 0;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.vc-bactivities-controls [class^="vc-activities-caret-"] {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
background-color: #ffffff4d;
|
||||
}
|
||||
|
||||
.vc-bactivities-controls [class^="vc-activities-caret-"].disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.vc-bactivities-controls [class^="vc-activities-caret-"]:hover:not(.disabled) {
|
||||
background: var(--background-modifier-accent);
|
||||
}
|
||||
|
||||
.vc-bactivities-controls .carousell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vc-bactivities-controls .carousell .dot {
|
||||
margin: 0 4px;
|
||||
width: 10px;
|
||||
cursor: pointer;
|
||||
height: 10px;
|
||||
border-radius: 100px;
|
||||
background: var(--interactive-muted);
|
||||
transition: background 0.3s;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.vc-bactivities-controls .carousell .dot:hover:not(.selected) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.vc-bactivities-controls .carousell .dot.selected {
|
||||
opacity: 1;
|
||||
background: var(--dot-color, var(--brand-experiment));
|
||||
}
|
||||
|
||||
.vc-bactivities-controls-tooltip {
|
||||
--background-floating: var(--background-secondary);
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { ImgHTMLAttributes } from "react";
|
||||
import { CSSProperties, ImgHTMLAttributes } from "react";
|
||||
|
||||
export interface Timestamp {
|
||||
start?: number;
|
||||
|
@ -76,3 +76,7 @@ export interface ActivityListIcon {
|
|||
iconElement: JSX.Element;
|
||||
tooltip?: JSX.Element | string;
|
||||
}
|
||||
|
||||
export interface IconCSSProperties extends CSSProperties {
|
||||
"--icon-size": string;
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
# MemberListActivities
|
||||
|
||||
Shows activity icons in the member list
|
||||
|
||||
![Screenshot](screenshot.png)
|
|
@ -1,60 +0,0 @@
|
|||
.vc-mla-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
margin-left: 5px;
|
||||
text-align: center;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.vc-mla-icon {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.vc-mla-icon img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.vc-mla-activity {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.vc-mla-activity-title {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vc-mla-activity-image {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.vc-mla-activity-divider {
|
||||
width: 100%;
|
||||
border-top: 1px dotted rgb(255 255 255 / 20%);
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.vc-mla-activity-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--text-muted);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.vc-mla-activity-time-bar {
|
||||
width: 100%;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
Loading…
Reference in a new issue