mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
f21fa4ecb7
Summary: In https://github.com/facebook/react-native/issues/25427, radex added initial support for running React Native projects on macOS via Catalyst. However, `RCTWebSocket` was disabled for that target because of some compilation issues. This meant that running projects via a connection to the packager wasn't possible: no live reload, and projects must be run in "Release" mode. It also meant making manual changes to Xcode projects deploying to macOS and scattering a number of conditional checks throughout the codebase. In this change, I've implemented support for `RCTWebSocket` on the macOS target and re-enabled the affected features. Live reload and the inspector now work for macOS targets. Manual modifications of Xcode build settings are no longer necessary for react-native projects running on macOS.  ### Limitations There's no binding which displays the developer menu (since there's no shake event on macOS). We'll probably want to add one, perhaps to the menu bar. I've chosen not to commit the modifications to RNTester which enable macOS support, since that would imply more "official" support for this target than I suspect you all would like to convey. I'm happy to add those chunks if it would be helpful. ## Changelog [iOS] [Added] - Added web socket support for macOS (Catalyst), enabling debug builds and live reload Pull Request resolved: https://github.com/facebook/react-native/pull/27469 Test Plan: * Open RNTester/RNTester.xcodeproj with Xcode 11.2.1, run it like a normal iOS app -- make sure it compiles and runs correctly (no regression) * Select "My Mac" as device target, and run. You may need to configure a valid development team to make signing work. * RNTester should run fine with no additional configuration. Modify a file in RNTester, note that live reload is now working. * Test the developer inspector. To display the developer menu, you'll need to manually show it; here's an example diff which does that: ``` diff --git a/RNTester/js/RNTesterApp.ios.js b/RNTester/js/RNTesterApp.ios.js index 8245a68d12..a447ad3b1b 100644 --- a/RNTester/js/RNTesterApp.ios.js +++ b/RNTester/js/RNTesterApp.ios.js @@ -19,6 +19,8 @@ const React = require('react'); const SnapshotViewIOS = require('./examples/Snapshot/SnapshotViewIOS.ios'); const URIActionMap = require('./utils/URIActionMap'); +import NativeDevMenu from '../../Libraries/NativeModules/specs/NativeDevMenu'; + const { AppRegistry, AsyncStorage, @@ -143,6 +145,7 @@ class RNTesterApp extends React.Component<Props, RNTesterNavigationState> { UNSAFE_componentWillMount() { BackHandler.addEventListener('hardwareBackPress', this._handleBack); + NativeDevMenu.show(); } componentDidMount() { ``` Reviewed By: sammy-SC Differential Revision: D18945861 Pulled By: hramos fbshipit-source-id: edcf02c5803742c89a845a3e5d72bc7dacae839f
574 lines
22 KiB
Plaintext
574 lines
22 KiB
Plaintext
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import <React/RCTDevMenu.h>
|
|
|
|
#import <FBReactNativeSpec/FBReactNativeSpec.h>
|
|
#import <React/RCTBridge+Private.h>
|
|
#import <React/RCTBundleURLProvider.h>
|
|
#import <React/RCTDefines.h>
|
|
#import <React/RCTDevSettings.h>
|
|
#import <React/RCTKeyCommands.h>
|
|
#import <React/RCTLog.h>
|
|
#import <React/RCTReloadCommand.h>
|
|
#import <React/RCTUtils.h>
|
|
|
|
#import "CoreModulesPlugins.h"
|
|
|
|
#if RCT_DEV_MENU
|
|
|
|
#if RCT_ENABLE_INSPECTOR
|
|
#import <React/RCTInspectorDevServerHelper.h>
|
|
#endif
|
|
|
|
NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification";
|
|
|
|
@implementation UIWindow (RCTDevMenu)
|
|
|
|
- (void)RCT_motionEnded:(__unused UIEventSubtype)motion withEvent:(UIEvent *)event
|
|
{
|
|
if (event.subtype == UIEventSubtypeMotionShake) {
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RCTShowDevMenuNotification object:nil];
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RCTDevMenuItem {
|
|
RCTDevMenuItemTitleBlock _titleBlock;
|
|
dispatch_block_t _handler;
|
|
}
|
|
|
|
- (instancetype)initWithTitleBlock:(RCTDevMenuItemTitleBlock)titleBlock handler:(dispatch_block_t)handler
|
|
{
|
|
if ((self = [super init])) {
|
|
_titleBlock = [titleBlock copy];
|
|
_handler = [handler copy];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
RCT_NOT_IMPLEMENTED(-(instancetype)init)
|
|
|
|
+ (instancetype)buttonItemWithTitleBlock:(NSString * (^)(void))titleBlock handler:(dispatch_block_t)handler
|
|
{
|
|
return [[self alloc] initWithTitleBlock:titleBlock handler:handler];
|
|
}
|
|
|
|
+ (instancetype)buttonItemWithTitle:(NSString *)title handler:(dispatch_block_t)handler
|
|
{
|
|
return [[self alloc]
|
|
initWithTitleBlock:^NSString * {
|
|
return title;
|
|
}
|
|
handler:handler];
|
|
}
|
|
|
|
- (void)callHandler
|
|
{
|
|
if (_handler) {
|
|
_handler();
|
|
}
|
|
}
|
|
|
|
- (NSString *)title
|
|
{
|
|
if (_titleBlock) {
|
|
return _titleBlock();
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
@end
|
|
|
|
typedef void (^RCTDevMenuAlertActionHandler)(UIAlertAction *action);
|
|
|
|
@interface RCTDevMenu () <RCTBridgeModule, RCTInvalidating, NativeDevMenuSpec>
|
|
|
|
@end
|
|
|
|
@implementation RCTDevMenu {
|
|
UIAlertController *_actionSheet;
|
|
NSMutableArray<RCTDevMenuItem *> *_extraMenuItems;
|
|
}
|
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
+ (void)initialize
|
|
{
|
|
// We're swizzling here because it's poor form to override methods in a category,
|
|
// however UIWindow doesn't actually implement motionEnded:withEvent:, so there's
|
|
// no need to call the original implementation.
|
|
RCTSwapInstanceMethods([UIWindow class], @selector(motionEnded:withEvent:), @selector(RCT_motionEnded:withEvent:));
|
|
}
|
|
|
|
+ (BOOL)requiresMainQueueSetup
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
if ((self = [super init])) {
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(showOnShake)
|
|
name:RCTShowDevMenuNotification
|
|
object:nil];
|
|
_extraMenuItems = [NSMutableArray new];
|
|
|
|
#if TARGET_OS_SIMULATOR
|
|
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
|
|
__weak __typeof(self) weakSelf = self;
|
|
|
|
// Toggle debug menu
|
|
[commands registerKeyCommandWithInput:@"d"
|
|
modifierFlags:UIKeyModifierCommand
|
|
action:^(__unused UIKeyCommand *command) {
|
|
[weakSelf toggle];
|
|
}];
|
|
|
|
// Toggle element inspector
|
|
[commands registerKeyCommandWithInput:@"i"
|
|
modifierFlags:UIKeyModifierCommand
|
|
action:^(__unused UIKeyCommand *command) {
|
|
[weakSelf.bridge.devSettings toggleElementInspector];
|
|
}];
|
|
|
|
// Reload in normal mode
|
|
[commands registerKeyCommandWithInput:@"n"
|
|
modifierFlags:UIKeyModifierCommand
|
|
action:^(__unused UIKeyCommand *command) {
|
|
[weakSelf.bridge.devSettings setIsDebuggingRemotely:NO];
|
|
}];
|
|
#endif
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (dispatch_queue_t)methodQueue
|
|
{
|
|
return dispatch_get_main_queue();
|
|
}
|
|
|
|
- (void)invalidate
|
|
{
|
|
_presentedItems = nil;
|
|
[_actionSheet dismissViewControllerAnimated:YES
|
|
completion:^(void){}];
|
|
}
|
|
|
|
- (void)showOnShake
|
|
{
|
|
if ([_bridge.devSettings isShakeToShowDevMenuEnabled]) {
|
|
[self show];
|
|
}
|
|
}
|
|
|
|
- (void)toggle
|
|
{
|
|
if (_actionSheet) {
|
|
[_actionSheet dismissViewControllerAnimated:YES
|
|
completion:^(void){
|
|
}];
|
|
_actionSheet = nil;
|
|
} else {
|
|
[self show];
|
|
}
|
|
}
|
|
|
|
- (BOOL)isActionSheetShown
|
|
{
|
|
return _actionSheet != nil;
|
|
}
|
|
|
|
- (void)addItem:(NSString *)title handler:(void (^)(void))handler
|
|
{
|
|
[self addItem:[RCTDevMenuItem buttonItemWithTitle:title handler:handler]];
|
|
}
|
|
|
|
- (void)addItem:(RCTDevMenuItem *)item
|
|
{
|
|
[_extraMenuItems addObject:item];
|
|
}
|
|
|
|
- (void)setDefaultJSBundle
|
|
{
|
|
[[RCTBundleURLProvider sharedSettings] resetToDefaults];
|
|
self->_bridge.bundleURL = [[RCTBundleURLProvider sharedSettings] jsBundleURLForFallbackResource:nil
|
|
fallbackExtension:nil];
|
|
RCTTriggerReloadCommandListeners(@"Dev menu - reset to default");
|
|
}
|
|
|
|
- (NSArray<RCTDevMenuItem *> *)_menuItemsToPresent
|
|
{
|
|
NSMutableArray<RCTDevMenuItem *> *items = [NSMutableArray new];
|
|
|
|
// Add built-in items
|
|
__weak RCTBridge *bridge = _bridge;
|
|
__weak RCTDevSettings *devSettings = _bridge.devSettings;
|
|
__weak RCTDevMenu *weakSelf = self;
|
|
|
|
[items addObject:[RCTDevMenuItem buttonItemWithTitle:@"Reload"
|
|
handler:^{
|
|
RCTTriggerReloadCommandListeners(@"Dev menu - reload");
|
|
}]];
|
|
|
|
if (!devSettings.isProfilingEnabled) {
|
|
if (!devSettings.isRemoteDebuggingAvailable) {
|
|
[items
|
|
addObject:[RCTDevMenuItem
|
|
buttonItemWithTitle:@"Debugger Unavailable"
|
|
handler:^{
|
|
NSString *message = RCTTurboModuleEnabled()
|
|
? @"Debugging is not currently supported when TurboModule is enabled."
|
|
: @"Include the RCTWebSocket library to enable JavaScript debugging.";
|
|
UIAlertController *alertController =
|
|
[UIAlertController alertControllerWithTitle:@"Debugger Unavailable"
|
|
message:message
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
__weak __typeof__(alertController) weakAlertController = alertController;
|
|
[alertController
|
|
addAction:[UIAlertAction actionWithTitle:@"OK"
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(__unused UIAlertAction *action) {
|
|
[weakAlertController
|
|
dismissViewControllerAnimated:YES
|
|
completion:nil];
|
|
}]];
|
|
[RCTPresentedViewController() presentViewController:alertController
|
|
animated:YES
|
|
completion:NULL];
|
|
}]];
|
|
} else {
|
|
[items addObject:[RCTDevMenuItem
|
|
buttonItemWithTitleBlock:^NSString * {
|
|
if (devSettings.isNuclideDebuggingAvailable) {
|
|
return devSettings.isDebuggingRemotely ? @"Stop Chrome Debugger" : @"Debug with Chrome";
|
|
} else {
|
|
return devSettings.isDebuggingRemotely ? @"Stop Debugging" : @"Debug";
|
|
}
|
|
}
|
|
handler:^{
|
|
devSettings.isDebuggingRemotely = !devSettings.isDebuggingRemotely;
|
|
}]];
|
|
}
|
|
|
|
if (devSettings.isNuclideDebuggingAvailable && !devSettings.isDebuggingRemotely) {
|
|
[items addObject:[RCTDevMenuItem buttonItemWithTitle:@"Debug with Nuclide"
|
|
handler:^{
|
|
#if RCT_ENABLE_INSPECTOR
|
|
[RCTInspectorDevServerHelper
|
|
attachDebugger:@"ReactNative"
|
|
withBundleURL:bridge.bundleURL
|
|
withView:RCTPresentedViewController()];
|
|
#endif
|
|
}]];
|
|
}
|
|
}
|
|
|
|
[items addObject:[RCTDevMenuItem
|
|
buttonItemWithTitleBlock:^NSString * {
|
|
return devSettings.isElementInspectorShown ? @"Hide Inspector" : @"Show Inspector";
|
|
}
|
|
handler:^{
|
|
[devSettings toggleElementInspector];
|
|
}]];
|
|
|
|
if (devSettings.isHotLoadingAvailable) {
|
|
[items addObject:[RCTDevMenuItem
|
|
buttonItemWithTitleBlock:^NSString * {
|
|
// Previously known as "Hot Reloading". We won't use this term anymore.
|
|
return devSettings.isHotLoadingEnabled ? @"Disable Fast Refresh" : @"Enable Fast Refresh";
|
|
}
|
|
handler:^{
|
|
devSettings.isHotLoadingEnabled = !devSettings.isHotLoadingEnabled;
|
|
}]];
|
|
}
|
|
|
|
if (devSettings.isLiveReloadAvailable) {
|
|
[items addObject:[RCTDevMenuItem
|
|
buttonItemWithTitleBlock:^NSString * {
|
|
return devSettings.isDebuggingRemotely
|
|
? @"Systrace Unavailable"
|
|
: devSettings.isProfilingEnabled ? @"Stop Systrace" : @"Start Systrace";
|
|
}
|
|
handler:^{
|
|
if (devSettings.isDebuggingRemotely) {
|
|
UIAlertController *alertController =
|
|
[UIAlertController alertControllerWithTitle:@"Systrace Unavailable"
|
|
message:@"Stop debugging to enable Systrace."
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
__weak __typeof__(alertController) weakAlertController = alertController;
|
|
[alertController
|
|
addAction:[UIAlertAction actionWithTitle:@"OK"
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(__unused UIAlertAction *action) {
|
|
[weakAlertController
|
|
dismissViewControllerAnimated:YES
|
|
completion:nil];
|
|
}]];
|
|
[RCTPresentedViewController() presentViewController:alertController
|
|
animated:YES
|
|
completion:NULL];
|
|
} else {
|
|
devSettings.isProfilingEnabled = !devSettings.isProfilingEnabled;
|
|
}
|
|
}]];
|
|
// "Live reload" which refreshes on every edit was removed in favor of "Fast Refresh".
|
|
// While native code for "Live reload" is still there, please don't add the option back.
|
|
// See D15958697 for more context.
|
|
}
|
|
|
|
[items
|
|
addObject:[RCTDevMenuItem
|
|
buttonItemWithTitleBlock:^NSString * {
|
|
return @"Configure Bundler";
|
|
}
|
|
handler:^{
|
|
UIAlertController *alertController = [UIAlertController
|
|
alertControllerWithTitle:@"Configure Bundler"
|
|
message:@"Provide a custom bundler address, port, and entrypoint."
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
|
textField.placeholder = @"0.0.0.0";
|
|
}];
|
|
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
|
textField.placeholder = @"8081";
|
|
}];
|
|
[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
|
|
textField.placeholder = @"index";
|
|
}];
|
|
[alertController
|
|
addAction:[UIAlertAction
|
|
actionWithTitle:@"Apply Changes"
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(__unused UIAlertAction *action) {
|
|
NSArray *textfields = alertController.textFields;
|
|
UITextField *ipTextField = textfields[0];
|
|
UITextField *portTextField = textfields[1];
|
|
UITextField *bundleRootTextField = textfields[2];
|
|
NSString *bundleRoot = bundleRootTextField.text;
|
|
if (ipTextField.text.length == 0 && portTextField.text.length == 0) {
|
|
[weakSelf setDefaultJSBundle];
|
|
return;
|
|
}
|
|
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
|
|
formatter.numberStyle = NSNumberFormatterDecimalStyle;
|
|
NSNumber *portNumber =
|
|
[formatter numberFromString:portTextField.text];
|
|
if (portNumber == nil) {
|
|
portNumber = [NSNumber numberWithInt:RCT_METRO_PORT];
|
|
}
|
|
[RCTBundleURLProvider sharedSettings].jsLocation = [NSString
|
|
stringWithFormat:@"%@:%d", ipTextField.text, portNumber.intValue];
|
|
__strong RCTBridge *strongBridge = bridge;
|
|
if (strongBridge) {
|
|
NSURL *bundleURL = bundleRoot.length
|
|
? [[RCTBundleURLProvider sharedSettings]
|
|
jsBundleURLForBundleRoot:bundleRoot
|
|
fallbackResource:nil]
|
|
: [strongBridge.delegate sourceURLForBridge:strongBridge];
|
|
strongBridge.bundleURL = bundleURL;
|
|
RCTTriggerReloadCommandListeners(@"Dev menu - apply changes");
|
|
}
|
|
}]];
|
|
[alertController addAction:[UIAlertAction actionWithTitle:@"Reset to Default"
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(__unused UIAlertAction *action) {
|
|
[weakSelf setDefaultJSBundle];
|
|
}]];
|
|
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel"
|
|
style:UIAlertActionStyleCancel
|
|
handler:^(__unused UIAlertAction *action) {
|
|
return;
|
|
}]];
|
|
[RCTPresentedViewController() presentViewController:alertController animated:YES completion:NULL];
|
|
}]];
|
|
|
|
[items addObjectsFromArray:_extraMenuItems];
|
|
return items;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(show)
|
|
{
|
|
if (_actionSheet || !_bridge || RCTRunningInAppExtension()) {
|
|
return;
|
|
}
|
|
|
|
NSString *bridgeDescription = _bridge.bridgeDescription;
|
|
NSString *description =
|
|
bridgeDescription.length > 0 ? [NSString stringWithFormat:@"Running %@", bridgeDescription] : nil;
|
|
|
|
// On larger devices we don't have an anchor point for the action sheet
|
|
UIAlertControllerStyle style = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone
|
|
? UIAlertControllerStyleActionSheet
|
|
: UIAlertControllerStyleAlert;
|
|
_actionSheet = [UIAlertController alertControllerWithTitle:@"React Native Debug Menu"
|
|
message:description
|
|
preferredStyle:style];
|
|
|
|
NSArray<RCTDevMenuItem *> *items = [self _menuItemsToPresent];
|
|
for (RCTDevMenuItem *item in items) {
|
|
[_actionSheet addAction:[UIAlertAction actionWithTitle:item.title
|
|
style:UIAlertActionStyleDefault
|
|
handler:[self alertActionHandlerForDevItem:item]]];
|
|
}
|
|
|
|
[_actionSheet addAction:[UIAlertAction actionWithTitle:@"Cancel"
|
|
style:UIAlertActionStyleCancel
|
|
handler:[self alertActionHandlerForDevItem:nil]]];
|
|
|
|
_presentedItems = items;
|
|
[RCTPresentedViewController() presentViewController:_actionSheet animated:YES completion:nil];
|
|
|
|
[_bridge enqueueJSCall:@"RCTNativeAppEventEmitter"
|
|
method:@"emit"
|
|
args:@[@"RCTDevMenuShown"]
|
|
completion:NULL];
|
|
}
|
|
|
|
- (RCTDevMenuAlertActionHandler)alertActionHandlerForDevItem:(RCTDevMenuItem *__nullable)item
|
|
{
|
|
return ^(__unused UIAlertAction *action) {
|
|
if (item) {
|
|
[item callHandler];
|
|
}
|
|
|
|
self->_actionSheet = nil;
|
|
};
|
|
}
|
|
|
|
#pragma mark - deprecated methods and properties
|
|
|
|
#define WARN_DEPRECATED_DEV_MENU_EXPORT() \
|
|
RCTLogWarn(@"Using deprecated method %s, use RCTDevSettings instead", __func__)
|
|
|
|
- (void)setShakeToShow:(BOOL)shakeToShow
|
|
{
|
|
_bridge.devSettings.isShakeToShowDevMenuEnabled = shakeToShow;
|
|
}
|
|
|
|
- (BOOL)shakeToShow
|
|
{
|
|
return _bridge.devSettings.isShakeToShowDevMenuEnabled;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(reload)
|
|
{
|
|
WARN_DEPRECATED_DEV_MENU_EXPORT();
|
|
RCTTriggerReloadCommandListeners(@"Unknown from JS");
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(debugRemotely : (BOOL)enableDebug)
|
|
{
|
|
WARN_DEPRECATED_DEV_MENU_EXPORT();
|
|
_bridge.devSettings.isDebuggingRemotely = enableDebug;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setProfilingEnabled : (BOOL)enabled)
|
|
{
|
|
WARN_DEPRECATED_DEV_MENU_EXPORT();
|
|
_bridge.devSettings.isProfilingEnabled = enabled;
|
|
}
|
|
|
|
- (BOOL)profilingEnabled
|
|
{
|
|
return _bridge.devSettings.isProfilingEnabled;
|
|
}
|
|
|
|
RCT_EXPORT_METHOD(setHotLoadingEnabled : (BOOL)enabled)
|
|
{
|
|
WARN_DEPRECATED_DEV_MENU_EXPORT();
|
|
_bridge.devSettings.isHotLoadingEnabled = enabled;
|
|
}
|
|
|
|
- (BOOL)hotLoadingEnabled
|
|
{
|
|
return _bridge.devSettings.isHotLoadingEnabled;
|
|
}
|
|
|
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
|
|
{
|
|
return std::make_shared<facebook::react::NativeDevMenuSpecJSI>(self, jsInvoker);
|
|
}
|
|
|
|
@end
|
|
|
|
#else // Unavailable when not in dev mode
|
|
|
|
@interface RCTDevMenu() <NativeDevMenuSpec>
|
|
@end
|
|
|
|
@implementation RCTDevMenu
|
|
|
|
- (void)show
|
|
{
|
|
}
|
|
- (void)reload
|
|
{
|
|
}
|
|
- (void)addItem:(NSString *)title handler:(dispatch_block_t)handler
|
|
{
|
|
}
|
|
- (void)addItem:(RCTDevMenu *)item
|
|
{
|
|
}
|
|
|
|
- (void)debugRemotely : (BOOL)enableDebug
|
|
{
|
|
}
|
|
|
|
- (BOOL)isActionSheetShown
|
|
{
|
|
return NO;
|
|
}
|
|
+ (NSString *)moduleName
|
|
{
|
|
return @"DevMenu";
|
|
}
|
|
|
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
|
|
{
|
|
return std::make_shared<facebook::react::NativeDevMenuSpecJSI>(self, jsInvoker);
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RCTDevMenuItem
|
|
|
|
+ (instancetype)buttonItemWithTitle:(NSString *)title handler:(void (^)(void))handler
|
|
{
|
|
return nil;
|
|
}
|
|
+ (instancetype)buttonItemWithTitleBlock:(NSString * (^)(void))titleBlock handler:(void (^)(void))handler
|
|
{
|
|
return nil;
|
|
}
|
|
|
|
@end
|
|
|
|
#endif
|
|
|
|
@implementation RCTBridge (RCTDevMenu)
|
|
|
|
- (RCTDevMenu *)devMenu
|
|
{
|
|
#if RCT_DEV_MENU
|
|
return [self moduleForClass:[RCTDevMenu class]];
|
|
#else
|
|
return nil;
|
|
#endif
|
|
}
|
|
|
|
@end
|
|
|
|
Class RCTDevMenuCls(void) {
|
|
return RCTDevMenu.class;
|
|
}
|