1 // Copyright 2015-present 650 Industries. All rights reserved.
2 
3 import EXDevMenuInterface
4 
5 @objc(DevMenuExtensions)
6 open class DevMenuExtensions: NSObject, DevMenuExtensionProtocol {
7   @objc
8   open var bridge: RCTBridge?
9 
10   // MARK: DevMenuExtensionProtocol
11 
12   @objc
devMenuItemsnull13   open func devMenuItems(_ settings: DevMenuExtensionSettingsProtocol) -> DevMenuItemsContainerProtocol? {
14     if !settings.wasRunOnDevelopmentBridge() {
15       return nil
16     }
17 
18     guard let bridge = bridge else {
19       return nil
20     }
21 
22     let devDelegate = DevMenuDevOptionsDelegate(forBridge: bridge)
23     guard let devSettings = devDelegate.devSettings else {
24       return nil
25     }
26 
27     let container = DevMenuItemsContainer()
28 
29     let reload = DevMenuExtensions.reloadAction(devDelegate.reload)
30     reload.isAvailable = { !DevMenuExtensions.checkIfLogBoxIsOpened() }
31 
32     let inspector = DevMenuExtensions.elementInspectorAction(devDelegate.toggleElementInsector)
33     inspector.isEnabled = { devSettings.isElementInspectorShown }
34 
35     #if DEBUG
36     let jsInspector = DevMenuExtensions.jsInspectorAction(devDelegate.openJSInspector)
37     jsInspector.isAvailable = { bridge.batched.isInspectable }
38     jsInspector.isEnabled = { true }
39 
40     let remoteDebug = DevMenuExtensions.remoteDebugAction(devDelegate.toggleRemoteDebugging)
41     remoteDebug.isAvailable = { devSettings.isRemoteDebuggingAvailable }
42     remoteDebug.isEnabled = { devSettings.isDebuggingRemotely }
43 
44     let fastRefresh = DevMenuExtensions.fastRefreshAction(devDelegate.toggleFastRefresh)
45     fastRefresh.isAvailable = { devSettings.isHotLoadingAvailable }
46     fastRefresh.isEnabled = { devSettings.isHotLoadingEnabled }
47 
48     let perfMonitor = DevMenuExtensions.performanceMonitorAction(devDelegate.togglePerformanceMonitor)
49     perfMonitor.isAvailable = { devDelegate.perfMonitor != nil }
50     perfMonitor.isEnabled = { devSettings.isPerfMonitorShown }
51 
52     container.addItem(reload)
53     container.addItem(perfMonitor)
54     container.addItem(inspector)
55     container.addItem(jsInspector)
56     container.addItem(remoteDebug)
57     container.addItem(fastRefresh)
58 
59     #endif
60 
61     return container
62   }
63 
64   @objc
devMenuScreensnull65   open func devMenuScreens(_ settings: DevMenuExtensionSettingsProtocol) -> [DevMenuScreen]? {
66     return nil
67   }
68 
69   @objc
devMenuDataSourcesnull70   open func devMenuDataSources(_ settings: DevMenuExtensionSettingsProtocol) -> [DevMenuDataSourceProtocol]? {
71     return nil
72   }
73 
74   // MARK: static helpers
75 
76   private static func reloadAction(_ action: @escaping () -> Void) -> DevMenuAction {
77     let reload = DevMenuAction(withId: "reload", action: action)
78     reload.label = { "Reload" }
79     reload.glyphName = { "reload" }
80     reload.importance = DevMenuScreenItem.ImportanceHighest
81     reload.registerKeyCommand(input: "r", modifiers: []) // "r" without modifiers
82     return reload
83   }
84 
85   private static func elementInspectorAction(_ action: @escaping () -> Void) -> DevMenuAction {
86     let inspector = DevMenuAction(withId: "inspector", action: action)
87     inspector.label = { inspector.isEnabled() ? "Hide Element Inspector" : "Show Element Inspector" }
88     inspector.glyphName = { "border-style" }
89     inspector.importance = DevMenuScreenItem.ImportanceHigh
90     inspector.registerKeyCommand(input: "i", modifiers: .command)
91     return inspector
92   }
93 
94   private static func jsInspectorAction(_ action: @escaping () -> Void) -> DevMenuAction {
95     let jsInspectror = DevMenuAction(withId: "js-inspector", action: action)
96     jsInspectror.label = { "Open JS debugger" }
97     jsInspectror.glyphName = { "language-javascript" }
98     jsInspectror.importance = DevMenuScreenItem.ImportanceLow
99     return jsInspectror
100   }
101 
102   private static func remoteDebugAction(_ action: @escaping () -> Void) -> DevMenuAction {
103     let remoteDebug = DevMenuAction(withId: "remote-debug", action: action)
104     remoteDebug.label = { remoteDebug.isAvailable() ? remoteDebug.isEnabled() ? "Stop Remote Debugging" : "Debug Remote JS" : "Remote Debugger Unavailable" }
105     remoteDebug.glyphName = { "remote-desktop" }
106     remoteDebug.importance = DevMenuScreenItem.ImportanceLow
107     return remoteDebug
108   }
109 
110   private static func fastRefreshAction(_ action: @escaping () -> Void) -> DevMenuAction {
111     let fastRefresh = DevMenuAction(withId: "fast-refresh", action: action)
112     fastRefresh.label = { fastRefresh.isAvailable() ? fastRefresh.isEnabled() ? "Disable Fast Refresh" : "Enable Fast Refresh" : "Fast Refresh Unavailable" }
113     fastRefresh.glyphName = { "run-fast" }
114     fastRefresh.importance = DevMenuScreenItem.ImportanceLow
115     return fastRefresh
116   }
117 
118   private static func performanceMonitorAction(_ action: @escaping () -> Void) -> DevMenuAction {
119     let perfMonitor = DevMenuAction(withId: "performance-monitor", action: action)
120     perfMonitor.label = { perfMonitor.isAvailable() ? perfMonitor.isEnabled() ? "Hide Performance Monitor" : "Show Performance Monitor" : "Performance Monitor Unavailable" }
121     perfMonitor.glyphName = { "speedometer" }
122     perfMonitor.importance = DevMenuScreenItem.ImportanceHigh
123     perfMonitor.registerKeyCommand(input: "p", modifiers: .command)
124     return perfMonitor
125   }
126 
checkIfLogBoxIsOpenednull127   private static func checkIfLogBoxIsOpened() -> Bool {
128     return UIApplication.shared.windows.contains {
129       let className = String(describing: type(of: $0))
130       if className == "RCTLogBoxView" || className == "RCTRedBoxView" {
131         return true
132       }
133 
134       return false
135     }
136   }
137 }
138