aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/plugins/apiBadges.tsx51
-rw-r--r--src/plugins/devCompanion.dev.tsx24
-rw-r--r--src/plugins/vencordToolbox.tsx142
-rw-r--r--src/utils/types.ts5
-rw-r--r--src/webpack/common/components.ts4
-rw-r--r--src/webpack/common/types/components.d.ts50
6 files changed, 244 insertions, 32 deletions
diff --git a/src/plugins/apiBadges.tsx b/src/plugins/apiBadges.tsx
index bf1906f..48e9eb9 100644
--- a/src/plugins/apiBadges.tsx
+++ b/src/plugins/apiBadges.tsx
@@ -26,7 +26,7 @@ import Logger from "@utils/Logger";
import { Margins } from "@utils/margins";
import { closeModal, Modals, openModal } from "@utils/modal";
import definePlugin from "@utils/types";
-import { Forms } from "@webpack/common";
+import { Forms, Toasts } from "@webpack/common";
const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/attachments/1033680203433660458/1092089947126780035/favicon.png";
@@ -49,6 +49,26 @@ const ContributorBadge: ProfileBadge = {
const DonorBadges = {} as Record<string, Pick<ProfileBadge, "image" | "description">[]>;
+async function loadBadges(noCache = false) {
+ const init = {} as RequestInit;
+ if (noCache)
+ init.cache = "no-cache";
+
+ const badges = await fetch("https://gist.githubusercontent.com/Vendicated/51a3dd775f6920429ec6e9b735ca7f01/raw/badges.csv", init)
+ .then(r => r.text());
+
+ const lines = badges.trim().split("\n");
+ if (lines.shift() !== "id,tooltip,image") {
+ new Logger("BadgeAPI").error("Invalid badges.csv file!");
+ return;
+ }
+
+ for (const line of lines) {
+ const [id, description, image] = line.split(",");
+ (DonorBadges[id] ??= []).push({ image, description });
+ }
+}
+
export default definePlugin({
name: "BadgeAPI",
description: "API to add badges to users.",
@@ -81,24 +101,27 @@ export default definePlugin({
}
],
+ toolboxActions: {
+ async "Refetch Badges"() {
+ await loadBadges(true);
+ Toasts.show({
+ id: Toasts.genId(),
+ message: "Successfully refetched badges!",
+ type: Toasts.Type.SUCCESS
+ });
+ }
+ },
+
+ async start() {
+ Vencord.Api.Badges.addBadge(ContributorBadge);
+ await loadBadges();
+ },
+
renderBadgeComponent: ErrorBoundary.wrap((badge: ProfileBadge & BadgeUserArgs) => {
const Component = badge.component!;
return <Component {...badge} />;
}, { noop: true }),
- async start() {
- Vencord.Api.Badges.addBadge(ContributorBadge);
- const badges = await fetch("https://gist.githubusercontent.com/Vendicated/51a3dd775f6920429ec6e9b735ca7f01/raw/badges.csv").then(r => r.text());
- const lines = badges.trim().split("\n");
- if (lines.shift() !== "id,tooltip,image") {
- new Logger("BadgeAPI").error("Invalid badges.csv file!");
- return;
- }
- for (const line of lines) {
- const [id, description, image] = line.split(",");
- (DonorBadges[id] ??= []).push({ image, description });
- }
- },
getDonorBadges(userId: string) {
return DonorBadges[userId]?.map(badge => ({
diff --git a/src/plugins/devCompanion.dev.tsx b/src/plugins/devCompanion.dev.tsx
index 73bf56f..161cdf9 100644
--- a/src/plugins/devCompanion.dev.tsx
+++ b/src/plugins/devCompanion.dev.tsx
@@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { showNotification } from "@api/Notifications";
import { definePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
@@ -24,7 +23,6 @@ import Logger from "@utils/Logger";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types";
import { filters, findAll, search } from "@webpack";
-import { Menu } from "@webpack/common";
const PORT = 8485;
const NAV_ID = "dev-companion-reconnect";
@@ -238,33 +236,25 @@ function initWs(isManual = false) {
});
}
-const contextMenuPatch: NavContextMenuPatchCallback = children => () => {
- children.unshift(
- <Menu.MenuItem
- id={NAV_ID}
- label="Reconnect Dev Companion"
- action={() => {
- socket?.close(1000, "Reconnecting");
- initWs(true);
- }}
- />
- );
-};
-
export default definePlugin({
name: "DevCompanion",
description: "Dev Companion Plugin",
authors: [Devs.Ven],
settings,
+ toolboxActions: {
+ "Reconnect"() {
+ socket?.close(1000, "Reconnecting");
+ initWs(true);
+ }
+ },
+
start() {
initWs();
- addContextMenuPatch("user-settings-cog", contextMenuPatch);
},
stop() {
socket?.close(1000, "Plugin Stopped");
socket = void 0;
- removeContextMenuPatch("user-settings-cog", contextMenuPatch);
}
});
diff --git a/src/plugins/vencordToolbox.tsx b/src/plugins/vencordToolbox.tsx
new file mode 100644
index 0000000..52ebb65
--- /dev/null
+++ b/src/plugins/vencordToolbox.tsx
@@ -0,0 +1,142 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2023 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+import { openNotificationLogModal } from "@api/Notifications/notificationLog";
+import ErrorBoundary from "@components/ErrorBoundary";
+import { Devs } from "@utils/constants";
+import IpcEvents from "@utils/IpcEvents";
+import { LazyComponent } from "@utils/misc";
+import definePlugin from "@utils/types";
+import { findByCode } from "@webpack";
+import { Menu, Popout, useState } from "@webpack/common";
+import type { ReactNode } from "react";
+
+const HeaderBarIcon = LazyComponent(() => findByCode(".HEADER_BAR_BADGE,", ".tooltip"));
+
+function VencordPopout(onClose: () => void) {
+ const pluginEntries = [] as ReactNode[];
+
+ for (const plugin of Object.values(Vencord.Plugins.plugins)) {
+ if (plugin.toolboxActions) {
+ pluginEntries.push(
+ <Menu.MenuGroup
+ label={plugin.name}
+ key={`vc-toolbox-${plugin.name}`}
+ >
+ {Object.entries(plugin.toolboxActions).map(([text, action]) => {
+ const key = `vc-toolbox-${plugin.name}-${text}`;
+
+ return (
+ <Menu.MenuItem
+ id={key}
+ key={key}
+ label={text}
+ action={action}
+ />
+ );
+ })}
+ </Menu.MenuGroup>
+ );
+ }
+ }
+
+ return (
+ <Menu.Menu
+ navId="vc-toolbox"
+ onClose={onClose}
+ >
+ <Menu.MenuItem
+ id="vc-toolbox-notifications"
+ label="Open Notification Log"
+ action={openNotificationLogModal}
+ />
+ <Menu.MenuItem
+ id="vc-toolbox-quickcss"
+ label="Open QuickCSS"
+ action={() => VencordNative.ipc.invoke(IpcEvents.OPEN_MONACO_EDITOR)}
+ />
+ {...pluginEntries}
+ </Menu.Menu>
+ );
+}
+
+function VencordPopoutIcon() {
+ return (
+ <img
+ width={24}
+ height={24}
+ src="https://raw.githubusercontent.com/Vencord/Website/main/public/assets/favicon.png"
+ alt="Vencord Toolbox"
+ />
+ );
+}
+
+function VencordPopoutButton() {
+ const [show, setShow] = useState(false);
+
+ return (
+ <Popout
+ position="bottom"
+ align="right"
+ animation={Popout.Animation.NONE}
+ shouldShow={show}
+ onRequestClose={() => setShow(false)}
+ renderPopout={() => VencordPopout(() => setShow(false))}
+ >
+ {(_, { isShown }) => (
+ <HeaderBarIcon
+ onClick={() => setShow(v => !v)}
+ tooltip={isShown ? null : "Vencord Toolbox"}
+ icon={VencordPopoutIcon}
+ selected={isShown}
+ />
+ )}
+ </Popout>
+ );
+}
+
+function ToolboxFragmentWrapper({ children }: { children: ReactNode[]; }) {
+ children.splice(
+ children.length - 1, 0,
+ <ErrorBoundary noop={true}>
+ <VencordPopoutButton />
+ </ErrorBoundary>
+ );
+
+ return <>{children}</>;
+}
+
+export default definePlugin({
+ name: "VencordToolbox",
+ description: "Adds a button next to the inbox button in the channel header that houses Vencord quick actions",
+ authors: [Devs.Ven],
+
+ patches: [
+ {
+ find: ".mobileToolbar",
+ replacement: {
+ match: /(?<=toolbar:function.{0,100}\()\i.Fragment,/,
+ replace: "$self.ToolboxFragmentWrapper,"
+ }
+ }
+ ],
+
+ ToolboxFragmentWrapper: ErrorBoundary.wrap(ToolboxFragmentWrapper, {
+ fallback: () => <p style={{ color: "red" }}>Failed to render :(</p>
+ })
+});
diff --git a/src/utils/types.ts b/src/utils/types.ts
index c1e54aa..60dc4d4 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -108,6 +108,11 @@ export interface PluginDef {
flux?: {
[E in FluxEvents]?: (event: any) => void;
};
+ /**
+ * Allows you to add custom actions to the Vencord Toolbox.
+ * The key will be used as text for the button
+ */
+ toolboxActions?: Record<string, () => void>;
tags?: string[];
}
diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts
index 9554f95..97816bf 100644
--- a/src/webpack/common/components.ts
+++ b/src/webpack/common/components.ts
@@ -40,6 +40,8 @@ export let Select: t.Select;
export let SearchableSelect: t.SearchableSelect;
export let Slider: t.Slider;
export let ButtonLooks: t.ButtonLooks;
+export let Popout: t.Popout;
+export let Dialog: t.Dialog;
export let TabBar: any;
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"));
@@ -48,6 +50,6 @@ export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"
export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>;
waitFor("FormItem", m => {
- ({ Card, Button, FormSwitch: Switch, Tooltip, TextInput, TextArea, Text, Select, SearchableSelect, Slider, ButtonLooks, TabBar } = m);
+ ({ Card, Button, FormSwitch: Switch, Tooltip, TextInput, TextArea, Text, Select, SearchableSelect, Slider, ButtonLooks, TabBar, Popout, Dialog } = m);
Forms = m;
});
diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts
index 7b02ed3..835de79 100644
--- a/src/webpack/common/types/components.d.ts
+++ b/src/webpack/common/types/components.d.ts
@@ -325,3 +325,53 @@ export type Flex = ComponentType<PropsWithChildren<any>> & {
Justify: Record<"START" | "END" | "CENTER" | "BETWEEN" | "AROUND", string>;
Wrap: Record<"NO_WRAP" | "WRAP" | "WRAP_REVERSE", string>;
};
+
+declare enum PopoutAnimation {
+ NONE = "1",
+ TRANSLATE = "2",
+ SCALE = "3",
+ FADE = "4"
+}
+
+export type Popout = ComponentType<{
+ children(
+ thing: {
+ "aria-controls": string;
+ "aria-expanded": boolean;
+ onClick(event: MouseEvent): void;
+ onKeyDown(event: KeyboardEvent): void;
+ onMouseDown(event: MouseEvent): void;
+ },
+ data: {
+ isShown: boolean;
+ position: string;
+ }
+ ): ReactNode;
+ shouldShow: boolean;
+ renderPopout(args: {
+ closePopout(): void;
+ isPositioned: boolean;
+ nudge: number;
+ position: string;
+ setPopoutRef(ref: any): void;
+ updatePosition(): void;
+ }): ReactNode;
+
+ onRequestOpen?(): void;
+ onRequestClose?(): void;
+
+ /** "center" and others */
+ align?: string;
+ /** Popout.Animation */
+ animation?: PopoutAnimation;
+ autoInvert?: boolean;
+ nudgeAlignIntoViewport?: boolean;
+ /** "bottom" and others */
+ position?: string;
+ positionKey?: string;
+ spacing?: number;
+}> & {
+ Animation: typeof PopoutAnimation;
+};
+
+export type Dialog = ComponentType<PropsWithChildren<any>>;