Merge branch 'feat/usercss' of ssh://github.com/lewisakura/Vencord into feat/usercss
This commit is contained in:
commit
f2dc34e023
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -38,7 +38,7 @@ jobs:
|
||||||
run: pnpm build --standalone
|
run: pnpm build --standalone
|
||||||
|
|
||||||
- name: Generate plugin list
|
- name: Generate plugin list
|
||||||
run: pnpm generatePluginJson dist/plugins.json
|
run: pnpm generatePluginJson dist/plugins.json dist/plugin-readmes.json
|
||||||
|
|
||||||
- name: Clean up obsolete files
|
- name: Clean up obsolete files
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.4.6",
|
"version": "1.4.7",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
@ -165,7 +165,11 @@ async function parseFile(fileName: string) {
|
||||||
data.target = target as any;
|
data.target = target as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
let readme = "";
|
||||||
|
try {
|
||||||
|
readme = readFileSync(join(fileName, "..", "README.md"), "utf-8");
|
||||||
|
} catch { }
|
||||||
|
return [data, readme] as const;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw fail("no default export called 'definePlugin' found");
|
throw fail("no default export called 'definePlugin' found");
|
||||||
|
@ -194,18 +198,24 @@ function isPluginFile({ name }: { name: string; }) {
|
||||||
(async () => {
|
(async () => {
|
||||||
parseDevs();
|
parseDevs();
|
||||||
|
|
||||||
const plugins = ["src/plugins", "src/plugins/_core"].flatMap(dir =>
|
const plugins = [] as PluginData[];
|
||||||
|
const readmes = {} as Record<string, string>;
|
||||||
|
|
||||||
|
await Promise.all(["src/plugins", "src/plugins/_core"].flatMap(dir =>
|
||||||
readdirSync(dir, { withFileTypes: true })
|
readdirSync(dir, { withFileTypes: true })
|
||||||
.filter(isPluginFile)
|
.filter(isPluginFile)
|
||||||
.map(async dirent =>
|
.map(async dirent => {
|
||||||
parseFile(await getEntryPoint(dir, dirent))
|
const [data, readme] = await parseFile(await getEntryPoint(dir, dirent));
|
||||||
)
|
plugins.push(data);
|
||||||
);
|
if (readme) readmes[data.name] = readme;
|
||||||
|
})
|
||||||
|
));
|
||||||
|
|
||||||
const data = JSON.stringify(await Promise.all(plugins));
|
const data = JSON.stringify(plugins);
|
||||||
|
|
||||||
if (process.argv.length > 2) {
|
if (process.argv.length > 3) {
|
||||||
writeFileSync(process.argv[2], data);
|
writeFileSync(process.argv[2], data);
|
||||||
|
writeFileSync(process.argv[3], JSON.stringify(readmes));
|
||||||
} else {
|
} else {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
}
|
}
|
||||||
|
|
113
src/components/PluginSettings/ContributorModal.tsx
Normal file
113
src/components/PluginSettings/ContributorModal.tsx
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./contributorModal.css";
|
||||||
|
|
||||||
|
import { useSettings } from "@api/Settings";
|
||||||
|
import { classNameFactory } from "@api/Styles";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { DevsById } from "@utils/constants";
|
||||||
|
import { fetchUserProfile, getTheme, Theme } from "@utils/discord";
|
||||||
|
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
|
||||||
|
import { Forms, MaskedLink, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
|
||||||
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
|
import { PluginCard } from ".";
|
||||||
|
|
||||||
|
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
||||||
|
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
||||||
|
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
||||||
|
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-author-modal-");
|
||||||
|
|
||||||
|
export function openContributorModal(user: User) {
|
||||||
|
openModal(modalProps =>
|
||||||
|
<ModalRoot {...modalProps}>
|
||||||
|
<ErrorBoundary>
|
||||||
|
<ModalContent className={cl("root")}>
|
||||||
|
<ContributorModal user={user} />
|
||||||
|
</ModalContent>
|
||||||
|
</ErrorBoundary>
|
||||||
|
</ModalRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GithubIcon() {
|
||||||
|
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
|
||||||
|
return <img src={src} alt="GitHub" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function WebsiteIcon() {
|
||||||
|
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
|
||||||
|
return <img src={src} alt="Website" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ContributorModal({ user }: { user: User; }) {
|
||||||
|
useSettings();
|
||||||
|
|
||||||
|
const profile = useStateFromStores([UserProfileStore], () => UserProfileStore.getUserProfile(user.id));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!profile && !user.bot && user.id)
|
||||||
|
fetchUserProfile(user.id);
|
||||||
|
}, [user.id]);
|
||||||
|
|
||||||
|
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
|
||||||
|
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;
|
||||||
|
|
||||||
|
const plugins = useMemo(() => {
|
||||||
|
const allPlugins = Object.values(Plugins);
|
||||||
|
const pluginsByAuthor = DevsById[user.id]
|
||||||
|
? allPlugins.filter(p => p.authors.includes(DevsById[user.id]))
|
||||||
|
: allPlugins.filter(p => p.authors.some(a => a.name === user.username));
|
||||||
|
|
||||||
|
return pluginsByAuthor
|
||||||
|
.filter(p => !p.name.endsWith("API"))
|
||||||
|
.sort((a, b) => Number(a.required ?? false) - Number(b.required ?? false));
|
||||||
|
}, [user.id, user.username]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={cl("header")}>
|
||||||
|
<img
|
||||||
|
className={cl("avatar")}
|
||||||
|
src={user.getAvatarURL(void 0, 512, true)}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
|
||||||
|
|
||||||
|
<div className={cl("links")}>
|
||||||
|
{website && (
|
||||||
|
<MaskedLink
|
||||||
|
href={"https://" + website}
|
||||||
|
>
|
||||||
|
<WebsiteIcon />
|
||||||
|
</MaskedLink>
|
||||||
|
)}
|
||||||
|
{githubName && (
|
||||||
|
<MaskedLink href={`https://github.com/${githubName}`}>
|
||||||
|
<GithubIcon />
|
||||||
|
</MaskedLink>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cl("plugins")}>
|
||||||
|
{plugins.map(p =>
|
||||||
|
<PluginCard
|
||||||
|
key={p.name}
|
||||||
|
plugin={p}
|
||||||
|
disabled={p.required ?? false}
|
||||||
|
onRestartNeeded={() => showToast("Restart to apply changes!")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
import { generateId } from "@api/Commands";
|
import { generateId } from "@api/Commands";
|
||||||
import { useSettings } from "@api/Settings";
|
import { useSettings } from "@api/Settings";
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { proxyLazy } from "@utils/lazy";
|
import { proxyLazy } from "@utils/lazy";
|
||||||
|
@ -28,7 +27,7 @@ import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, M
|
||||||
import { LazyComponent } from "@utils/react";
|
import { LazyComponent } from "@utils/react";
|
||||||
import { OptionType, Plugin } from "@utils/types";
|
import { OptionType, Plugin } from "@utils/types";
|
||||||
import { findByCode, findByPropsLazy } from "@webpack";
|
import { findByCode, findByPropsLazy } from "@webpack";
|
||||||
import { Button, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
import { Constructor } from "type-fest";
|
import { Constructor } from "type-fest";
|
||||||
|
|
||||||
|
@ -41,7 +40,7 @@ import {
|
||||||
SettingSliderComponent,
|
SettingSliderComponent,
|
||||||
SettingTextComponent
|
SettingTextComponent
|
||||||
} from "./components";
|
} from "./components";
|
||||||
import hideBotTagStyle from "./userPopoutHideBotTag.css?managed";
|
import { openContributorModal } from "./ContributorModal";
|
||||||
|
|
||||||
const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
|
const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
|
||||||
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
||||||
|
@ -92,27 +91,16 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options));
|
const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options));
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
enableStyle(hideBotTagStyle);
|
|
||||||
|
|
||||||
let originalUser: User;
|
|
||||||
(async () => {
|
(async () => {
|
||||||
for (const user of plugin.authors.slice(0, 6)) {
|
for (const user of plugin.authors.slice(0, 6)) {
|
||||||
const author = user.id
|
const author = user.id
|
||||||
? await UserUtils.fetchUser(`${user.id}`)
|
? await UserUtils.fetchUser(`${user.id}`)
|
||||||
// only show name & pfp and no actions so users cannot harass plugin devs for support (send dms, add as friend, etc)
|
|
||||||
.then(u => (originalUser = u, makeDummyUser(u)))
|
|
||||||
.catch(() => makeDummyUser({ username: user.name }))
|
.catch(() => makeDummyUser({ username: user.name }))
|
||||||
: makeDummyUser({ username: user.name });
|
: makeDummyUser({ username: user.name });
|
||||||
|
|
||||||
setAuthors(a => [...a, author]);
|
setAuthors(a => [...a, author]);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return () => {
|
|
||||||
disableStyle(hideBotTagStyle);
|
|
||||||
if (originalUser)
|
|
||||||
FluxDispatcher.dispatch({ type: "USER_UPDATE", user: originalUser });
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
async function saveAndClose() {
|
async function saveAndClose() {
|
||||||
|
@ -214,6 +202,19 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
showDefaultAvatarsForNullUsers
|
showDefaultAvatarsForNullUsers
|
||||||
showUserPopout
|
showUserPopout
|
||||||
renderMoreUsers={renderMoreUsers}
|
renderMoreUsers={renderMoreUsers}
|
||||||
|
renderUser={(user: User) => (
|
||||||
|
<Clickable
|
||||||
|
className={AvatarStyles.clickableAvatar}
|
||||||
|
onClick={() => openContributorModal(user)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className={AvatarStyles.avatar}
|
||||||
|
src={user.getAvatarURL(void 0, 80, true)}
|
||||||
|
alt={user.username}
|
||||||
|
title={user.username}
|
||||||
|
/>
|
||||||
|
</Clickable>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
|
|
57
src/components/PluginSettings/contributorModal.css
Normal file
57
src/components/PluginSettings/contributorModal.css
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
.vc-author-modal-root {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-author-modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-author-modal-name {
|
||||||
|
text-transform: none;
|
||||||
|
flex-grow: 0;
|
||||||
|
background: var(--background-tertiary);
|
||||||
|
border-radius: 0 9999px 9999px 0;
|
||||||
|
padding: 6px 0.8em 6px 0.5em;
|
||||||
|
font-size: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-author-modal-name::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 16px;
|
||||||
|
background: var(--background-tertiary);
|
||||||
|
z-index: -1;
|
||||||
|
left: -16px;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-author-modal-avatar {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-author-modal-links {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-author-modal-links img {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4px solid var(--background-tertiary);
|
||||||
|
box-sizing: border-box
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-author-modal-plugins {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5em;
|
||||||
|
}
|
|
@ -91,7 +91,7 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
|
||||||
isNew?: boolean;
|
isNew?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
|
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
|
||||||
const settings = Settings.plugins[plugin.name];
|
const settings = Settings.plugins[plugin.name];
|
||||||
|
|
||||||
const isEnabled = () => settings.enabled ?? false;
|
const isEnabled = () => settings.enabled ?? false;
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
[class|="userPopoutOuter"] [class*="botTag"] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
|
@ -8,6 +8,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: 0.1s ease-out;
|
transition: 0.1s ease-out;
|
||||||
transition-property: box-shadow, transform, background, opacity;
|
transition-property: box-shadow, transform, background, opacity;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-addon-card-disabled {
|
.vc-addon-card-disabled {
|
||||||
|
|
|
@ -49,7 +49,7 @@ export default definePlugin({
|
||||||
replace: "$1,vcProps=$2$3+(vcProps.channel.nsfw?' vc-nsfw-img':'')"
|
replace: "$1,vcProps=$2$3+(vcProps.channel.nsfw?' vc-nsfw-img':'')"
|
||||||
}, {
|
}, {
|
||||||
match: /(\.renderAttachments=.+?(.)=this\.props)(.+?\.embedWrapper)/g,
|
match: /(\.renderAttachments=.+?(.)=this\.props)(.+?\.embedWrapper)/g,
|
||||||
replace: "$1,vcProps=$2$3+(vcProps.channel.nsfw?' vc-nsfw-img':'')"
|
replace: "$1,vcProps=$2$3+(vcProps.nsfw?' vc-nsfw-img':'')"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -72,6 +72,12 @@ const enum ActivityFlag {
|
||||||
INSTANCE = 1 << 0,
|
INSTANCE = 1 << 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const enum NameFormat {
|
||||||
|
StatusName = "status-name",
|
||||||
|
ArtistFirst = "artist-first",
|
||||||
|
SongFirst = "song-first",
|
||||||
|
}
|
||||||
|
|
||||||
const applicationId = "1108588077900898414";
|
const applicationId = "1108588077900898414";
|
||||||
const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
|
const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
|
||||||
|
|
||||||
|
@ -117,10 +123,29 @@ const settings = definePluginSettings({
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
statusName: {
|
statusName: {
|
||||||
description: "text shown in status",
|
description: "custom status text",
|
||||||
type: OptionType.STRING,
|
type: OptionType.STRING,
|
||||||
default: "some music",
|
default: "some music",
|
||||||
},
|
},
|
||||||
|
nameFormat: {
|
||||||
|
description: "Show name of song and artist in status name",
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "Use custom status name",
|
||||||
|
value: NameFormat.StatusName,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Use format 'artist - song'",
|
||||||
|
value: NameFormat.ArtistFirst
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Use format 'song - artist'",
|
||||||
|
value: NameFormat.SongFirst
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
useListeningStatus: {
|
useListeningStatus: {
|
||||||
description: 'show "Listening to" status instead of "Playing"',
|
description: 'show "Listening to" status instead of "Playing"',
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
|
@ -140,13 +165,13 @@ const settings = definePluginSettings({
|
||||||
value: "placeholder"
|
value: "placeholder"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "LastFMRichPresence",
|
name: "LastFMRichPresence",
|
||||||
description: "Little plugin for Last.fm rich presence",
|
description: "Little plugin for Last.fm rich presence",
|
||||||
authors: [Devs.dzshn, Devs.RuiNtD],
|
authors: [Devs.dzshn, Devs.RuiNtD, Devs.blahajZip],
|
||||||
|
|
||||||
settingsAboutComponent: () => (
|
settingsAboutComponent: () => (
|
||||||
<>
|
<>
|
||||||
|
@ -267,9 +292,20 @@ export default definePlugin({
|
||||||
url: `https://www.last.fm/user/${settings.store.username}`,
|
url: `https://www.last.fm/user/${settings.store.username}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const statusName = (() => {
|
||||||
|
switch (settings.store.nameFormat) {
|
||||||
|
case NameFormat.ArtistFirst:
|
||||||
|
return trackData.artist + " - " + trackData.name;
|
||||||
|
case NameFormat.SongFirst:
|
||||||
|
return trackData.name + " - " + trackData.artist;
|
||||||
|
default:
|
||||||
|
return settings.store.statusName;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
application_id: applicationId,
|
application_id: applicationId,
|
||||||
name: settings.store.statusName,
|
name: statusName,
|
||||||
|
|
||||||
details: trackData.name,
|
details: trackData.name,
|
||||||
state: trackData.artist,
|
state: trackData.artist,
|
||||||
|
|
83
src/plugins/pictureInPicture.tsx
Normal file
83
src/plugins/pictureInPicture.tsx
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { React, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
loop: {
|
||||||
|
description: "Whether to make the PiP video loop or not",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
restartNeeded: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "PictureInPicture",
|
||||||
|
description: "Adds picture in picture to videos (next to the Download button)",
|
||||||
|
authors: [Devs.Lumap],
|
||||||
|
settings,
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: ".onRemoveAttachment,",
|
||||||
|
replacement: {
|
||||||
|
match: /\.nonMediaAttachment.{0,10}children:\[(\i),/,
|
||||||
|
replace: "$&$1&&$self.renderPiPButton(),"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
renderPiPButton: ErrorBoundary.wrap(() => {
|
||||||
|
return (
|
||||||
|
<Tooltip text="Toggle Picture in Picture">
|
||||||
|
{tooltipProps => (
|
||||||
|
<div
|
||||||
|
{...tooltipProps}
|
||||||
|
role="button"
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
paddingTop: "4px",
|
||||||
|
paddingLeft: "4px",
|
||||||
|
paddingRight: "4px",
|
||||||
|
}}
|
||||||
|
onClick={e => {
|
||||||
|
const video = e.currentTarget.parentNode!.parentNode!.querySelector("video")!;
|
||||||
|
const videoClone = document.body.appendChild(video.cloneNode(true)) as HTMLVideoElement;
|
||||||
|
|
||||||
|
videoClone.loop = settings.store.loop;
|
||||||
|
videoClone.style.display = "none";
|
||||||
|
videoClone.onleavepictureinpicture = () => videoClone.remove();
|
||||||
|
|
||||||
|
function launchPiP() {
|
||||||
|
videoClone.currentTime = video.currentTime;
|
||||||
|
videoClone.requestPictureInPicture();
|
||||||
|
video.pause();
|
||||||
|
videoClone.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoClone.readyState === 4 /* HAVE_ENOUGH_DATA */)
|
||||||
|
launchPiP();
|
||||||
|
else
|
||||||
|
videoClone.onloadedmetadata = launchPiP;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg width="24px" height="24px" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
fill="var(--interactive-normal)"
|
||||||
|
d="M21 3a1 1 0 0 1 1 1v7h-2V5H4v14h6v2H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h18zm0 10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h8zm-1 2h-6v4h6v-4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}, { noop: true })
|
||||||
|
});
|
|
@ -21,14 +21,11 @@ import { VENCORD_USER_AGENT } from "@utils/constants";
|
||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
import { getCurrentChannel } from "@utils/discord";
|
import { getCurrentChannel } from "@utils/discord";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { findStoreLazy } from "@webpack";
|
import { UserProfileStore, UserStore } from "@webpack/common";
|
||||||
import { UserStore } from "@webpack/common";
|
|
||||||
|
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
import { PronounCode, PronounMapping, PronounsResponse } from "./types";
|
import { PronounCode, PronounMapping, PronounsResponse } from "./types";
|
||||||
|
|
||||||
const UserProfileStore = findStoreLazy("UserProfileStore");
|
|
||||||
|
|
||||||
type PronounsWithSource = [string | null, string];
|
type PronounsWithSource = [string | null, string];
|
||||||
const EmptyPronouns: PronounsWithSource = [null, ""];
|
const EmptyPronouns: PronounsWithSource = [null, ""];
|
||||||
|
|
||||||
|
|
|
@ -27,13 +27,12 @@ import { copyWithToast } from "@utils/misc";
|
||||||
import { LazyComponent } from "@utils/react";
|
import { LazyComponent } from "@utils/react";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByCode, findByCodeLazy, findByPropsLazy, findStoreLazy } from "@webpack";
|
import { findByCode, findByCodeLazy, findByPropsLazy, findStoreLazy } from "@webpack";
|
||||||
import { Text, Tooltip } from "@webpack/common";
|
import { Text, Tooltip, UserProfileStore } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
import { VerifiedIcon } from "./VerifiedIcon";
|
import { VerifiedIcon } from "./VerifiedIcon";
|
||||||
|
|
||||||
const Section = LazyComponent(() => findByCode("().lastSection"));
|
const Section = LazyComponent(() => findByCode("().lastSection"));
|
||||||
const UserProfileStore = findStoreLazy("UserProfileStore");
|
|
||||||
const ThemeStore = findStoreLazy("ThemeStore");
|
const ThemeStore = findStoreLazy("ThemeStore");
|
||||||
const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl");
|
const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl");
|
||||||
const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"');
|
const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"');
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default definePlugin({
|
||||||
name: "vencord-debug",
|
name: "vencord-debug",
|
||||||
description: "Send Vencord Debug info",
|
description: "Send Vencord Debug info",
|
||||||
predicate: ctx => AllowedChannelIds.includes(ctx.channel.id),
|
predicate: ctx => AllowedChannelIds.includes(ctx.channel.id),
|
||||||
execute() {
|
async execute() {
|
||||||
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
||||||
|
|
||||||
const client = (() => {
|
const client = (() => {
|
||||||
|
@ -75,6 +75,10 @@ export default definePlugin({
|
||||||
OpenAsar: "openasar" in window,
|
OpenAsar: "openasar" in window,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (IS_DISCORD_DESKTOP) {
|
||||||
|
info["Last Crash Reason"] = (await DiscordNative.processUtils.getLastCrash())?.rendererCrashReason ?? "N/A";
|
||||||
|
}
|
||||||
|
|
||||||
const debugInfo = `
|
const debugInfo = `
|
||||||
**Vencord Debug Info**
|
**Vencord Debug Info**
|
||||||
>>> ${Object.entries(info).map(([k, v]) => `${k}: ${v}`).join("\n")}
|
>>> ${Object.entries(info).map(([k, v]) => `${k}: ${v}`).join("\n")}
|
||||||
|
|
|
@ -355,6 +355,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
name: "bb010g",
|
name: "bb010g",
|
||||||
id: 72791153467990016n,
|
id: 72791153467990016n,
|
||||||
},
|
},
|
||||||
|
Lumap: {
|
||||||
|
name: "lumap",
|
||||||
|
id: 635383782576357407n
|
||||||
|
},
|
||||||
Dolfies: {
|
Dolfies: {
|
||||||
name: "Dolfies",
|
name: "Dolfies",
|
||||||
id: 852892297661906993n,
|
id: 852892297661906993n,
|
||||||
|
@ -363,6 +367,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
name: "RuukuLada",
|
name: "RuukuLada",
|
||||||
id: 119705748346241027n,
|
id: 119705748346241027n,
|
||||||
},
|
},
|
||||||
|
blahajZip: {
|
||||||
|
name: "blahaj.zip",
|
||||||
|
id: 683954422241427471n,
|
||||||
|
}
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// iife so #__PURE__ works correctly
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { MessageObject } from "@api/MessageEvents";
|
import { MessageObject } from "@api/MessageEvents";
|
||||||
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack";
|
||||||
import { ChannelStore, ComponentDispatch, GuildStore, MaskedLink, ModalImageClasses, PrivateChannelsStore, SelectedChannelStore, SelectedGuildStore, UserUtils } from "@webpack/common";
|
import { ChannelStore, ComponentDispatch, FluxDispatcher, GuildStore, MaskedLink, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileStore, UserUtils } from "@webpack/common";
|
||||||
import { Guild, Message, User } from "discord-types/general";
|
import { Guild, Message, User } from "discord-types/general";
|
||||||
|
|
||||||
import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal";
|
import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal";
|
||||||
|
@ -118,6 +118,41 @@ export async function openUserProfile(id: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FetchUserProfileOptions {
|
||||||
|
friend_token?: string;
|
||||||
|
connections_role_id?: string;
|
||||||
|
guild_id?: string;
|
||||||
|
with_mutual_guilds?: boolean;
|
||||||
|
with_mutual_friends_count?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a user's profile
|
||||||
|
*/
|
||||||
|
export async function fetchUserProfile(id: string, options?: FetchUserProfileOptions) {
|
||||||
|
const cached = UserProfileStore.getUserProfile(id);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
|
FluxDispatcher.dispatch({ type: "USER_PROFILE_FETCH_START", userId: id });
|
||||||
|
|
||||||
|
const { body } = await RestAPI.get({
|
||||||
|
url: `/users/${id}/profile`,
|
||||||
|
query: {
|
||||||
|
with_mutual_guilds: false,
|
||||||
|
with_mutual_friends_count: false,
|
||||||
|
...options
|
||||||
|
},
|
||||||
|
oldFormErrors: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
FluxDispatcher.dispatch({ type: "USER_UPDATE", user: body.user });
|
||||||
|
await FluxDispatcher.dispatch({ type: "USER_PROFILE_FETCH_SUCCESS", ...body });
|
||||||
|
if (options?.guild_id && body.guild_member)
|
||||||
|
FluxDispatcher.dispatch({ type: "GUILD_MEMBER_PROFILE_UPDATE", guildId: options.guild_id, guildMember: body.guild_member });
|
||||||
|
|
||||||
|
return UserProfileStore.getUserProfile(id);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the unique username for a user. Returns user.username for pomelo people, user.tag otherwise
|
* Get the unique username for a user. Returns user.username for pomelo people, user.tag otherwise
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -48,6 +48,7 @@ export let PoggerModeSettingsStore: GenericStore;
|
||||||
|
|
||||||
export let GuildStore: Stores.GuildStore & t.FluxStore;
|
export let GuildStore: Stores.GuildStore & t.FluxStore;
|
||||||
export let UserStore: Stores.UserStore & t.FluxStore;
|
export let UserStore: Stores.UserStore & t.FluxStore;
|
||||||
|
export let UserProfileStore: GenericStore;
|
||||||
export let SelectedChannelStore: Stores.SelectedChannelStore & t.FluxStore;
|
export let SelectedChannelStore: Stores.SelectedChannelStore & t.FluxStore;
|
||||||
export let SelectedGuildStore: t.FluxStore & Record<string, any>;
|
export let SelectedGuildStore: t.FluxStore & Record<string, any>;
|
||||||
export let ChannelStore: Stores.ChannelStore & t.FluxStore;
|
export let ChannelStore: Stores.ChannelStore & t.FluxStore;
|
||||||
|
@ -86,6 +87,7 @@ export const useStateFromStores: <T>(
|
||||||
|
|
||||||
waitForStore("DraftStore", s => DraftStore = s);
|
waitForStore("DraftStore", s => DraftStore = s);
|
||||||
waitForStore("UserStore", s => UserStore = s);
|
waitForStore("UserStore", s => UserStore = s);
|
||||||
|
waitForStore("UserProfileStore", m => UserProfileStore = m);
|
||||||
waitForStore("ChannelStore", m => ChannelStore = m);
|
waitForStore("ChannelStore", m => ChannelStore = m);
|
||||||
waitForStore("SelectedChannelStore", m => SelectedChannelStore = m);
|
waitForStore("SelectedChannelStore", m => SelectedChannelStore = m);
|
||||||
waitForStore("SelectedGuildStore", m => SelectedGuildStore = m);
|
waitForStore("SelectedGuildStore", m => SelectedGuildStore = m);
|
||||||
|
|
16
src/webpack/common/types/components.d.ts
vendored
16
src/webpack/common/types/components.d.ts
vendored
|
@ -398,12 +398,18 @@ export type Paginator = ComponentType<{
|
||||||
hideMaxPage?: boolean;
|
hideMaxPage?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type MaskedLink = ComponentType<{
|
export type MaskedLink = ComponentType<PropsWithChildren<{
|
||||||
onClick(): void;
|
|
||||||
trusted: boolean;
|
|
||||||
title: string,
|
|
||||||
href: string;
|
href: string;
|
||||||
}>;
|
rel?: string;
|
||||||
|
target?: string;
|
||||||
|
title?: string,
|
||||||
|
className?: string;
|
||||||
|
tabIndex?: number;
|
||||||
|
onClick?(): void;
|
||||||
|
trusted?: boolean;
|
||||||
|
messageId?: string;
|
||||||
|
channelId?: string;
|
||||||
|
}>>;
|
||||||
|
|
||||||
export type ScrollerThin = ComponentType<PropsWithChildren<{
|
export type ScrollerThin = ComponentType<PropsWithChildren<{
|
||||||
className?: string;
|
className?: string;
|
||||||
|
|
Loading…
Reference in a new issue