Upgraded dev menu

This commit is contained in:
Nick Lockwood 2015-05-01 06:21:03 -07:00
parent 67196b36bb
commit ba501a1bf5
8 changed files with 223 additions and 103 deletions

View File

@ -78,7 +78,7 @@ var TabBarExample = React.createClass({
this.setState({
selectedTab: 'greenTab',
presses: this.state.presses + 1
});
});
}}>
{this._renderContent('#21551C', 'Green Tab')}
</TabBarIOS.Item>

View File

@ -582,7 +582,7 @@ var TextInput = React.createClass({
var counter = event.nativeEvent.eventCounter;
if (counter > this.state.mostRecentEventCounter) {
this.setState({mostRecentEventCounter: counter});
}
}
},
});

View File

@ -964,18 +964,13 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
__weak RCTBridge *weakSelf = self;
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
// Workaround around the first cmd+R not working: http://openradar.appspot.com/19613391
// You can register just the cmd key and do nothing. This will trigger the bug and cmd+R
// will work like a charm!
[commands registerKeyCommandWithInput:@""
modifierFlags:UIKeyModifierCommand
action:NULL];
// reload in current mode
[commands registerKeyCommandWithInput:@"r"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
[weakSelf reload];
}];
#endif
}

View File

@ -36,15 +36,16 @@
@property (nonatomic, assign) BOOL liveReloadEnabled;
/**
* The time between checks for code changes. Defaults to 1 second.
*/
@property (nonatomic, assign) NSTimeInterval liveReloadPeriod;
/**
* Manually show the dev menu.
* Manually show the dev menu (can be called from JS).
*/
- (void)show;
/**
* Manually reload the application. Equivalent to calling [bridge reload]
* directly, but can be called from JS.
*/
- (void)reload;
@end
/**

View File

@ -28,6 +28,7 @@
@end
static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification";
static NSString *const RCTDevMenuSettingsKey = @"RCTDevMenu";
@implementation UIWindow (RCTDevMenu)
@ -40,14 +41,20 @@ static NSString *const RCTShowDevMenuNotification = @"RCTShowDevMenuNotification
@end
@interface RCTDevMenu () <RCTBridgeModule, RCTInvalidating, UIActionSheetDelegate>
@interface RCTDevMenu () <RCTBridgeModule, UIActionSheetDelegate>
@property (nonatomic, strong) Class executorClass;
@end
@implementation RCTDevMenu
{
NSTimer *_updateTimer;
UIActionSheet *_actionSheet;
NSUserDefaults *_defaults;
NSMutableDictionary *_settings;
NSURLSessionDataTask *_updateTask;
NSURL *_liveReloadURL;
BOOL _jsLoaded;
}
@synthesize bridge = _bridge;
@ -66,31 +73,43 @@ RCT_EXPORT_MODULE()
{
if ((self = [super init])) {
_shakeToShow = YES;
_liveReloadPeriod = 1.0; // 1 second
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(showOnShake)
name:RCTShowDevMenuNotification
object:nil];
_defaults = [NSUserDefaults standardUserDefaults];
[self updateSettings];
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(showOnShake)
name:RCTShowDevMenuNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(updateSettings)
name:NSUserDefaultsDidChangeNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(jsLoaded)
name:RCTJavaScriptDidLoadNotification
object:nil];
#if TARGET_IPHONE_SIMULATOR
__weak RCTDevMenu *weakSelf = self;
RCTKeyCommands *commands = [RCTKeyCommands sharedInstance];
// Workaround around the first cmd+D not working: http://openradar.appspot.com/19613391
// You can register just the cmd key and do nothing. This will trigger the bug and cmd+R
// will work like a charm!
[commands registerKeyCommandWithInput:@""
modifierFlags:UIKeyModifierCommand
action:NULL];
// reload in debug mode
// toggle debug menu
[commands registerKeyCommandWithInput:@"d"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
__strong RCTDevMenu *strongSelf = weakSelf;
[strongSelf show];
[weakSelf toggle];
}];
// reload in normal mode
[commands registerKeyCommandWithInput:@"n"
modifierFlags:UIKeyModifierCommand
action:^(UIKeyCommand *command) {
weakSelf.executorClass = Nil;
}];
#endif
@ -98,11 +117,58 @@ RCT_EXPORT_MODULE()
return self;
}
- (void)updateSettings
{
_settings = [NSMutableDictionary dictionaryWithDictionary:[_defaults objectForKey:RCTDevMenuSettingsKey]];
self.shakeToShow = [_settings[@"shakeToShow"] ?: @YES boolValue];
self.profilingEnabled = [_settings[@"profilingEnabled"] ?: @NO boolValue];
self.liveReloadEnabled = [_settings[@"liveReloadEnabled"] ?: @NO boolValue];
self.executorClass = NSClassFromString(_settings[@"executorClass"]);
}
- (void)jsLoaded
{
_jsLoaded = YES;
// Check if live reloading is available
_liveReloadURL = nil;
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
if (!sourceCodeModule.scriptURL) {
if (!sourceCodeModule) {
RCTLogWarn(@"RCTSourceCode module not found");
} else {
RCTLogWarn(@"RCTSourceCode module scriptURL has not been set");
}
} else if (![sourceCodeModule.scriptURL isFileURL]) {
// Live reloading is disabled when running from bundled JS file
_liveReloadURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL];
}
// Hit these setters again after bridge has finished loading
self.profilingEnabled = _profilingEnabled;
self.liveReloadEnabled = _liveReloadEnabled;
self.executorClass = _executorClass;
}
- (void)dealloc
{
[_updateTask cancel];
[_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)updateSetting:(NSString *)name value:(id)value
{
if (value) {
_settings[name] = value;
} else {
[_settings removeObjectForKey:name];
}
[_defaults setObject:_settings forKey:RCTDevMenuSettingsKey];
[_defaults synchronize];
}
- (void)showOnShake
{
if (_shakeToShow) {
@ -110,48 +176,73 @@ RCT_EXPORT_MODULE()
}
}
- (void)show
- (void)toggle
{
if (_actionSheet) {
[_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES];
_actionSheet = nil;
} else {
[self show];
}
}
RCT_EXPORT_METHOD(show)
{
if (_actionSheet || !_bridge) {
return;
}
NSString *debugTitleChrome = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Enable Chrome Debugging";
NSString *debugTitleSafari = _bridge.executorClass && _bridge.executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Enable Safari Debugging";
NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
NSString *debugTitleChrome = _executorClass && _executorClass == NSClassFromString(@"RCTWebSocketExecutor") ? @"Disable Chrome Debugging" : @"Debug in Chrome";
NSString *debugTitleSafari = _executorClass && _executorClass == NSClassFromString(@"RCTWebViewExecutor") ? @"Disable Safari Debugging" : @"Debug in Safari";
UIActionSheet *actionSheet =
[[UIActionSheet alloc] initWithTitle:@"React Native: Development"
delegate:self
cancelButtonTitle:@"Cancel"
cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, liveReloadTitle, profilingTitle, nil];
otherButtonTitles:@"Reload", debugTitleChrome, debugTitleSafari, nil];
if (_liveReloadURL) {
NSString *liveReloadTitle = _liveReloadEnabled ? @"Disable Live Reload" : @"Enable Live Reload";
NSString *profilingTitle = RCTProfileIsProfiling() ? @"Stop Profiling" : @"Start Profiling";
[actionSheet addButtonWithTitle:liveReloadTitle];
[actionSheet addButtonWithTitle:profilingTitle];
}
[actionSheet addButtonWithTitle:@"Cancel"];
actionSheet.cancelButtonIndex = [actionSheet numberOfButtons] - 1;
actionSheet.actionSheetStyle = UIBarStyleBlack;
[actionSheet showInView:[UIApplication sharedApplication].keyWindow.rootViewController.view];
_actionSheet = actionSheet;
}
RCT_EXPORT_METHOD(reload)
{
_jsLoaded = NO;
_liveReloadURL = nil;
[_bridge reload];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
_actionSheet = nil;
switch (buttonIndex) {
case 0: {
[_bridge reload];
[self reload];
break;
}
case 1: {
Class cls = NSClassFromString(@"RCTWebSocketExecutor");
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : nil;
[_bridge reload];
self.executorClass = (_executorClass == cls) ? Nil : cls;
break;
}
case 2: {
Class cls = NSClassFromString(@"RCTWebViewExecutor");
_bridge.executorClass = (_bridge.executorClass != cls) ? cls : Nil;
[_bridge reload];
self.executorClass = (_executorClass == cls) ? Nil : cls;
break;
}
case 3: {
@ -167,89 +258,110 @@ RCT_EXPORT_MODULE()
}
}
- (void)setShakeToShow:(BOOL)shakeToShow
{
if (_shakeToShow != shakeToShow) {
_shakeToShow = shakeToShow;
[self updateSetting:@"shakeToShow" value: @(_shakeToShow)];
}
}
- (void)setProfilingEnabled:(BOOL)enabled
{
if (_profilingEnabled == enabled) {
return;
if (_profilingEnabled != enabled) {
_profilingEnabled = enabled;
[self updateSetting:@"profilingEnabled" value: @(_profilingEnabled)];
}
_profilingEnabled = enabled;
if (RCTProfileIsProfiling()) {
[_bridge stopProfiling];
} else {
[_bridge startProfiling];
if (_liveReloadURL && enabled != RCTProfileIsProfiling()) {
if (enabled) {
[_bridge startProfiling];
} else {
[_bridge stopProfiling];
}
}
}
- (void)setLiveReloadEnabled:(BOOL)enabled
{
if (_liveReloadEnabled == enabled) {
if (_liveReloadEnabled != enabled) {
_liveReloadEnabled = enabled;
[self updateSetting:@"liveReloadEnabled" value: @(_liveReloadEnabled)];
}
if (_liveReloadEnabled) {
[self checkForUpdates];
} else {
[_updateTask cancel];
_updateTask = nil;
}
}
- (void)setExecutorClass:(Class)executorClass
{
if (_executorClass != executorClass) {
_executorClass = executorClass;
[self updateSetting:@"executorClass" value: NSStringFromClass(executorClass)];
}
if (_bridge.executorClass != executorClass) {
// TODO (6929129): we can remove this special case test once we have better
// support for custom executors in the dev menu. But right now this is
// needed to prevent overriding a custom executor with the default if a
// custom executor has been set directly on the bridge
if (executorClass == Nil &&
(_bridge.executorClass != NSClassFromString(@"RCTWebSocketExecutor") &&
_bridge.executorClass != NSClassFromString(@"RCTWebViewExecutor"))) {
return;
}
_bridge.executorClass = executorClass;
[self reload];
}
}
- (void)checkForUpdates
{
if (!_jsLoaded || !_liveReloadEnabled || !_liveReloadURL) {
return;
}
_liveReloadEnabled = enabled;
if (_liveReloadEnabled) {
_updateTimer = [NSTimer scheduledTimerWithTimeInterval:_liveReloadPeriod
target:self
selector:@selector(pollForUpdates)
userInfo:nil
repeats:YES];
} else {
[_updateTimer invalidate];
_updateTimer = nil;
}
}
- (void)setLiveReloadPeriod:(NSTimeInterval)liveReloadPeriod
{
_liveReloadPeriod = liveReloadPeriod;
if (_liveReloadEnabled) {
self.liveReloadEnabled = NO;
self.liveReloadEnabled = YES;
}
}
- (void)pollForUpdates
{
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
if (!sourceCodeModule) {
RCTLogError(@"RCTSourceCode module not found");
self.liveReloadEnabled = NO;
if (_updateTask) {
[_updateTask cancel];
_updateTask = nil;
return;
}
NSURL *longPollURL = [[NSURL alloc] initWithString:@"/onchange" relativeToURL:sourceCodeModule.scriptURL];
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:longPollURL]
queue:[[NSOperationQueue alloc] init]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
__weak RCTDevMenu *weakSelf = self;
_updateTask = [[NSURLSession sharedSession] dataTaskWithURL:_liveReloadURL completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
if (_liveReloadEnabled && HTTPResponse.statusCode == 205) {
[_bridge reload];
}
}];
}
dispatch_async(dispatch_get_main_queue(), ^{
__strong RCTDevMenu *strongSelf = weakSelf;
if (strongSelf && strongSelf->_liveReloadEnabled) {
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
if (!error && HTTPResponse.statusCode == 205) {
[strongSelf reload];
} else {
strongSelf->_updateTask = nil;
[strongSelf checkForUpdates];
}
}
});
- (BOOL)isValid
{
return !_liveReloadEnabled || _updateTimer != nil;
}
}];
- (void)invalidate
{
[_actionSheet dismissWithClickedButtonIndex:_actionSheet.cancelButtonIndex animated:YES];
[_updateTimer invalidate];
_updateTimer = nil;
[_updateTask resume];
}
@end
#else // Unvailable
#else // Unavailable when not in dev mode
@implementation RCTDevMenu
- (void)show {}
- (void)reload {}
@end

View File

@ -90,6 +90,17 @@ static RCTKeyCommands *RKKeyCommandsSharedInstance = nil;
{
RCTAssertMainThread();
if (input.length && flags) {
// Workaround around the first cmd not working: http://openradar.appspot.com/19613391
// You can register just the cmd key and do nothing. This ensures that
// command-key modified commands will work first time.
[self registerKeyCommandWithInput:@""
modifierFlags:flags
action:nil];
}
UIKeyCommand *command = [UIKeyCommand keyCommandWithInput:input
modifierFlags:flags
action:@selector(RCT_handleKeyCommand:)];

View File

@ -91,7 +91,7 @@
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:nil];
[[[NSURLSession sharedSession] dataTaskWithRequest:request] resume];
}
- (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack showIfHidden:(BOOL)shouldShow

View File

@ -582,6 +582,7 @@
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"RCT_DEBUG=1",
"RCT_DEV=1",
"RCT_NSASSERT=1",