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 devSettings = bridge?.module(forName: "DevSettings") as? RCTDevSettings else { 20 return nil 21 } 22 23 let container = DevMenuItemsContainer() 24 25 let reload = DevMenuExtensions.reloadAction { 26 // Without this the `expo-splash-screen` will reject 27 // No native splash screen registered for given view controller. Call 'SplashScreen.show' for given view controller first. 28 DevMenuManager.shared.hideMenu(); 29 self.bridge?.requestReload() 30 } 31 reload.isAvailable = { !DevMenuExtensions.checkIfLogBoxIsOpened() } 32 33 let inspector = DevMenuExtensions.elementInspectorAction { 34 devSettings.toggleElementInspector() 35 } 36 inspector.isEnabled = { devSettings.isElementInspectorShown } 37 38 #if DEBUG 39 let remoteDebug = DevMenuExtensions.remoteDebugAction { 40 DispatchQueue.main.async { 41 devSettings.isDebuggingRemotely = !devSettings.isDebuggingRemotely 42 } 43 } 44 remoteDebug.isAvailable = { devSettings.isRemoteDebuggingAvailable } 45 remoteDebug.isEnabled = { devSettings.isDebuggingRemotely } 46 47 let fastRefresh = DevMenuExtensions.fastRefreshAction { 48 devSettings.isHotLoadingEnabled = !devSettings.isHotLoadingEnabled 49 } 50 fastRefresh.isAvailable = { devSettings.isHotLoadingAvailable } 51 fastRefresh.isEnabled = { devSettings.isHotLoadingEnabled } 52 53 let perfMonitor = DevMenuExtensions.performanceMonitorAction { 54 if let perfMonitorModule = self.bridge?.module(forName: "PerfMonitor") as? RCTPerfMonitor { 55 DispatchQueue.main.async { 56 devSettings.isPerfMonitorShown ? perfMonitorModule.hide() : perfMonitorModule.show() 57 devSettings.isPerfMonitorShown = !devSettings.isPerfMonitorShown 58 } 59 } 60 } 61 perfMonitor.isAvailable = { self.bridge?.module(forName: "PerfMonitor") != nil } 62 perfMonitor.isEnabled = { devSettings.isPerfMonitorShown } 63 64 container.addItem(remoteDebug) 65 container.addItem(fastRefresh) 66 container.addItem(perfMonitor) 67 68 container.addItem(reload) 69 container.addItem(inspector) 70 #endif 71 72 return container 73 } 74 75 @objc 76 open func devMenuScreens(_ settings: DevMenuExtensionSettingsProtocol) -> [DevMenuScreen]? { 77 return nil 78 } 79 80 @objc 81 open func devMenuDataSources(_ settings: DevMenuExtensionSettingsProtocol) -> [DevMenuDataSourceProtocol]? { 82 return nil 83 } 84 85 // MARK: static helpers 86 87 private static func reloadAction(action: @escaping () -> ()) -> DevMenuAction { 88 let reload = DevMenuAction(withId: "reload", action: action) 89 reload.label = { "Reload" } 90 reload.glyphName = { "reload" } 91 reload.importance = DevMenuScreenItem.ImportanceHighest 92 reload.registerKeyCommand(input: "r", modifiers: .command) 93 return reload 94 } 95 96 private static func elementInspectorAction(_ action: @escaping () -> ()) -> DevMenuAction { 97 let inspector = DevMenuAction(withId: "inspector", action: action) 98 inspector.label = { inspector.isEnabled() ? "Hide Element Inspector" : "Show Element Inspector" } 99 inspector.glyphName = { "border-style" } 100 inspector.importance = DevMenuScreenItem.ImportanceHigh 101 inspector.registerKeyCommand(input: "i", modifiers: .command) 102 return inspector 103 } 104 105 private static func remoteDebugAction(_ action: @escaping () -> ()) -> DevMenuAction { 106 let remoteDebug = DevMenuAction(withId: "remote-debug", action: action) 107 remoteDebug.label = { remoteDebug.isAvailable() ? remoteDebug.isEnabled() ? "Stop Remote Debugging" : "Debug Remote JS" : "Remote Debugger Unavailable" } 108 remoteDebug.glyphName = { "remote-desktop" } 109 remoteDebug.importance = DevMenuScreenItem.ImportanceLow 110 return remoteDebug 111 } 112 113 private static func fastRefreshAction(_ action: @escaping () -> ()) -> DevMenuAction { 114 let fastRefresh = DevMenuAction(withId: "fast-refresh", action: action) 115 fastRefresh.label = { fastRefresh.isAvailable() ? fastRefresh.isEnabled() ? "Disable Fast Refresh" : "Enable Fast Refresh" : "Fast Refresh Unavailable" } 116 fastRefresh.glyphName = { "run-fast" } 117 fastRefresh.importance = DevMenuScreenItem.ImportanceLow 118 return fastRefresh 119 } 120 121 private static func performanceMonitorAction(_ action: @escaping () -> ()) -> DevMenuAction { 122 let perfMonitor = DevMenuAction(withId: "performance-monitor", action: action) 123 perfMonitor.label = { perfMonitor.isAvailable() ? perfMonitor.isEnabled() ? "Hide Performance Monitor" : "Show Performance Monitor" : "Performance Monitor Unavailable" } 124 perfMonitor.glyphName = { "speedometer" } 125 perfMonitor.importance = DevMenuScreenItem.ImportanceHigh 126 perfMonitor.registerKeyCommand(input: "p", modifiers: .command) 127 return perfMonitor 128 } 129 130 private static func checkIfLogBoxIsOpened() -> Bool { 131 return UIApplication.shared.windows.contains { 132 let className = String(describing: type(of: $0)) 133 if className == "RCTLogBoxView" || className == "RCTRedBoxView" { 134 return true 135 } 136 137 return false 138 } 139 } 140 } 141