Skip to content

Commit e440f88

Browse files
committed
Fix commands
1 parent b998a2c commit e440f88

File tree

17 files changed

+441
-103
lines changed

17 files changed

+441
-103
lines changed

injections/vars.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,14 @@ request.formData = async (input, init) => {
4747
}
4848

4949
function cache<T>(factory: () => T): Cache<T> {
50-
const value = { } as { current: T } | { };
50+
const value = { count: 0 } as { current: T, count: number } | { count: number };
5151

5252
function cache() {
53+
if (value.count++ > (limit - 1)) cache.reset();
5354
if ("current" in value) return value.current;
5455

5556
const current = factory();
56-
(value as { current: T }).current = current;
57+
(value as { current: T, count: number }).current = current;
5758

5859
return current;
5960
}
@@ -65,8 +66,22 @@ function cache<T>(factory: () => T): Cache<T> {
6566
cache.reset = () => {
6667
// @ts-expect-error
6768
if ("current" in value) delete value.current;
69+
value.count = 0;
6870
};
6971

72+
let limit = Infinity;
73+
Object.defineProperty(cache, "CALL_LIMIT", {
74+
get: () => limit,
75+
set: (v) => {
76+
if (typeof v !== "number" || isNaN(v) || v <= 0 || Math.round(v) !== v) {
77+
throw new Error("Unable to set max call threshould. Value is not a positive int");
78+
}
79+
80+
limit = v;
81+
cache.reset();
82+
}
83+
})
84+
7085
Object.defineProperty(cache, "get", {
7186
get: () => cache()
7287
});

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vx",
3-
"version": "1.0.42",
3+
"version": "1.0.43",
44
"author": "doggybootsy",
55
"main": "index.js",
66
"dependencies": {

packages/desktop/main/addons.ts

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { app, WebContents } from "electron";
1+
import { app, BrowserWindow, WebContents } from "electron";
22
import { watch } from "chokidar";
33
import { existsSync, mkdirSync } from "fs";
44
import path, { basename, extname } from "path";
@@ -8,29 +8,29 @@ const vxDir = path.join(appData, ".vx");
88

99
if (!existsSync(vxDir)) mkdirSync(vxDir);
1010

11-
export function setupWindow(webContents: WebContents) {
12-
function createWatcher(type: "themes" | "plugins") {
13-
const dir = path.join(vxDir, type);
14-
if (!existsSync(dir)) mkdirSync(dir);
11+
function createWatcher(type: "themes" | "plugins") {
12+
const dir = path.join(vxDir, type);
13+
if (!existsSync(dir)) mkdirSync(dir);
14+
15+
const watcher = watch(dir, {
16+
awaitWriteFinish: true,
17+
ignoreInitial: true,
18+
atomic: true
19+
});
1520

16-
const watcher = watch(dir, {
17-
awaitWriteFinish: true,
18-
ignoreInitial: true,
19-
atomic: true
20-
});
21+
const requiredExt = type === "themes" ? ".css" : ".js";
22+
23+
watcher.on("all", (eventName, path) => {
24+
const filename = basename(path);
25+
const ext = extname(path);
2126

22-
const requiredExt = type === "themes" ? ".css" : ".js";
27+
if (ext !== requiredExt) return;
2328

24-
watcher.on("all", (eventName, path) => {
25-
const filename = basename(path);
26-
const ext = extname(path);
27-
28-
if (ext !== requiredExt) return;
29+
for (const window of BrowserWindow.getAllWindows()) {
30+
window.webContents.send(`@vx/addons/${type}`, eventName, filename);
31+
}
32+
});
33+
}
2934

30-
webContents.send(`@vx/addons/${type}`, eventName, filename);
31-
});
32-
}
33-
34-
createWatcher("plugins");
35-
createWatcher("themes");
36-
}
35+
createWatcher("plugins");
36+
createWatcher("themes");

packages/desktop/main/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import "./ipc";
88
import "./request";
99
import "./colors";
1010
import "./spotify";
11+
import "./addons";
1112
import { env } from "vx:self";
1213

1314
console.log(`Welcome to VX v${env.VERSION}`);

packages/desktop/main/window.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import electron, { BrowserWindowConstructorOptions } from "electron";
22
import path from "node:path";
33
import { UndefinedSymbol, Storage } from "./storage";
44
import { env } from "vx:self";
5-
import { setupWindow } from "./addons";
65

76
const preloadSymbol = Symbol.for("vx.browserwindow.preload");
87

@@ -98,11 +97,7 @@ export class BrowserWindow extends electron.BrowserWindow {
9897
window.webContents.on("devtools-opened", () => {
9998
window.webContents.devToolsWebContents?.executeJavaScript(script);
10099
});
101-
102-
if (originalPreload.includes("main")) {
103-
setupWindow(window.webContents);
104-
}
105-
100+
106101
// Open devtools in devtools
107102
// const devtools = new electron.BrowserWindow();
108103
// devtools.webContents.openDevTools({ mode: "right" });

packages/mod/src/api/commands/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ function addCommand(command: Command) {
1414
},
1515
get name() { return command.name },
1616
get displayName() { return command.name },
17+
get untranslatedName() { return command.name },
1718
get description() { return command.description || "" },
1819
get displayDescription() { return command.description || "" },
20+
get untranslatedDescription() { return command.description || "" },
1921
isVX: true
2022
}
2123

packages/mod/src/api/menu/components.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ interface MenuItemRenderProps {
2323

2424
type MenuItemLabelAndRenderProps = { label: React.ReactNode } | { render(props: MenuItemRenderProps): React.ReactNode };
2525

26-
interface BaseMenuItemProps extends Record<string, any> {
26+
export interface BaseMenuItemProps extends Record<string, any> {
2727
id: string,
2828
disabled?: boolean,
2929
action?(event: React.MouseEvent): void,
@@ -38,9 +38,9 @@ interface BaseMenuItemProps extends Record<string, any> {
3838
imageUrl?(props: unknown): string
3939
};
4040

41-
type MenuItemProps = BaseMenuItemProps & MenuItemLabelAndRenderProps
41+
export type MenuItemProps = BaseMenuItemProps & MenuItemLabelAndRenderProps
4242

43-
interface MenuCheckboxItemProps {
43+
export interface MenuCheckboxItemProps {
4444
id: string,
4545
label: string,
4646
disabled?: boolean,
@@ -49,45 +49,45 @@ interface MenuCheckboxItemProps {
4949
checked: boolean
5050
};
5151

52-
interface MenuControlProps {
52+
export interface MenuControlProps {
5353
disabled?: boolean,
5454
isFocused: boolean,
5555
onClose(): void
5656
};
57-
interface MenuControlRef {
57+
export interface MenuControlRef {
5858
activate(): void,
5959
blur(): void,
6060
focus(): void
6161
};
6262

63-
interface MenuControlItemProps {
63+
export interface MenuControlItemProps {
6464
id: string,
6565
label?: string,
6666
disabled?: boolean,
6767
control(props: MenuControlProps, ref: { ref: null | void | MenuControlRef }): React.ReactElement
6868
};
6969

70-
interface MenuSliderControlProps extends MenuControlProps {
70+
export interface MenuSliderControlProps extends MenuControlProps {
7171
value: number,
7272
maxValue?: number,
7373
minValue?: number
7474
onChange(value: number): void,
7575
renderValue?(value: number): React.ReactNode,
7676
ref: { ref: null | void | MenuControlRef }
7777
};
78-
interface MenuSearchControlProps extends MenuControlProps {
78+
export interface MenuSearchControlProps extends MenuControlProps {
7979
query: string,
8080
onChange(value: string): void,
8181
loading?: boolean,
8282
placeholder?: string
8383
ref: { ref: null | void | MenuControlRef }
8484
}
8585

86-
interface MenuGroupProps {
86+
export interface MenuGroupProps {
8787
label?: string,
8888
children?: React.ReactNode
8989
};
90-
interface MenuRadioItemProps extends MenuCheckboxItemProps {
90+
export interface MenuRadioItemProps extends MenuCheckboxItemProps {
9191
group: string
9292
};
9393

packages/mod/src/api/menu/patch.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,22 @@ export function patch(caller: string, menuId: string, callback: MenuCallback) {
1515

1616
return () => void menusPatches.delete(callback);
1717
}
18-
export function unpatch(caller: string, menuId?: string) {
18+
export function unpatch(caller: string, menuId?: string, callback?: MenuCallback) {
1919
if (!menuPatches.has(caller)) return;
2020

2121
const callerPatches = menuPatches.get(caller)!;
22-
if (typeof menuId !== "string") {
23-
callerPatches.clear();
22+
if (typeof callback === "function") {
23+
const menusPatches = callerPatches.get(menuId!)!;
24+
menusPatches.delete(callback);
25+
return;
26+
}
27+
if (typeof menuId === "string") {
28+
const menusPatches = callerPatches.get(menuId)!;
29+
menusPatches.clear();
2430
return;
2531
}
2632

27-
const menusPatches = callerPatches.get(menuId)!;
28-
menusPatches.clear();
33+
callerPatches.clear();
2934
}
3035

3136
addPlainTextPatch({
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { useState } from "react";
2+
import { closeMenu, MenuCallback, MenuComponents, MenuConfig, MenuRenderProps, openMenu, patch, unpatch } from "../api/menu";
3+
import { BaseMenuItemProps, MenuCheckboxItemProps, MenuControlItemProps, MenuRadioItemProps } from "../api/menu/components";
4+
5+
interface MenuItemSeparator {
6+
type: "separator"
7+
}
8+
interface MenuItemSubmenu extends BaseMenuItemProps {
9+
type: "submenu",
10+
render?: MenuItem[],
11+
items?: MenuItem[],
12+
danger?: boolean,
13+
onClick?(event: React.MouseEvent): void,
14+
}
15+
interface MenuItemDefault extends BaseMenuItemProps {
16+
type?: "item",
17+
danger?: boolean,
18+
onClick?(event: React.MouseEvent): void,
19+
}
20+
interface MenuItemRadio extends MenuRadioItemProps {
21+
type: "radio",
22+
danger?: boolean,
23+
onClick?(event: React.MouseEvent): void,
24+
active?: boolean
25+
}
26+
interface MenuItemCheckbox extends MenuCheckboxItemProps {
27+
type: "toggle",
28+
danger?: boolean,
29+
onClick?(event: React.MouseEvent): void,
30+
active?: boolean
31+
}
32+
interface MenuItemControl extends MenuControlItemProps {
33+
type: "control"
34+
}
35+
interface MenuItemGroup {
36+
type: "group",
37+
items: MenuItem[]
38+
}
39+
type MenuItem = MenuItemSeparator | MenuItemSubmenu | MenuItemDefault | MenuItemRadio | MenuItemCheckbox | MenuItemControl;
40+
41+
export class ContextMenu {
42+
patch(menuId: string, callback: MenuCallback) {
43+
return patch("betterdiscord", menuId, callback);
44+
}
45+
unpatch(menuId: string, callback: MenuCallback) {
46+
unpatch("betterdiscord", menuId, callback);
47+
}
48+
49+
buildItem(props: MenuItem) {
50+
if (props.type === "separator") return <MenuComponents.MenuSeparator />;
51+
52+
let Component: React.ComponentType<any> = MenuComponents.MenuItem;
53+
if (props.type === "submenu") {
54+
if (!props.children) props.children = this.buildMenuChildren((props.render || props.items)!);
55+
}
56+
else if (props.type === "toggle" || props.type === "radio") {
57+
Component = props.type === "toggle" ? MenuComponents.MenuCheckboxItem : MenuComponents.MenuRadioItem;
58+
if (props.active) props.checked = props.active;
59+
}
60+
else if (props.type === "control") {
61+
Component = MenuComponents.MenuControlItem;
62+
}
63+
64+
if (props.type !== "control") {
65+
if (!props.id) props.id = `${props.label.replace(/^[^a-z]+|[^\w-]+/gi, "-")}`;
66+
// @ts-expect-error
67+
if (props.danger) props.color = "danger";
68+
if (props.onClick && !props.action) props.action = props.onClick;
69+
// @ts-expect-error
70+
props.extended = true;
71+
}
72+
73+
// This is done to make sure the UI actually displays the on/off correctly
74+
if (props.type === "toggle") {
75+
const [ active, doToggle ] = useState(props.checked || false);
76+
const originalAction = props.action;
77+
props.checked = active;
78+
props.action = function(event: React.MouseEvent) {
79+
doToggle(!active);
80+
originalAction?.(event);
81+
}
82+
}
83+
84+
return <Component {...props} />;
85+
}
86+
buildMenuChildren(setup: (MenuItem | MenuItemGroup)[]) {
87+
function mapper(this: ContextMenu, item: MenuItem | MenuItemGroup) {
88+
if (item.type === "group") return buildGroup.call(this, item);
89+
return this.buildItem(item);
90+
};
91+
function buildGroup(this: ContextMenu, group: MenuItemGroup): React.ReactNode {
92+
const items = group.items.map(mapper.bind(this)).filter(i => i);
93+
return <MenuComponents.MenuGroup>{items}</MenuComponents.MenuGroup>;
94+
}
95+
96+
return setup.map(mapper.bind(this)).filter(i => i);
97+
}
98+
buildMenu(setup: (MenuItem | MenuItemGroup)[]): React.ComponentType<MenuRenderProps & { onClose(): void }> {
99+
return (props) => <MenuComponents.Menu {...props} navId={(props as any).navId || "betterdiscord-menu"}>{this.buildMenuChildren(setup)}</MenuComponents.Menu>
100+
}
101+
102+
open(event: MouseEvent | React.MouseEvent, MenuComponent: React.ComponentType<MenuRenderProps & { onClose(): void }>, config: MenuConfig) {
103+
return openMenu(event, (props) => (
104+
<MenuComponent {...props} onClose={closeMenu} />
105+
), config);
106+
}
107+
close() {
108+
closeMenu();
109+
}
110+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ContextMenu } from "./context-menu";
2+
import { Patcher } from "./patcher";
3+
4+
const contextMenuApi = new ContextMenu();
5+
6+
const bounded = new Map<string, BdApi>();
7+
8+
export class BdApi {
9+
#name!: string;
10+
constructor(name: string) {
11+
// @ts-expect-error
12+
if (!name) return BdApi;
13+
if (bounded.has(name)) return bounded.get(name)!;
14+
15+
this.#name = name;
16+
bounded.set(name, this);
17+
18+
this.Patcher = new Patcher(name);
19+
}
20+
21+
static readonly ContextMenu = contextMenuApi;
22+
readonly ContextMenu = contextMenuApi;
23+
static readonly Patcher = Patcher;
24+
readonly Patcher!: Patcher;
25+
26+
}

0 commit comments

Comments
 (0)