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
13   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 remoteDebug = DevMenuExtensions.remoteDebugAction(devDelegate.toggleRemoteDebugging)
37     remoteDebug.isAvailable = { devSettings.isRemoteDebuggingAvailable }
38     remoteDebug.isEnabled = { devSettings.isDebuggingRemotely }
39 
40     let fastRefresh = DevMenuExtensions.fastRefreshAction(devDelegate.toggleFastRefresh)
41     fastRefresh.isAvailable = { devSettings.isHotLoadingAvailable }
42     fastRefresh.isEnabled = { devSettings.isHotLoadingEnabled }
43 
44     let perfMonitor = DevMenuExtensions.performanceMonitorAction(devDelegate.togglePerformanceMonitor)
45     perfMonitor.isAvailable = { devDelegate.perfMonitor != nil }
46     perfMonitor.isEnabled = { devSettings.isPerfMonitorShown }
47 
48     container.addItem(reload)
49     container.addItem(perfMonitor)
50     container.addItem(inspector)
51     container.addItem(remoteDebug)
52     container.addItem(fastRefresh)
53 
54     #endif
55 
56     return container
57   }
58 
59   @objc
60   open func devMenuScreens(_ settings: DevMenuExtensionSettingsProtocol) -> [DevMenuScreen]? {
61     return nil
62   }
63 
64   @objc
65   open func devMenuDataSources(_ settings: DevMenuExtensionSettingsProtocol) -> [DevMenuDataSourceProtocol]? {
66     return nil
67   }
68 
69   // MARK: static helpers
70 
71   private static func reloadAction(_ action: @escaping () -> Void) -> DevMenuAction {
72     let reload = DevMenuAction(withId: "reload", action: action)
73     reload.label = { "Reload" }
74     reload.glyphName = { "reload" }
75     reload.importance = DevMenuScreenItem.ImportanceHighest
76     reload.registerKeyCommand(input: "r", modifiers: []) // "r" without modifiers
77     return reload
78   }
79 
80   private static func elementInspectorAction(_ action: @escaping () -> Void) -> DevMenuAction {
81     let inspector = DevMenuAction(withId: "inspector", action: action)
82     inspector.label = { inspector.isEnabled() ? "Hide Element Inspector" : "Show Element Inspector" }
83     inspector.glyphName = { "border-style" }
84     inspector.importance = DevMenuScreenItem.ImportanceHigh
85     inspector.registerKeyCommand(input: "i", modifiers: .command)
86     return inspector
87   }
88 
89   private static func remoteDebugAction(_ action: @escaping () -> Void) -> DevMenuAction {
90     let remoteDebug = DevMenuAction(withId: "remote-debug", action: action)
91     remoteDebug.label = { remoteDebug.isAvailable() ? remoteDebug.isEnabled() ? "Stop Remote Debugging" : "Debug Remote JS" : "Remote Debugger Unavailable" }
92     remoteDebug.glyphName = { "remote-desktop" }
93     remoteDebug.importance = DevMenuScreenItem.ImportanceLow
94     return remoteDebug
95   }
96 
97   private static func fastRefreshAction(_ action: @escaping () -> Void) -> DevMenuAction {
98     let fastRefresh = DevMenuAction(withId: "fast-refresh", action: action)
99     fastRefresh.label = { fastRefresh.isAvailable() ? fastRefresh.isEnabled() ? "Disable Fast Refresh" : "Enable Fast Refresh" : "Fast Refresh Unavailable" }
100     fastRefresh.glyphName = { "run-fast" }
101     fastRefresh.importance = DevMenuScreenItem.ImportanceLow
102     return fastRefresh
103   }
104 
105   private static func performanceMonitorAction(_ action: @escaping () -> Void) -> DevMenuAction {
106     let perfMonitor = DevMenuAction(withId: "performance-monitor", action: action)
107     perfMonitor.label = { perfMonitor.isAvailable() ? perfMonitor.isEnabled() ? "Hide Performance Monitor" : "Show Performance Monitor" : "Performance Monitor Unavailable" }
108     perfMonitor.glyphName = { "speedometer" }
109     perfMonitor.importance = DevMenuScreenItem.ImportanceHigh
110     perfMonitor.registerKeyCommand(input: "p", modifiers: .command)
111     return perfMonitor
112   }
113 
114   private static func checkIfLogBoxIsOpened() -> Bool {
115     return UIApplication.shared.windows.contains {
116       let className = String(describing: type(of: $0))
117       if className == "RCTLogBoxView" || className == "RCTRedBoxView" {
118         return true
119       }
120 
121       return false
122     }
123   }
124 }
125