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