@@ -5,6 +5,7 @@ import * as Layer from "effect/Layer";
55import * as Option from "effect/Option" ;
66
77import * as Electron from "electron" ;
8+ import { HostProcessPlatform } from "@t3tools/shared/hostProcess" ;
89
910export interface ElectronMenuPosition {
1011 readonly x : number ;
@@ -79,109 +80,113 @@ const normalizePosition = (
7980 ( { x, y } ) => Number . isFinite ( x ) && Number . isFinite ( y ) && x >= 0 && y >= 0 ,
8081 ) . pipe ( Option . map ( ( { x, y } ) => ( { x : Math . floor ( x ) , y : Math . floor ( y ) } ) ) ) ;
8182
82- export const layer = Layer . sync ( ElectronMenu , ( ) => {
83- let destructiveMenuIconCache : Option . Option < Electron . NativeImage > | undefined ;
83+ export const layer = Layer . effect (
84+ ElectronMenu ,
85+ Effect . gen ( function * ( ) {
86+ const platform = yield * HostProcessPlatform ;
87+ let destructiveMenuIconCache : Option . Option < Electron . NativeImage > | undefined ;
8488
85- const getDestructiveMenuIcon = ( ) : Option . Option < Electron . NativeImage > => {
86- if ( process . platform !== "darwin" ) {
87- return Option . none ( ) ;
88- }
89- if ( destructiveMenuIconCache !== undefined ) {
90- return destructiveMenuIconCache ;
91- }
92-
93- try {
94- const icon = Electron . nativeImage . createFromNamedImage ( "trash" ) . resize ( {
95- width : 12 ,
96- height : 12 ,
97- } ) ;
98- destructiveMenuIconCache = icon . isEmpty ( ) ? Option . none ( ) : Option . some ( icon ) ;
99- } catch {
100- destructiveMenuIconCache = Option . none ( ) ;
101- }
102-
103- return destructiveMenuIconCache ;
104- } ;
105-
106- const buildTemplate = (
107- entries : readonly ContextMenuItem [ ] ,
108- complete : ( selectedItemId : Option . Option < string > ) => void ,
109- ) : Electron . MenuItemConstructorOptions [ ] => {
110- const template : Electron . MenuItemConstructorOptions [ ] = [ ] ;
111- let hasInsertedDestructiveSeparator = false ;
112-
113- for ( const item of entries ) {
114- if ( item . destructive && ! hasInsertedDestructiveSeparator && template . length > 0 ) {
115- template . push ( { type : "separator" } ) ;
116- hasInsertedDestructiveSeparator = true ;
89+ const getDestructiveMenuIcon = ( ) : Option . Option < Electron . NativeImage > => {
90+ if ( platform !== "darwin" ) {
91+ return Option . none ( ) ;
11792 }
118-
119- const itemOption : Electron . MenuItemConstructorOptions = {
120- label : item . label ,
121- enabled : ! item . disabled ,
122- } ;
123- if ( item . children && item . children . length > 0 ) {
124- itemOption . submenu = buildTemplate ( item . children , complete ) ;
125- } else {
126- itemOption . click = ( ) => complete ( Option . some ( item . id ) ) ;
93+ if ( destructiveMenuIconCache !== undefined ) {
94+ return destructiveMenuIconCache ;
12795 }
128- if ( item . destructive && ( ! item . children || item . children . length === 0 ) ) {
129- const destructiveIcon = getDestructiveMenuIcon ( ) ;
130- if ( Option . isSome ( destructiveIcon ) ) {
131- itemOption . icon = destructiveIcon . value ;
132- }
96+
97+ try {
98+ const icon = Electron . nativeImage . createFromNamedImage ( "trash" ) . resize ( {
99+ width : 12 ,
100+ height : 12 ,
101+ } ) ;
102+ destructiveMenuIconCache = icon . isEmpty ( ) ? Option . none ( ) : Option . some ( icon ) ;
103+ } catch {
104+ destructiveMenuIconCache = Option . none ( ) ;
133105 }
134106
135- template . push ( itemOption ) ;
136- }
107+ return destructiveMenuIconCache ;
108+ } ;
137109
138- return template ;
139- } ;
140-
141- return ElectronMenu . of ( {
142- setApplicationMenu : ( template ) =>
143- Effect . sync ( ( ) => {
144- Electron . Menu . setApplicationMenu ( Electron . Menu . buildFromTemplate ( [ ...template ] ) ) ;
145- } ) ,
146- popupTemplate : ( input ) =>
147- Effect . sync ( ( ) => {
148- if ( input . template . length === 0 ) {
149- return ;
110+ const buildTemplate = (
111+ entries : readonly ContextMenuItem [ ] ,
112+ complete : ( selectedItemId : Option . Option < string > ) => void ,
113+ ) : Electron . MenuItemConstructorOptions [ ] => {
114+ const template : Electron . MenuItemConstructorOptions [ ] = [ ] ;
115+ let hasInsertedDestructiveSeparator = false ;
116+
117+ for ( const item of entries ) {
118+ if ( item . destructive && ! hasInsertedDestructiveSeparator && template . length > 0 ) {
119+ template . push ( { type : "separator" } ) ;
120+ hasInsertedDestructiveSeparator = true ;
150121 }
151- Electron . Menu . buildFromTemplate ( [ ...input . template ] ) . popup ( { window : input . window } ) ;
152- } ) ,
153- showContextMenu : ( input ) =>
154- Effect . callback < Option . Option < string > > ( ( resume ) => {
155- const normalizedItems = normalizeContextMenuItems ( input . items ) ;
156- if ( normalizedItems . length === 0 ) {
157- resume ( Effect . succeed ( Option . none ( ) ) ) ;
158- return ;
122+
123+ const itemOption : Electron . MenuItemConstructorOptions = {
124+ label : item . label ,
125+ enabled : ! item . disabled ,
126+ } ;
127+ if ( item . children && item . children . length > 0 ) {
128+ itemOption . submenu = buildTemplate ( item . children , complete ) ;
129+ } else {
130+ itemOption . click = ( ) => complete ( Option . some ( item . id ) ) ;
159131 }
132+ if ( item . destructive && ( ! item . children || item . children . length === 0 ) ) {
133+ const destructiveIcon = getDestructiveMenuIcon ( ) ;
134+ if ( Option . isSome ( destructiveIcon ) ) {
135+ itemOption . icon = destructiveIcon . value ;
136+ }
137+ }
138+
139+ template . push ( itemOption ) ;
140+ }
160141
161- let completed = false ;
162- const complete = ( selectedItemId : Option . Option < string > ) => {
163- if ( completed ) {
142+ return template ;
143+ } ;
144+
145+ return ElectronMenu . of ( {
146+ setApplicationMenu : ( template ) =>
147+ Effect . sync ( ( ) => {
148+ Electron . Menu . setApplicationMenu ( Electron . Menu . buildFromTemplate ( [ ...template ] ) ) ;
149+ } ) ,
150+ popupTemplate : ( input ) =>
151+ Effect . sync ( ( ) => {
152+ if ( input . template . length === 0 ) {
153+ return ;
154+ }
155+ Electron . Menu . buildFromTemplate ( [ ...input . template ] ) . popup ( { window : input . window } ) ;
156+ } ) ,
157+ showContextMenu : ( input ) =>
158+ Effect . callback < Option . Option < string > > ( ( resume ) => {
159+ const normalizedItems = normalizeContextMenuItems ( input . items ) ;
160+ if ( normalizedItems . length === 0 ) {
161+ resume ( Effect . succeed ( Option . none ( ) ) ) ;
164162 return ;
165163 }
166- completed = true ;
167- resume ( Effect . succeed ( selectedItemId ) ) ;
168- } ;
169164
170- const menu = Electron . Menu . buildFromTemplate ( buildTemplate ( normalizedItems , complete ) ) ;
171- const popupPosition = normalizePosition ( input . position ) ;
172- const popupOptions = Option . match ( popupPosition , {
173- onNone : ( ) : Electron . PopupOptions => ( {
174- window : input . window ,
175- callback : ( ) => complete ( Option . none ( ) ) ,
176- } ) ,
177- onSome : ( position ) : Electron . PopupOptions => ( {
178- window : input . window ,
179- x : position . x ,
180- y : position . y ,
181- callback : ( ) => complete ( Option . none ( ) ) ,
182- } ) ,
183- } ) ;
184- menu . popup ( popupOptions ) ;
185- } ) ,
186- } ) ;
187- } ) ;
165+ let completed = false ;
166+ const complete = ( selectedItemId : Option . Option < string > ) => {
167+ if ( completed ) {
168+ return ;
169+ }
170+ completed = true ;
171+ resume ( Effect . succeed ( selectedItemId ) ) ;
172+ } ;
173+
174+ const menu = Electron . Menu . buildFromTemplate ( buildTemplate ( normalizedItems , complete ) ) ;
175+ const popupPosition = normalizePosition ( input . position ) ;
176+ const popupOptions = Option . match ( popupPosition , {
177+ onNone : ( ) : Electron . PopupOptions => ( {
178+ window : input . window ,
179+ callback : ( ) => complete ( Option . none ( ) ) ,
180+ } ) ,
181+ onSome : ( position ) : Electron . PopupOptions => ( {
182+ window : input . window ,
183+ x : position . x ,
184+ y : position . y ,
185+ callback : ( ) => complete ( Option . none ( ) ) ,
186+ } ) ,
187+ } ) ;
188+ menu . popup ( popupOptions ) ;
189+ } ) ,
190+ } ) ;
191+ } ) ,
192+ ) ;
0 commit comments