1+ import * as Electron from 'electron' ;
2+ import { UnreachableCheck } from './util' ;
3+
4+ export interface ContextMenuDefinition {
5+ position : { x : number ; y : number } ;
6+ items : ContextMenuItem [ ] ;
7+ }
8+
9+ type ContextMenuItem =
10+ | ContextMenuOption
11+ | ContextMenuSubmenu
12+ | { type : 'separator' } ;
13+
14+ interface ContextMenuOption {
15+ type : 'option' ;
16+ id : string ;
17+ label : string ;
18+ enabled ?: boolean ;
19+ }
20+
21+ interface ContextMenuSubmenu {
22+ type : 'submenu' ;
23+ label : string ;
24+ enabled ?: boolean ;
25+ items : ContextMenuItem [ ] ;
26+ }
27+
28+ type ContextMenuCallback = ( item : Electron . MenuItem ) => void ;
29+
30+
31+ // This resolves either with an id of a selected option, or undefined if the menu is closes any other way.
32+ export function openContextMenu ( { position, items } : ContextMenuDefinition ) {
33+ return new Promise ( ( resolve ) => {
34+ const callback = ( menuItem : Electron . MenuItem ) => resolve ( menuItem . id ) ;
35+
36+ const menu = buildContextMenu ( items , callback ) ;
37+ menu . addListener ( 'menu-will-close' , ( ) => {
38+ // Resolve has to be deferred briefly, because the click callback fires *after* menu-will-close.
39+ setTimeout ( resolve , 0 ) ;
40+ } ) ;
41+
42+ menu . popup ( { x : position . x , y : position . y } ) ;
43+ } ) ;
44+ }
45+
46+ function buildContextMenu ( items : ContextMenuItem [ ] , callback : ContextMenuCallback ) {
47+ const menu = new Electron . Menu ( ) ;
48+
49+ items
50+ . map ( ( item ) => buildContextMenuItem ( item , callback ) )
51+ . forEach ( menuItem => menu . append ( menuItem ) ) ;
52+
53+ return menu ;
54+ }
55+
56+ function buildContextMenuItem ( item : ContextMenuItem , callback : ContextMenuCallback ) : Electron . MenuItem {
57+ if ( item . type === 'option' ) return buildContextMenuOption ( item , callback ) ;
58+ else if ( item . type === 'submenu' ) return buildContextSubmenu ( item , callback ) ;
59+ else if ( item . type === 'separator' ) return new Electron . MenuItem ( item ) ;
60+ else throw new UnreachableCheck ( item , ( i ) => i . type ) ;
61+ }
62+
63+ function buildContextMenuOption ( option : ContextMenuOption , callback : ContextMenuCallback ) {
64+ return new Electron . MenuItem ( {
65+ type : 'normal' ,
66+ id : option . id ,
67+ label : option . label ,
68+ enabled : option . enabled ,
69+ click : callback
70+ } ) ;
71+ }
72+
73+ function buildContextSubmenu ( option : ContextMenuSubmenu , callback : ContextMenuCallback ) {
74+ const submenu = buildContextMenu ( option . items , callback ) ;
75+ return new Electron . MenuItem ( {
76+ label : option . label ,
77+ enabled : option . enabled ,
78+ submenu
79+ } ) ;
80+ }
0 commit comments