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+ }
0 commit comments