Skip to content

Commit

Permalink
Merge pull request #28 from redstonekasi/contextMenu
Browse files Browse the repository at this point in the history
Context menu library
  • Loading branch information
Cynosphere authored May 8, 2024
2 parents c06ae47 + 90deda9 commit 6084592
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 0 deletions.
37 changes: 37 additions & 0 deletions packages/core-extensions/src/contextMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";

export const patches: Patch[] = [
{
find: "Menu API only allows Items and groups of Items as children.",
replace: [
{
match:
/(?<=let{navId[^}]+?}=(.),(.)=function .\(.\){.+(?=,.=function))/,
replacement: (_, props, items) =>
`,__contextMenu=!${props}.__contextMenu_evilMenu&&require("contextMenu_contextMenu")._patchMenu(${props}, ${items})`
}
]
},
{
find: ".getContextMenu(",
replace: [
{
match: /(?<=let\{[^}]+?\}=.;return ).\({[^}]+?}\)/,
replacement: (render) =>
`require("contextMenu_contextMenu")._saveProps(${render})`
}
]
}
];

export const webpackModules: Record<string, ExtensionWebpackModule> = {
contextMenu: {
dependencies: [{ ext: "spacepack", id: "spacepack" }, "MenuGroup:"]
},
evilMenu: {
dependencies: [
{ ext: "spacepack", id: "spacepack" },
"Menu API only allows Items and groups of Items as children."
]
}
};
9 changes: 9 additions & 0 deletions packages/core-extensions/src/contextMenu/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"id": "contextMenu",
"meta": {
"name": "Context Menu",
"tagline": "A library for patching and creating context menus",
"authors": ["redstonekasi"],
"tags": ["library"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {
InternalItem,
MenuElement,
MenuProps
} from "@moonlight-mod/types/coreExtensions/contextMenu";
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
import parser from "@moonlight-mod/wp/contextMenu_evilMenu";

type Patch = {
navId: string;
item: (
props: any
) =>
| React.ReactComponentElement<MenuElement>
| React.ReactComponentElement<MenuElement>[];
anchorId: string;
before: boolean;
};

export function addItem<T>(
navId: string,
item: (
props: T
) =>
| React.ReactComponentElement<MenuElement>
| React.ReactComponentElement<MenuElement>[],
anchorId: string,
before = false
) {
patches.push({ navId, item, anchorId, before });
}

export const patches: Patch[] = [];
function _patchMenu(props: MenuProps, items: InternalItem[]) {
const matches = patches.filter((p) => p.navId === props.navId);
if (!matches.length) return;

for (const patch of matches) {
const idx = items.findIndex((i) => i.key === patch.anchorId);
if (idx === -1) continue;
items.splice(idx + 1 - +patch.before, 0, ...parser(patch.item(menuProps)));
}
}

let menuProps: any;
function _saveProps(el: any) {
menuProps = el.props;

const original = el.props.config.onClose;
el.props.config.onClose = function (...args: any[]) {
menuProps = undefined;
return original?.apply(this, args);
};

return el;
}

const MenuElements = spacepack.findByCode("return null", "MenuGroup:")[0]
.exports;

module.exports = {
...MenuElements,
addItem,
_patchMenu,
_saveProps
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";

let code =
spacepack.require.m[
spacepack.findByCode(
"Menu API only allows Items and groups of Items as children."
)[0].id
].toString();
code = code.replace(/,.=(?=function .\(.\){.+?,.=function)/, ";return ");
code = code.replace(/,(?=__contextMenu)/, ";let ");
const mod = new Function(
"module",
"exports",
"require",
`(${code}).apply(this, arguments)`
);
const exp: any = {};
mod({}, exp, require);
module.exports = (el: any) => {
return exp.Menu({
children: el,
__contextMenu_evilMenu: true
});
};
1 change: 1 addition & 0 deletions packages/types/src/coreExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,4 @@ export type CommonComponents = CommonComponents_; // lol
export type CommonFluxDispatcher = Dispatcher<any>;

export * as Markdown from "./coreExtensions/markdown";
export * as ContextMenu from "./coreExtensions/contextMenu";
154 changes: 154 additions & 0 deletions packages/types/src/coreExtensions/contextMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// TODO: Deduplicate common props

export type Menu = React.FunctionComponent<{
navId: string;
variant?: string;
hideScrollbar?: boolean;
className?: string;
children: React.ReactComponentElement<MenuElement>[];
onClose?: () => void;
onSelect?: () => void;
}>;
export type MenuProps = React.ComponentProps<Menu>;

export type MenuElement =
| MenuSeparator
| MenuGroup
| MenuItem
| MenuCheckboxItem
| MenuRadioItem
| MenuControlItem;

/* eslint-disable prettier/prettier */
export type MenuSeparator = React.FunctionComponent;
export type MenuGroup = React.FunctionComponent<{
label?: string;
className?: string;
color?: string;
children: React.ReactComponentElement<MenuElement>[];
}>;
export type MenuItem = React.FunctionComponent<{
id: any;
dontCloseOnActionIfHoldingShiftKey?: boolean;
} & ({
label: string;
subtext?: string;
color?: string;
hint?: string;
disabled?: boolean;
icon?: any;
showIconFirst?: boolean;
imageUrl?: string;

className?: string;
focusedClassName?: string;
subMenuIconClassName?: string;

action?: () => void;
onFocus?: () => void;

iconProps?: any;
sparkle?: any;

children?: React.ReactComponentElement<MenuElement>[];
onChildrenScroll?: any;
childRowHeight?: any;
listClassName?: string;
subMenuClassName?: string;
} | {
color?: string;
disabled?: boolean;
keepItemStyles?: boolean;

action?: () => void;

render: any;
navigable?: boolean;
})>;
export type MenuCheckboxItem = React.FunctionComponent<{
id: any;
label: string;
subtext?: string;
color?: string;
className?: string;
focusedClassName?: string;
disabled?: boolean;
checked: boolean;
action?: () => void;
}>;
export type MenuRadioItem = React.FunctionComponent<{
id: any;
label: string;
subtext?: string;
color?: string;
disabled?: boolean;
action?: () => void;
}>;
export type MenuControlItem = React.FunctionComponent<{
id: any;
label: string;
color?: string;
disabled?: boolean;
showDefaultFocus?: boolean;
} & ({
control: any;
} | {
control?: undefined;
interactive?: boolean;
children?: React.ReactComponentElement<MenuElement>[];
})>;
/* eslint-disable prettier/prettier */

export type ContextMenu = {
addItem: (
navId: string,
item: (props: any) => React.ReactComponentElement<MenuElement>,
anchorId: string,
before?: boolean
) => void;

MenuCheckboxItem: MenuCheckboxItem;
MenuControlItem: MenuControlItem;
MenuGroup: MenuGroup;
MenuItem: MenuItem;
MenuRadioItem: MenuRadioItem;
MenuSeparator: MenuSeparator;
};

export type InternalItem = {
type: string;
key?: string;
};

export type InternalSeparator = {
type: "separator";
navigable: false;
};
export type InternalGroupStart = {
type: "groupstart";
length: number;
navigable: false;
props: React.ComponentProps<MenuGroup>;
};
export type InternalGroupEnd = {
type: "groupend";
} & Omit<InternalGroupStart, "type">;
export type InternalCustomItem = {
type: "customitem";
key: any;
navigable?: boolean;
render: any;
props: Extract<React.ComponentProps<MenuItem>, { render: any }>;
};
export type InternalItem_ = {
type: "item";
key: any;
navigable: true;
label: string;
};

export type EvilItemParser = (
el:
| React.ReactComponentElement<MenuElement>
| React.ReactComponentElement<MenuElement>[]
) => InternalItem[];
4 changes: 4 additions & 0 deletions packages/types/src/discord/require.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
CommonComponents,
CommonFluxDispatcher
} from "../coreExtensions";
import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu";
import { Markdown } from "../coreExtensions/markdown";

declare function WebpackRequire(id: string): any;
Expand All @@ -28,4 +29,7 @@ declare function WebpackRequire(id: "settings_settings"): {

declare function WebpackRequire(id: "markdown_markdown"): Markdown;

declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser;
declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu;

export default WebpackRequire;
12 changes: 12 additions & 0 deletions packages/types/src/import.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,15 @@ declare module "@moonlight-mod/wp/markdown_markdown" {
const Markdown: CoreExtensions.Markdown.Markdown;
export = Markdown;
}

declare module "@moonlight-mod/wp/contextMenu_evilMenu" {
import { CoreExtensions } from "@moonlight-mod/types";
const EvilParser: CoreExtensions.ContextMenu.EvilItemParser;
export = EvilParser;
}

declare module "@moonlight-mod/wp/contextMenu_contextMenu" {
import { CoreExtensions } from "@moonlight-mod/types";
const ContextMenu: CoreExtensions.ContextMenu.ContextMenu;
export = ContextMenu;
}

0 comments on commit 6084592

Please sign in to comment.