Merge pull request #168 from moonlight-mod/componentEditor
Add Component Editor
Changed files
@@ -0,0 +1,75 @@+import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";++export const patches: Patch[] = [+ // dm list+ {+ find: ".interactiveSystemDM]:",+ replace: [+ {+ match: /decorators:(\i\.isSystemDM\(\)\?\(0,\i\.jsx\)\(.+?verified:!0}\):null)/,+ replacement: (_, decorators) =>+ `decorators:require("componentEditor_dmList").default._patchDecorators([${decorators}],arguments[0])`+ },+ {+ match: /(?<=selected:\i,)children:\[/,+ replacement: 'children:require("componentEditor_dmList").default._patchItems(['+ },+ {+ match: /(?<=onMouseDown:\i}\))]/,+ replacement: "],arguments[0])"+ }+ ],+ hardFail: true+ },++ // member list+ {+ find: ".lostPermission",+ replace: [+ {+ match: /(?<=\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[\i\(\),.+?\i\(\)])/,+ replacement: (_, decorators) =>+ `children:require("componentEditor_memberList").default._patchDecorators(${decorators},arguments[0])`+ },+ {+ match: /name:null==\i\?\(0,\i\.jsx\)\("span"/,+ replacement: (orig: string) =>+ `children:require("componentEditor_memberList").default._patchItems([],arguments[0]),${orig}`+ }+ ]+ },++ // messages+ {+ find: '},"new-member")),',+ replace: [+ {+ match: /(?<=\.BADGES]=)(\i);/,+ replacement: (_, badges) =>+ `require("componentEditor_messages").default._patchUsernameBadges(${badges},arguments[0]);`+ },+ {+ match: /(?<=className:\i,)badges:(\i)/,+ replacement: (_, badges) =>+ `badges:require("componentEditor_messages").default._patchBadges(${badges},arguments[0])`+ },+ {+ match: /(?<=username:\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[.+?,\i])/,+ replacement: (_, elements) =>+ `children:require("componentEditor_messages").default._patchUsername(${elements},arguments[0])`+ }+ ]+ }+];++export const webpackModules: Record<string, ExtensionWebpackModule> = {+ dmList: {+ dependencies: [{ id: "react" }]+ },+ memberList: {+ dependencies: [{ id: "react" }]+ },+ messages: {+ dependencies: [{ id: "react" }]+ }+};
@@ -0,0 +1,11 @@+{+ "$schema": "https://moonlight-mod.github.io/manifest.schema.json",+ "id": "componentEditor",+ "apiLevel": 2,+ "meta": {+ "name": "Component Editor",+ "tagline": "A library to add to commonly patched components",+ "authors": ["Cynosphere"],+ "tags": ["library"]+ }+}
@@ -0,0 +1,61 @@+import {+ DMList,+ DMListItem,+ DMListDecorator,+ DMListAnchorIndicies,+ DMListDecoratorAnchorIndicies+} from "@moonlight-mod/types/coreExtensions/componentEditor";+import React from "@moonlight-mod/wp/react";++const items: Record<string, DMListItem> = {};+const decorators: Record<string, DMListDecorator> = {};++function addEntries(+ elements: React.ReactNode[],+ entries: Record<string, DMListItem | DMListDecorator>,+ indicies: Partial<Record<keyof typeof DMListAnchorIndicies | keyof typeof DMListDecoratorAnchorIndicies, number>>,+ props: any+) {+ const originalElements = [...elements];+ for (const [id, entry] of Object.entries(entries)) {+ const component = <entry.component {...props} key={id} />;++ if (entry.anchor === undefined) {+ if (entry.before) {+ elements.splice(0, 0, component);+ } else {+ elements.push(component);+ }+ } else {+ const index = elements.indexOf(originalElements[indicies[entry.anchor]!]);+ elements.splice(index! + (entry.before ? 0 : 1), 0, component);+ }+ }+}++export const dmList: DMList = {+ addItem(id, component, anchor, before = false) {+ items[id] = {+ component,+ anchor,+ before+ };+ },+ addDecorator(id, component, anchor, before = false) {+ decorators[id] = {+ component,+ anchor,+ before+ };+ },+ _patchItems(elements, props) {+ addEntries(elements, items, DMListAnchorIndicies, props);+ return elements;+ },+ _patchDecorators(elements, props) {+ addEntries(elements, decorators, DMListDecoratorAnchorIndicies, props);+ return elements;+ }+};++export default dmList;
@@ -0,0 +1,50 @@+import {+ MemberList,+ MemberListDecorator,+ MemberListDecoratorAnchorIndicies+} from "@moonlight-mod/types/coreExtensions/componentEditor";+import React from "@moonlight-mod/wp/react";++const items: Record<string, React.FC<any>> = {};+const decorators: Record<string, MemberListDecorator> = {};++export const memberList: MemberList = {+ addItem(id, component) {+ items[id] = component;+ },+ addDecorator(id, component, anchor, before = false) {+ decorators[id] = {+ component,+ anchor,+ before+ };+ },+ _patchItems(elements, props) {+ for (const [id, Component] of Object.entries(items)) {+ elements.push(<Component {...props} key={id} />);+ }++ return elements;+ },+ _patchDecorators(elements, props) {+ const originalElements = [...elements];+ for (const [id, entry] of Object.entries(decorators)) {+ const component = <entry.component {...props} key={id} />;++ if (entry.anchor === undefined) {+ if (entry.before) {+ elements.splice(0, 0, component);+ } else {+ elements.push(component);+ }+ } else {+ const index = elements.indexOf(originalElements[MemberListDecoratorAnchorIndicies[entry.anchor]!]);+ elements.splice(index! + (entry.before ? 0 : 1), 0, component);+ }+ }++ return elements;+ }+};++export default memberList;
@@ -0,0 +1,82 @@+import {+ MessageBadge,+ MessageBadgeIndicies,+ Messages,+ MessageUsername,+ MessageUsernameBadge,+ MessageUsernameBadgeIndicies,+ MessageUsernameIndicies+} from "@moonlight-mod/types/coreExtensions/componentEditor";+import React from "@moonlight-mod/wp/react";++const username: Record<string, MessageUsername> = {};+const usernameBadges: Record<string, MessageUsernameBadge> = {};+const badges: Record<string, MessageBadge> = {};++function addEntries(+ elements: React.ReactNode[],+ entries: Record<string, MessageUsername | MessageUsernameBadge | MessageBadge>,+ indicies: Partial<+ Record<+ | keyof typeof MessageUsernameIndicies+ | keyof typeof MessageUsernameBadgeIndicies+ | keyof typeof MessageBadgeIndicies,+ number+ >+ >,+ props: any+) {+ const originalElements = [...elements];+ for (const [id, entry] of Object.entries(entries)) {+ const component = <entry.component {...props} key={id} />;++ if (entry.anchor === undefined) {+ if (entry.before) {+ elements.splice(0, 0, component);+ } else {+ elements.push(component);+ }+ } else {+ const index = elements.indexOf(originalElements[indicies[entry.anchor]!]);+ elements.splice(index! + (entry.before ? 0 : 1), 0, component);+ }+ }+}++export const messages: Messages = {+ addToUsername(id, component, anchor, before = false) {+ username[id] = {+ component,+ anchor,+ before+ };+ },+ addUsernameBadge(id, component, anchor, before = false) {+ usernameBadges[id] = {+ component,+ anchor,+ before+ };+ },+ addBadge(id, component, anchor, before = false) {+ badges[id] = {+ component,+ anchor,+ before+ };+ },+ _patchUsername(elements, props) {+ addEntries(elements, username, MessageUsernameIndicies, props);+ return elements;+ },+ _patchUsernameBadges(elements, props) {+ addEntries(elements, usernameBadges, MessageUsernameBadgeIndicies, props);+ return elements;+ },+ _patchBadges(elements, props) {+ addEntries(elements, badges, MessageBadgeIndicies, props);+ return elements;+ }+};++export default messages;
MODIFIED
packages/types/src/coreExtensions.ts
MODIFIED
packages/types/src/coreExtensions.ts
@@ -6,3 +6,4 @@ export * as Notices from "./coreExtensions/notices";export * as Moonbase from "./coreExtensions/moonbase";export * as AppPanels from "./coreExtensions/appPanels";export * as Commands from "./coreExtensions/commands";+export * as ComponentEditor from "./coreExtensions/componentEditor";
@@ -0,0 +1,145 @@+type Patcher<T> = (elements: React.ReactNode[], props: T) => React.ReactNode[];++//#region DM List+export type DMListAnchors =+ | "content"+ | "favorite-server-indicator"+ | "ignored-indicator"+ | "blocked-indicator"+ | "close-button"+ | undefined;+export type DMListDecoratorAnchors = "system-tag" | undefined;++export enum DMListAnchorIndicies {+ content = 0,+ "favorite-server-indicator",+ "ignored-indicator",+ "blocked-indicator",+ "close-button"+}+export enum DMListDecoratorAnchorIndicies {+ "system-tag" = 0+}++export type DMListItem = {+ component: React.FC<any>;+ anchor: DMListAnchors;+ before: boolean;+};+export type DMListDecorator = {+ component: React.FC<any>;+ anchor: DMListDecoratorAnchors;+ before: boolean;+};++export type DMList = {+ addItem: (id: string, component: React.FC<any>, anchor?: DMListAnchors, before?: boolean) => void;+ addDecorator: (id: string, component: React.FC<any>, anchor?: DMListDecoratorAnchors, before?: boolean) => void;+ //TODO: fix props type+ /**+ * @private+ */+ _patchItems: Patcher<any>;+ /**+ * @private+ */+ _patchDecorators: Patcher<any>;+};+//#endregion++//#region Member List+export type MemberListDecoratorAnchors = "bot-tag" | "owner-crown" | "boost-icon" | undefined;++export enum MemberListDecoratorAnchorIndicies {+ "bot-tag" = 0,+ "owner-crown",+ "boost-icon"+}++export type MemberListDecorator = {+ component: React.FC<any>;+ anchor: MemberListDecoratorAnchors;+ before: boolean;+};++export type MemberList = {+ addItem: (id: string, component: React.FC<any>) => void;+ addDecorator: (id: string, component: React.FC<any>, anchor?: MemberListDecoratorAnchors, before?: boolean) => void;+ //TODO: fix props type+ /**+ * @private+ */+ _patchItems: Patcher<any>;+ /**+ * @private+ */+ _patchDecorators: Patcher<any>;+};+//#endregion++//#region Messages+export type MessageUsernameAnchors = "communication-disabled" | "username" | undefined;+export type MessageUsernameBadgeAnchors =+ | "nitro-author"+ | "role-icon"+ | "new-member"+ | "leaderboard-champion"+ | "connections"+ | undefined;+export type MessageBadgeAnchors = "silent" | "potion" | undefined;++export type MessageUsername = {+ component: React.FC<any>;+ anchor: MessageUsernameAnchors;+ before: boolean;+};+export type MessageUsernameBadge = {+ component: React.FC<any>;+ anchor: MessageUsernameBadgeAnchors;+ before: boolean;+};+export type MessageBadge = {+ component: React.FC<any>;+ anchor: MessageBadgeAnchors;+ before: boolean;+};++export enum MessageUsernameIndicies {+ "communication-disabled" = 0,+ username+}+export enum MessageUsernameBadgeIndicies {+ "nitro-author" = 0,+ "role-icon",+ "new-member",+ "leaderboard-champion",+ connections+}+export enum MessageBadgeIndicies {+ silent = 0,+ potion+}++export type Messages = {+ addToUsername: (id: string, component: React.FC<any>, anchor?: MessageUsernameAnchors, before?: boolean) => void;+ addUsernameBadge: (+ id: string,+ component: React.FC<any>,+ anchor?: MessageUsernameBadgeAnchors,+ before?: boolean+ ) => void;+ addBadge: (id: string, component: React.FC<any>, anchor?: MessageBadgeAnchors, before?: boolean) => void;+ /**+ * @private+ */+ _patchUsername: Patcher<any>;+ /**+ * @private+ */+ _patchUsernameBadges: Patcher<any>;+ /**+ * @private+ */+ _patchBadges: Patcher<any>;+};+//#endregion
@@ -1,5 +1,6 @@import { AppPanels } from "../coreExtensions/appPanels";import { Commands } from "../coreExtensions/commands";+import { DMList, MemberList, Messages } from "../coreExtensions/componentEditor";import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu";import { Markdown } from "../coreExtensions/markdown";import { Moonbase } from "../coreExtensions/moonbase";@@ -12,6 +13,10 @@declare function WebpackRequire(id: "appPanels_appPanels"): AppPanels;declare function WebpackRequire(id: "commands_commands"): Commands;++declare function WebpackRequire(id: "componentEditor_dmList"): DMList;+declare function WebpackRequire(id: "componentEditor_memberList"): MemberList;+declare function WebpackRequire(id: "componentEditor_messages"): Messages;declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser;declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu;
MODIFIED
packages/types/src/import.d.ts
MODIFIED
packages/types/src/import.d.ts
@@ -12,6 +12,22 @@ }declare module "@moonlight-mod/wp/common_stores";+declare module "@moonlight-mod/wp/componentEditor_dmList" {+ import { CoreExtensions } from "@moonlight-mod/types";+ export const dmList: CoreExtensions.ComponentEditor.DMList;+ export default dmList;+}+declare module "@moonlight-mod/wp/componentEditor_memberList" {+ import { CoreExtensions } from "@moonlight-mod/types";+ export const memberList: CoreExtensions.ComponentEditor.MemberList;+ export default memberList;+}+declare module "@moonlight-mod/wp/componentEditor_messages" {+ import { CoreExtensions } from "@moonlight-mod/types";+ export const message: CoreExtensions.ComponentEditor.Messages;+ export default message;+}+declare module "@moonlight-mod/wp/contextMenu_evilMenu" {import { CoreExtensions } from "@moonlight-mod/types";const EvilParser: CoreExtensions.ContextMenu.EvilItemParser;