From ee521f9c05c2be5b28a2c1bc86e0a89bbea6a0bf Mon Sep 17 00:00:00 2001 From: Ayush Sood Date: Thu, 14 Dec 2017 10:38:34 -0800 Subject: [PATCH] Add extra data view to RN RedBox Reviewed By: shergin Differential Revision: D6382976 fbshipit-source-id: 33568a241395b085a840ac52adab3c9dc463ea4c --- Libraries/BugReporting/BugReporting.js | 11 + React/Modules/RCTRedBox.m | 594 ++++++++++-------- .../RCTRedBoxExtraDataViewController.h | 22 + .../RCTRedBoxExtraDataViewController.m | 288 +++++++++ React/React.xcodeproj/project.pbxproj | 8 + 5 files changed, 652 insertions(+), 271 deletions(-) create mode 100644 React/Modules/RCTRedBoxExtraDataViewController.h create mode 100644 React/Modules/RCTRedBoxExtraDataViewController.m diff --git a/Libraries/BugReporting/BugReporting.js b/Libraries/BugReporting/BugReporting.js index 277f16b45e1186..bf795899139a69 100644 --- a/Libraries/BugReporting/BugReporting.js +++ b/Libraries/BugReporting/BugReporting.js @@ -35,6 +35,7 @@ class BugReporting { static _extraSources: Map = new Map(); static _fileSources: Map = new Map(); static _subscription: ?EmitterSubscription = null; + static _redboxSubscription: ?EmitterSubscription = null; static _maybeInit() { if (!BugReporting._subscription) { @@ -42,6 +43,11 @@ class BugReporting { .addListener('collectBugExtraData', BugReporting.collectExtraData, null); defaultExtras(); } + + if (!BugReporting._redboxSubscription) { + BugReporting._redboxSubscription = RCTDeviceEventEmitter + .addListener('collectRedBoxExtraData', BugReporting.collectExtraData, null); + } } /** @@ -98,6 +104,11 @@ class BugReporting { BugReportingNativeModule.setExtraData && BugReportingNativeModule.setExtraData(extraData, fileData); + const RedBoxNativeModule = require('NativeModules').RedBox; + RedBoxNativeModule && + RedBoxNativeModule.setExtraData && + RedBoxNativeModule.setExtraData(extraData, 'From BugReporting.js'); + return { extras: extraData, files: fileData }; } } diff --git a/React/Modules/RCTRedBox.m b/React/Modules/RCTRedBox.m index cb20776b9660ab..71728640d1fa39 100644 --- a/React/Modules/RCTRedBox.m +++ b/React/Modules/RCTRedBox.m @@ -13,7 +13,9 @@ #import "RCTConvert.h" #import "RCTDefines.h" #import "RCTErrorInfo.h" +#import "RCTEventDispatcher.h" #import "RCTJSStackFrame.h" +#import "RCTRedBoxExtraDataViewController.h" #import "RCTUtils.h" #if RCT_DEBUG @@ -24,6 +26,7 @@ @protocol RCTRedBoxWindowActionDelegate - (void)redBoxWindow:(RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame; - (void)reloadFromRedBoxWindow:(RCTRedBoxWindow *)redBoxWindow; +- (void)loadExtraDataViewController; @end @@ -33,308 +36,332 @@ @interface RCTRedBoxWindow : UIWindow *_lastStackTrace; + UITableView *_stackTraceTableView; + NSString *_lastErrorMessage; + NSArray *_lastStackTrace; } - (instancetype)initWithFrame:(CGRect)frame { - if ((self = [super initWithFrame:frame])) { + if ((self = [super initWithFrame:frame])) { #if TARGET_OS_TV - self.windowLevel = UIWindowLevelAlert + 1000; + self.windowLevel = UIWindowLevelAlert + 1000; #else - self.windowLevel = UIWindowLevelStatusBar - 1; + self.windowLevel = UIWindowLevelStatusBar - 1; #endif - self.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1]; - self.hidden = YES; + self.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1]; + self.hidden = YES; - UIViewController *rootController = [UIViewController new]; - self.rootViewController = rootController; - UIView *rootView = rootController.view; - rootView.backgroundColor = [UIColor clearColor]; + UIViewController *rootController = [UIViewController new]; + self.rootViewController = rootController; + UIView *rootView = rootController.view; + rootView.backgroundColor = [UIColor clearColor]; - const CGFloat buttonHeight = 60; + const CGFloat buttonHeight = 60; - CGRect detailsFrame = rootView.bounds; - detailsFrame.size.height -= buttonHeight; + CGRect detailsFrame = rootView.bounds; + detailsFrame.size.height -= buttonHeight; - _stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain]; - _stackTraceTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _stackTraceTableView.delegate = self; - _stackTraceTableView.dataSource = self; - _stackTraceTableView.backgroundColor = [UIColor clearColor]; + _stackTraceTableView = [[UITableView alloc] initWithFrame:detailsFrame style:UITableViewStylePlain]; + _stackTraceTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + _stackTraceTableView.delegate = self; + _stackTraceTableView.dataSource = self; + _stackTraceTableView.backgroundColor = [UIColor clearColor]; #if !TARGET_OS_TV - _stackTraceTableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.3]; - _stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone; + _stackTraceTableView.separatorColor = [UIColor colorWithWhite:1 alpha:0.3]; + _stackTraceTableView.separatorStyle = UITableViewCellSeparatorStyleNone; +#endif + _stackTraceTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite; + [rootView addSubview:_stackTraceTableView]; + +#if TARGET_OS_SIMULATOR + NSString *reloadText = @"Reload JS (\u2318R)"; + NSString *dismissText = @"Dismiss (ESC)"; + NSString *copyText = @"Copy (\u2325\u2318C)"; + NSString *extraText = @"Extra Info (\u2318E)"; +#else + NSString *reloadText = @"Reload JS"; + NSString *dismissText = @"Dismiss"; + NSString *copyText = @"Copy"; + NSString *extraText = @"Extra Info"; #endif - _stackTraceTableView.indicatorStyle = UIScrollViewIndicatorStyleWhite; - [rootView addSubview:_stackTraceTableView]; - - #if TARGET_OS_SIMULATOR - NSString *reloadText = @"Reload JS (\u2318R)"; - NSString *dismissText = @"Dismiss (ESC)"; - NSString *copyText = @"Copy (\u2325\u2318C)"; - #else - NSString *reloadText = @"Reload JS"; - NSString *dismissText = @"Dismiss"; - NSString *copyText = @"Copy"; - #endif - - UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom]; - dismissButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin; - dismissButton.accessibilityIdentifier = @"redbox-dismiss"; - dismissButton.titleLabel.font = [UIFont systemFontOfSize:14]; - [dismissButton setTitle:dismissText forState:UIControlStateNormal]; - [dismissButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal]; - [dismissButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; - [dismissButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside]; - - UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom]; - reloadButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin; - reloadButton.accessibilityIdentifier = @"redbox-reload"; - reloadButton.titleLabel.font = [UIFont systemFontOfSize:14]; - - [reloadButton setTitle:reloadText forState:UIControlStateNormal]; - [reloadButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal]; - [reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; - [reloadButton addTarget:self action:@selector(reload) forControlEvents:UIControlEventTouchUpInside]; - - UIButton *copyButton = [UIButton buttonWithType:UIButtonTypeCustom]; - copyButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin; - copyButton.accessibilityIdentifier = @"redbox-copy"; - copyButton.titleLabel.font = [UIFont systemFontOfSize:14]; - [copyButton setTitle:copyText forState:UIControlStateNormal]; - [copyButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal]; - [copyButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; - [copyButton addTarget:self action:@selector(copyStack) forControlEvents:UIControlEventTouchUpInside]; - - CGFloat buttonWidth = self.bounds.size.width / 3; - dismissButton.frame = CGRectMake(0, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight); - reloadButton.frame = CGRectMake(buttonWidth, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight); - copyButton.frame = CGRectMake(buttonWidth * 2, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight); - [rootView addSubview:dismissButton]; - [rootView addSubview:reloadButton]; - [rootView addSubview:copyButton]; - } - return self; + + UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom]; + dismissButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin; + dismissButton.accessibilityIdentifier = @"redbox-dismiss"; + dismissButton.titleLabel.font = [UIFont systemFontOfSize:13]; + [dismissButton setTitle:dismissText forState:UIControlStateNormal]; + [dismissButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal]; + [dismissButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; + [dismissButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside]; + + UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom]; + reloadButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin; + reloadButton.accessibilityIdentifier = @"redbox-reload"; + reloadButton.titleLabel.font = [UIFont systemFontOfSize:13]; + + [reloadButton setTitle:reloadText forState:UIControlStateNormal]; + [reloadButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal]; + [reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; + [reloadButton addTarget:self action:@selector(reload) forControlEvents:UIControlEventTouchUpInside]; + + UIButton *copyButton = [UIButton buttonWithType:UIButtonTypeCustom]; + copyButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin; + copyButton.accessibilityIdentifier = @"redbox-copy"; + copyButton.titleLabel.font = [UIFont systemFontOfSize:13]; + [copyButton setTitle:copyText forState:UIControlStateNormal]; + [copyButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal]; + [copyButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; + [copyButton addTarget:self action:@selector(copyStack) forControlEvents:UIControlEventTouchUpInside]; + + UIButton *extraButton = [UIButton buttonWithType:UIButtonTypeCustom]; + extraButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin; + extraButton.accessibilityIdentifier = @"redbox-extra"; + extraButton.titleLabel.font = [UIFont systemFontOfSize:13]; + [extraButton setTitle:extraText forState:UIControlStateNormal]; + [extraButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal]; + [extraButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted]; + [extraButton addTarget:self action:@selector(showExtraDataViewController) forControlEvents:UIControlEventTouchUpInside]; + + CGFloat buttonWidth = self.bounds.size.width / 4; + dismissButton.frame = CGRectMake(0, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight); + reloadButton.frame = CGRectMake(buttonWidth, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight); + copyButton.frame = CGRectMake(buttonWidth * 2, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight); + extraButton.frame = CGRectMake(buttonWidth * 3, self.bounds.size.height - buttonHeight, buttonWidth, buttonHeight); + + [rootView addSubview:dismissButton]; + [rootView addSubview:reloadButton]; + [rootView addSubview:copyButton]; + [rootView addSubview:extraButton]; + } + return self; } RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:(NSCoder *)aDecoder) - (void)dealloc { - _stackTraceTableView.dataSource = nil; - _stackTraceTableView.delegate = nil; - [[NSNotificationCenter defaultCenter] removeObserver:self]; + _stackTraceTableView.dataSource = nil; + _stackTraceTableView.delegate = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack isUpdate:(BOOL)isUpdate { - // Show if this is a new message, or if we're updating the previous message - if ((self.hidden && !isUpdate) || (!self.hidden && isUpdate && [_lastErrorMessage isEqualToString:message])) { - _lastStackTrace = stack; - // message is displayed using UILabel, which is unable to render text of - // unlimited length, so we truncate it - _lastErrorMessage = [message substringToIndex:MIN((NSUInteger)10000, message.length)]; + // Show if this is a new message, or if we're updating the previous message + if ((self.hidden && !isUpdate) || (!self.hidden && isUpdate && [_lastErrorMessage isEqualToString:message])) { + _lastStackTrace = stack; + // message is displayed using UILabel, which is unable to render text of + // unlimited length, so we truncate it + _lastErrorMessage = [message substringToIndex:MIN((NSUInteger)10000, message.length)]; - [_stackTraceTableView reloadData]; + [_stackTraceTableView reloadData]; - if (self.hidden) { - [_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] - atScrollPosition:UITableViewScrollPositionTop - animated:NO]; - } + if (self.hidden) { + [_stackTraceTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] + atScrollPosition:UITableViewScrollPositionTop + animated:NO]; + } - [self makeKeyAndVisible]; - [self becomeFirstResponder]; - } + [self makeKeyAndVisible]; + [self becomeFirstResponder]; + } } - (void)dismiss { - self.hidden = YES; - [self resignFirstResponder]; - [RCTSharedApplication().delegate.window makeKeyWindow]; + self.hidden = YES; + [self resignFirstResponder]; + [RCTSharedApplication().delegate.window makeKeyWindow]; } - (void)reload { - [_actionDelegate reloadFromRedBoxWindow:self]; + [_actionDelegate reloadFromRedBoxWindow:self]; +} + +- (void)showExtraDataViewController +{ + [_actionDelegate loadExtraDataViewController]; } - (void)copyStack { - NSMutableString *fullStackTrace; + NSMutableString *fullStackTrace; - if (_lastErrorMessage != nil) { - fullStackTrace = [_lastErrorMessage mutableCopy]; - [fullStackTrace appendString:@"\n\n"]; - } - else { - fullStackTrace = [NSMutableString string]; - } + if (_lastErrorMessage != nil) { + fullStackTrace = [_lastErrorMessage mutableCopy]; + [fullStackTrace appendString:@"\n\n"]; + } + else { + fullStackTrace = [NSMutableString string]; + } - for (RCTJSStackFrame *stackFrame in _lastStackTrace) { - [fullStackTrace appendString:[NSString stringWithFormat:@"%@\n", stackFrame.methodName]]; - if (stackFrame.file) { - [fullStackTrace appendFormat:@" %@\n", [self formatFrameSource:stackFrame]]; + for (RCTJSStackFrame *stackFrame in _lastStackTrace) { + [fullStackTrace appendString:[NSString stringWithFormat:@"%@\n", stackFrame.methodName]]; + if (stackFrame.file) { + [fullStackTrace appendFormat:@" %@\n", [self formatFrameSource:stackFrame]]; + } } - } #if !TARGET_OS_TV - UIPasteboard *pb = [UIPasteboard generalPasteboard]; - [pb setString:fullStackTrace]; + UIPasteboard *pb = [UIPasteboard generalPasteboard]; + [pb setString:fullStackTrace]; #endif } - (NSString *)formatFrameSource:(RCTJSStackFrame *)stackFrame { - NSString *fileName = RCTNilIfNull(stackFrame.file) ? [stackFrame.file lastPathComponent] : @""; - NSString *lineInfo = [NSString stringWithFormat:@"%@:%lld", - fileName, - (long long)stackFrame.lineNumber]; + NSString *fileName = RCTNilIfNull(stackFrame.file) ? [stackFrame.file lastPathComponent] : @""; + NSString *lineInfo = [NSString stringWithFormat:@"%@:%lld", + fileName, + (long long)stackFrame.lineNumber]; - if (stackFrame.column != 0) { - lineInfo = [lineInfo stringByAppendingFormat:@":%lld", (long long)stackFrame.column]; - } - return lineInfo; + if (stackFrame.column != 0) { + lineInfo = [lineInfo stringByAppendingFormat:@":%lld", (long long)stackFrame.column]; + } + return lineInfo; } #pragma mark - TableView - (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView { - return 2; + return 2; } - (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return section == 0 ? 1 : _lastStackTrace.count; + return section == 0 ? 1 : _lastStackTrace.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == 0) { - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"msg-cell"]; - return [self reuseCell:cell forErrorMessage:_lastErrorMessage]; - } - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; - NSUInteger index = indexPath.row; - RCTJSStackFrame *stackFrame = _lastStackTrace[index]; - return [self reuseCell:cell forStackFrame:stackFrame]; + if (indexPath.section == 0) { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"msg-cell"]; + return [self reuseCell:cell forErrorMessage:_lastErrorMessage]; + } + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; + NSUInteger index = indexPath.row; + RCTJSStackFrame *stackFrame = _lastStackTrace[index]; + return [self reuseCell:cell forStackFrame:stackFrame]; } - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forErrorMessage:(NSString *)message { - if (!cell) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"msg-cell"]; - cell.textLabel.accessibilityIdentifier = @"redbox-error"; - cell.textLabel.textColor = [UIColor whiteColor]; - cell.textLabel.font = [UIFont boldSystemFontOfSize:16]; - cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping; - cell.textLabel.numberOfLines = 0; - cell.detailTextLabel.textColor = [UIColor whiteColor]; - cell.backgroundColor = [UIColor clearColor]; - cell.selectionStyle = UITableViewCellSelectionStyleNone; - } + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"msg-cell"]; + cell.textLabel.accessibilityIdentifier = @"redbox-error"; + cell.textLabel.textColor = [UIColor whiteColor]; + cell.textLabel.font = [UIFont boldSystemFontOfSize:16]; + cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping; + cell.textLabel.numberOfLines = 0; + cell.detailTextLabel.textColor = [UIColor whiteColor]; + cell.backgroundColor = [UIColor clearColor]; + cell.selectionStyle = UITableViewCellSelectionStyleNone; + } - cell.textLabel.text = message; + cell.textLabel.text = message; - return cell; + return cell; } - (UITableViewCell *)reuseCell:(UITableViewCell *)cell forStackFrame:(RCTJSStackFrame *)stackFrame { - if (!cell) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"]; - cell.textLabel.textColor = [UIColor colorWithWhite:1 alpha:0.9]; - cell.textLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:14]; - cell.textLabel.lineBreakMode = NSLineBreakByCharWrapping; - cell.textLabel.numberOfLines = 2; - cell.detailTextLabel.textColor = [UIColor colorWithWhite:1 alpha:0.7]; - cell.detailTextLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:11]; - cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; - cell.backgroundColor = [UIColor clearColor]; - cell.selectedBackgroundView = [UIView new]; - cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2]; - } - - cell.textLabel.text = stackFrame.methodName ?: @"(unnamed method)"; - if (stackFrame.file) { - cell.detailTextLabel.text = [self formatFrameSource:stackFrame]; - } else { - cell.detailTextLabel.text = @""; - } - return cell; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"cell"]; + cell.textLabel.textColor = [UIColor colorWithWhite:1 alpha:0.9]; + cell.textLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:14]; + cell.textLabel.lineBreakMode = NSLineBreakByCharWrapping; + cell.textLabel.numberOfLines = 2; + cell.detailTextLabel.textColor = [UIColor colorWithWhite:1 alpha:0.7]; + cell.detailTextLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:11]; + cell.detailTextLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; + cell.backgroundColor = [UIColor clearColor]; + cell.selectedBackgroundView = [UIView new]; + cell.selectedBackgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.2]; + } + + cell.textLabel.text = stackFrame.methodName ?: @"(unnamed method)"; + if (stackFrame.file) { + cell.detailTextLabel.text = [self formatFrameSource:stackFrame]; + } else { + cell.detailTextLabel.text = @""; + } + return cell; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == 0) { - NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; - paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; + if (indexPath.section == 0) { + NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; + paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; - NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:16], - NSParagraphStyleAttributeName: paragraphStyle}; - CGRect boundingRect = [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]; - return ceil(boundingRect.size.height) + 40; - } else { - return 50; - } + NSDictionary *attributes = @{NSFontAttributeName: [UIFont boldSystemFontOfSize:16], + NSParagraphStyleAttributeName: paragraphStyle}; + CGRect boundingRect = [_lastErrorMessage boundingRectWithSize:CGSizeMake(tableView.frame.size.width - 30, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil]; + return ceil(boundingRect.size.height) + 40; + } else { + return 50; + } } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - if (indexPath.section == 1) { - NSUInteger row = indexPath.row; - RCTJSStackFrame *stackFrame = _lastStackTrace[row]; - [_actionDelegate redBoxWindow:self openStackFrameInEditor:stackFrame]; - } - [tableView deselectRowAtIndexPath:indexPath animated:YES]; + if (indexPath.section == 1) { + NSUInteger row = indexPath.row; + RCTJSStackFrame *stackFrame = _lastStackTrace[row]; + [_actionDelegate redBoxWindow:self openStackFrameInEditor:stackFrame]; + } + [tableView deselectRowAtIndexPath:indexPath animated:YES]; } #pragma mark - Key commands - (NSArray *)keyCommands { - // NOTE: We could use RCTKeyCommands for this, but since - // we control this window, we can use the standard, non-hacky - // mechanism instead + // NOTE: We could use RCTKeyCommands for this, but since + // we control this window, we can use the standard, non-hacky + // mechanism instead - return @[ - // Dismiss red box - [UIKeyCommand keyCommandWithInput:UIKeyInputEscape - modifierFlags:0 - action:@selector(dismiss)], + return @[ + // Dismiss red box + [UIKeyCommand keyCommandWithInput:UIKeyInputEscape + modifierFlags:0 + action:@selector(dismiss)], - // Reload - [UIKeyCommand keyCommandWithInput:@"r" - modifierFlags:UIKeyModifierCommand - action:@selector(reload)], + // Reload + [UIKeyCommand keyCommandWithInput:@"r" + modifierFlags:UIKeyModifierCommand + action:@selector(reload)], - // Copy = Cmd-Option C since Cmd-C in the simulator copies the pasteboard from - // the simulator to the desktop pasteboard. - [UIKeyCommand keyCommandWithInput:@"c" - modifierFlags:UIKeyModifierCommand | UIKeyModifierAlternate - action:@selector(copyStack)] + // Copy = Cmd-Option C since Cmd-C in the simulator copies the pasteboard from + // the simulator to the desktop pasteboard. + [UIKeyCommand keyCommandWithInput:@"c" + modifierFlags:UIKeyModifierCommand | UIKeyModifierAlternate + action:@selector(copyStack)], - ]; + // Extra data + [UIKeyCommand keyCommandWithInput:@"e" + modifierFlags:UIKeyModifierCommand + action:@selector(showExtraDataViewController)] + ]; } - (BOOL)canBecomeFirstResponder { - return YES; + return YES; } @end -@interface RCTRedBox () +@interface RCTRedBox () @end @implementation RCTRedBox { - RCTRedBoxWindow *_window; - NSMutableArray> *_errorCustomizers; + RCTRedBoxWindow *_window; + NSMutableArray> *_errorCustomizers; + RCTRedBoxExtraDataViewController *_extraDataViewController; } @synthesize bridge = _bridge; @@ -343,140 +370,165 @@ @implementation RCTRedBox - (void)registerErrorCustomizer:(id)errorCustomizer { - dispatch_async(dispatch_get_main_queue(), ^{ - if (!self->_errorCustomizers) { - self->_errorCustomizers = [NSMutableArray array]; - } - if (![self->_errorCustomizers containsObject:errorCustomizer]) { - [self->_errorCustomizers addObject:errorCustomizer]; - } - }); + dispatch_async(dispatch_get_main_queue(), ^{ + if (!self->_errorCustomizers) { + self->_errorCustomizers = [NSMutableArray array]; + } + if (![self->_errorCustomizers containsObject:errorCustomizer]) { + [self->_errorCustomizers addObject:errorCustomizer]; + } + }); } // WARNING: Should only be called from the main thread/dispatch queue. - (RCTErrorInfo *)_customizeError:(RCTErrorInfo *)error { - RCTAssertMainQueue(); - - if (!self->_errorCustomizers) { - return error; - } - for (id customizer in self->_errorCustomizers) { - RCTErrorInfo *newInfo = [customizer customizeErrorInfo:error]; - if (newInfo) { - error = newInfo; + RCTAssertMainQueue(); + if (!self->_errorCustomizers) { + return error; } - } - return error; + for (id customizer in self->_errorCustomizers) { + RCTErrorInfo *newInfo = [customizer customizeErrorInfo:error]; + if (newInfo) { + error = newInfo; + } + } + return error; } - (void)showError:(NSError *)error { - [self showErrorMessage:error.localizedDescription - withDetails:error.localizedFailureReason - stack:error.userInfo[RCTJSStackTraceKey]]; + [self showErrorMessage:error.localizedDescription + withDetails:error.localizedFailureReason + stack:error.userInfo[RCTJSStackTraceKey]]; } - (void)showErrorMessage:(NSString *)message { - [self showErrorMessage:message withParsedStack:nil isUpdate:NO]; + [self showErrorMessage:message withParsedStack:nil isUpdate:NO]; } - (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details { - [self showErrorMessage:message withDetails:details stack:nil]; + [self showErrorMessage:message withDetails:details stack:nil]; } - (void)showErrorMessage:(NSString *)message withDetails:(NSString *)details stack:(NSArray *)stack { - NSString *combinedMessage = message; - if (details) { - combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details]; - } - [self showErrorMessage:combinedMessage withParsedStack:stack isUpdate:NO]; + NSString *combinedMessage = message; + if (details) { + combinedMessage = [NSString stringWithFormat:@"%@\n\n%@", message, details]; + } + [self showErrorMessage:combinedMessage withParsedStack:stack isUpdate:NO]; } - (void)showErrorMessage:(NSString *)message withRawStack:(NSString *)rawStack { - NSArray *stack = [RCTJSStackFrame stackFramesWithLines:rawStack]; - [self showErrorMessage:message withParsedStack:stack isUpdate:NO]; + NSArray *stack = [RCTJSStackFrame stackFramesWithLines:rawStack]; + [self showErrorMessage:message withParsedStack:stack isUpdate:NO]; } - (void)showErrorMessage:(NSString *)message withStack:(NSArray *)stack { - [self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:NO]; + [self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:NO]; } - (void)updateErrorMessage:(NSString *)message withStack:(NSArray *)stack { - [self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:YES]; + [self showErrorMessage:message withParsedStack:[RCTJSStackFrame stackFramesWithDictionaries:stack] isUpdate:YES]; } - (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray *)stack { - [self showErrorMessage:message withParsedStack:stack isUpdate:NO]; + [self showErrorMessage:message withParsedStack:stack isUpdate:NO]; } - (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray *)stack { - [self showErrorMessage:message withParsedStack:stack isUpdate:YES]; + [self showErrorMessage:message withParsedStack:stack isUpdate:YES]; } - (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray *)stack isUpdate:(BOOL)isUpdate { - dispatch_async(dispatch_get_main_queue(), ^{ - if (!self->_window) { - self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - self->_window.actionDelegate = self; - } - RCTErrorInfo *errorInfo = [[RCTErrorInfo alloc] initWithErrorMessage:message - stack:stack]; - errorInfo = [self _customizeError:errorInfo]; - [self->_window showErrorMessage:errorInfo.errorMessage - withStack:errorInfo.stack - isUpdate:isUpdate]; - }); + dispatch_async(dispatch_get_main_queue(), ^{ + if (self->_extraDataViewController == nil) { + self->_extraDataViewController = [RCTRedBoxExtraDataViewController new]; + self->_extraDataViewController.actionDelegate = self; + } + [self->_bridge.eventDispatcher sendDeviceEventWithName:@"collectRedBoxExtraData" body:nil]; + + if (!self->_window) { + self->_window = [[RCTRedBoxWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + self->_window.actionDelegate = self; + } + + RCTErrorInfo *errorInfo = [[RCTErrorInfo alloc] initWithErrorMessage:message + stack:stack]; + errorInfo = [self _customizeError:errorInfo]; + [self->_window showErrorMessage:errorInfo.errorMessage + withStack:errorInfo.stack + isUpdate:isUpdate]; + }); +} + +- (void)loadExtraDataViewController { + dispatch_async(dispatch_get_main_queue(), ^{ + // Make sure the CMD+E shortcut doesn't call this twice + if (self->_extraDataViewController != nil && ![self->_window.rootViewController presentedViewController]) { + [self->_window.rootViewController presentViewController:self->_extraDataViewController animated:YES completion:nil]; + } + }); +} + +RCT_EXPORT_METHOD(setExtraData:(NSDictionary *)extraData forIdentifier:(NSString *)identifier) { + [_extraDataViewController addExtraData:extraData forIdentifier:identifier]; } RCT_EXPORT_METHOD(dismiss) { - dispatch_async(dispatch_get_main_queue(), ^{ - [self->_window dismiss]; - }); + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_window dismiss]; + }); } - (void)invalidate { - [self dismiss]; + [self dismiss]; } - (void)redBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame { - NSURL *const bundleURL = _overrideBundleURL ?: _bridge.bundleURL; - if (![bundleURL.scheme hasPrefix:@"http"]) { - RCTLogWarn(@"Cannot open stack frame in editor because you're not connected to the packager."); - return; - } + NSURL *const bundleURL = _overrideBundleURL ?: _bridge.bundleURL; + if (![bundleURL.scheme hasPrefix:@"http"]) { + RCTLogWarn(@"Cannot open stack frame in editor because you're not connected to the packager."); + return; + } - NSData *stackFrameJSON = [RCTJSONStringify([stackFrame toDictionary], NULL) dataUsingEncoding:NSUTF8StringEncoding]; - NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length]; - NSMutableURLRequest *request = [NSMutableURLRequest new]; - request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:bundleURL]; - request.HTTPMethod = @"POST"; - request.HTTPBody = stackFrameJSON; - [request setValue:postLength forHTTPHeaderField:@"Content-Length"]; - [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + NSData *stackFrameJSON = [RCTJSONStringify([stackFrame toDictionary], NULL) dataUsingEncoding:NSUTF8StringEncoding]; + NSString *postLength = [NSString stringWithFormat:@"%tu", stackFrameJSON.length]; + NSMutableURLRequest *request = [NSMutableURLRequest new]; + request.URL = [NSURL URLWithString:@"/open-stack-frame" relativeToURL:bundleURL]; + request.HTTPMethod = @"POST"; + request.HTTPBody = stackFrameJSON; + [request setValue:postLength forHTTPHeaderField:@"Content-Length"]; + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - [[[NSURLSession sharedSession] dataTaskWithRequest:request] resume]; + [[[NSURLSession sharedSession] dataTaskWithRequest:request] resume]; +} + +- (void)reload +{ + // Window is not used and can be nil + [self reloadFromRedBoxWindow:nil]; } - (void)reloadFromRedBoxWindow:(__unused RCTRedBoxWindow *)redBoxWindow { - if (_overrideReloadAction) { - _overrideReloadAction(); - } else { - [_bridge reload]; - } - [self dismiss]; + if (_overrideReloadAction) { + _overrideReloadAction(); + } else { + [_bridge reload]; + } + [self dismiss]; } @end @@ -485,7 +537,7 @@ @implementation RCTBridge (RCTRedBox) - (RCTRedBox *)redBox { - return [self moduleForClass:[RCTRedBox class]]; + return [self moduleForClass:[RCTRedBox class]]; } @end diff --git a/React/Modules/RCTRedBoxExtraDataViewController.h b/React/Modules/RCTRedBoxExtraDataViewController.h new file mode 100644 index 00000000000000..484547814476e7 --- /dev/null +++ b/React/Modules/RCTRedBoxExtraDataViewController.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@protocol RCTRedBoxExtraDataActionDelegate +- (void)reload; +@end + +@interface RCTRedBoxExtraDataViewController : UIViewController + +@property (nonatomic, weak) id actionDelegate; + +- (void)addExtraData:(NSDictionary *)data forIdentifier:(NSString *)identifier; + +@end diff --git a/React/Modules/RCTRedBoxExtraDataViewController.m b/React/Modules/RCTRedBoxExtraDataViewController.m new file mode 100644 index 00000000000000..63935106809baa --- /dev/null +++ b/React/Modules/RCTRedBoxExtraDataViewController.m @@ -0,0 +1,288 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTRedBoxExtraDataViewController.h" + +@interface RCTRedBoxExtraDataCell : UITableViewCell + +@property (nonatomic, strong) UILabel *keyLabel; +@property (nonatomic, strong) UILabel *valueLabel; + +@end + +@implementation RCTRedBoxExtraDataCell + +- (instancetype)initWithStyle:(UITableViewCellStyle)style + reuseIdentifier:(NSString *)reuseIdentifier +{ + if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) { + self.backgroundColor = [UIColor colorWithRed:0.8 + green:0 blue:0 + alpha:1]; + UILayoutGuide *contentLayout = self.contentView.layoutMarginsGuide; + + self.keyLabel = [UILabel new]; + [self.contentView addSubview:self.keyLabel]; + + self.keyLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.keyLabel.leadingAnchor + constraintEqualToAnchor:contentLayout.leadingAnchor].active = YES; + [self.keyLabel.topAnchor + constraintEqualToAnchor:contentLayout.topAnchor].active = YES; + [self.keyLabel.bottomAnchor + constraintEqualToAnchor:contentLayout.bottomAnchor].active = YES; + [self.keyLabel.widthAnchor + constraintEqualToAnchor:contentLayout.widthAnchor + multiplier:0.3].active = YES; + + self.keyLabel.textColor = [UIColor whiteColor]; + self.keyLabel.numberOfLines = 0; + self.keyLabel.lineBreakMode = UILineBreakModeWordWrap; + self.keyLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:12.0f]; + + self.valueLabel = [UILabel new]; + [self.contentView addSubview:self.valueLabel]; + + self.valueLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.valueLabel.leadingAnchor + constraintEqualToAnchor:self.keyLabel.trailingAnchor + constant:10.f].active = YES; + [self.valueLabel.trailingAnchor + constraintEqualToAnchor:contentLayout.trailingAnchor].active = YES; + [self.valueLabel.topAnchor + constraintEqualToAnchor:contentLayout.topAnchor].active = YES; + [self.valueLabel.bottomAnchor + constraintEqualToAnchor:contentLayout.bottomAnchor].active = YES; + + self.valueLabel.textColor = [UIColor whiteColor]; + self.valueLabel.numberOfLines = 0; + self.valueLabel.lineBreakMode = UILineBreakModeWordWrap; + self.valueLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:12.0f]; + } + return self; +} + +@end + +@interface RCTRedBoxExtraDataViewController () + +@end + +@implementation RCTRedBoxExtraDataViewController +{ + UITableView *_tableView; + NSMutableArray *_extraDataTitle; + NSMutableArray *_extraData; +} + +@synthesize actionDelegate = _actionDelegate; + +- (instancetype)init +{ + if (self = [super init]) { + _extraData = [NSMutableArray new]; + _extraDataTitle = [NSMutableArray new]; + self.view.backgroundColor = [UIColor colorWithRed:0.8 + green:0 + blue:0 + alpha:1]; + + _tableView = [UITableView new]; + _tableView.delegate = self; + _tableView.dataSource = self; + _tableView.backgroundColor = [UIColor clearColor]; + _tableView.estimatedRowHeight = 200; + _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; + _tableView.rowHeight = UITableViewAutomaticDimension; + _tableView.allowsSelection = NO; + +#if TARGET_OS_SIMULATOR + NSString *reloadText = @"Reload JS (\u2318R)"; + NSString *dismissText = @"Dismiss (ESC)"; +#else + NSString *reloadText = @"Reload JS"; + NSString *dismissText = @"Dismiss"; +#endif + + UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom]; + dismissButton.translatesAutoresizingMaskIntoConstraints = NO; + dismissButton.accessibilityIdentifier = @"redbox-extra-data-dismiss"; + dismissButton.titleLabel.font = [UIFont systemFontOfSize:13]; + [dismissButton setTitle:dismissText forState:UIControlStateNormal]; + [dismissButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] + forState:UIControlStateNormal]; + [dismissButton setTitleColor:[UIColor whiteColor] + forState:UIControlStateHighlighted]; + [dismissButton addTarget:self + action:@selector(dismiss) + forControlEvents:UIControlEventTouchUpInside]; + + UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom]; + reloadButton.accessibilityIdentifier = @"redbox-reload"; + reloadButton.titleLabel.font = [UIFont systemFontOfSize:13]; + [reloadButton setTitle:reloadText forState:UIControlStateNormal]; + [reloadButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] + forState:UIControlStateNormal]; + [reloadButton setTitleColor:[UIColor whiteColor] + forState:UIControlStateHighlighted]; + [reloadButton addTarget:self + action:@selector(reload) + forControlEvents:UIControlEventTouchUpInside]; + + UIStackView *buttonStackView = [UIStackView new]; + buttonStackView.axis = UILayoutConstraintAxisHorizontal; + buttonStackView.distribution = UIStackViewDistributionEqualSpacing; + buttonStackView.alignment = UIStackViewAlignmentFill; + buttonStackView.spacing = 20; + + [buttonStackView addArrangedSubview:dismissButton]; + [buttonStackView addArrangedSubview:reloadButton]; + buttonStackView.translatesAutoresizingMaskIntoConstraints = NO; + + UIStackView *mainStackView = [UIStackView new]; + mainStackView.axis = UILayoutConstraintAxisVertical; + mainStackView.backgroundColor = [UIColor colorWithRed:0.8 + green:0 blue:0 + alpha:1]; + [mainStackView addArrangedSubview:_tableView]; + [mainStackView addArrangedSubview:buttonStackView]; + mainStackView.translatesAutoresizingMaskIntoConstraints = NO; + + [self.view addSubview:mainStackView]; + + CGFloat tableHeight = self.view.bounds.size.height - 60.f; + [_tableView.heightAnchor constraintEqualToConstant:tableHeight].active = YES; + [_tableView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor].active = YES; + + CGFloat buttonWidth = self.view.bounds.size.width / 4; + [dismissButton.heightAnchor constraintEqualToConstant:60].active = YES; + [dismissButton.widthAnchor + constraintEqualToConstant:buttonWidth].active = YES; + [reloadButton.heightAnchor constraintEqualToConstant:60].active = YES; + [reloadButton.widthAnchor + constraintEqualToConstant:buttonWidth].active = YES; + } + return self; +} + +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + [_tableView reloadData]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return [[_extraData objectAtIndex:section] count]; +} + +- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section +{ + return 40; +} + +- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section +{ + UIView *view = [UIView new]; + view.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:1]; + + UILabel *header = [UILabel new]; + [view addSubview:header]; + + header.translatesAutoresizingMaskIntoConstraints = NO; + [header.leadingAnchor + constraintEqualToAnchor:view.leadingAnchor constant:5].active = YES; + [header.trailingAnchor + constraintEqualToAnchor:view.trailingAnchor].active = YES; + [header.topAnchor + constraintEqualToAnchor:view.topAnchor].active = YES; + [header.bottomAnchor + constraintEqualToAnchor:view.bottomAnchor].active = YES; + + header.textColor = [UIColor whiteColor]; + header.font = [UIFont fontWithName:@"Menlo-Bold" size:14.0f]; + header.text = [_extraDataTitle[section] uppercaseString]; + + return view; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + static NSString *reuseIdentifier = @"RedBoxExtraData"; + + RCTRedBoxExtraDataCell *cell = + (RCTRedBoxExtraDataCell *)[tableView + dequeueReusableCellWithIdentifier:reuseIdentifier]; + + if (cell == nil) { + cell = [[RCTRedBoxExtraDataCell alloc] + initWithStyle:UITableViewCellStyleDefault + reuseIdentifier:reuseIdentifier]; + } + + NSArray *dataKVPair = _extraData[indexPath.section][indexPath.row]; + cell.keyLabel.text = dataKVPair[0]; + cell.valueLabel.text = dataKVPair[1]; + + return cell; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return _extraDataTitle.count; +} + +- (void)addExtraData:(NSDictionary *)data forIdentifier:(NSString *)identifier +{ + dispatch_async(dispatch_get_main_queue(), ^{ + NSMutableArray *newData = [NSMutableArray new]; + for (id key in data) { + [newData addObject:@[[NSString stringWithFormat:@"%@", key], + [NSString stringWithFormat:@"%@", [data objectForKey:key]]]]; + } + + NSInteger idx = [self->_extraDataTitle indexOfObject:identifier]; + if (idx == NSNotFound) { + [self->_extraDataTitle addObject:identifier]; + [self->_extraData addObject:newData]; + } else { + [self->_extraData replaceObjectAtIndex:idx withObject:newData]; + } + + [self->_tableView reloadData]; + }); +} + +- (void)dismiss +{ + [self dismissViewControllerAnimated:YES completion:nil]; +} + +- (void)reload +{ + [_actionDelegate reload]; +} + +#pragma mark - Key commands + +- (NSArray *)keyCommands +{ + return @[ + // Dismiss + [UIKeyCommand keyCommandWithInput:UIKeyInputEscape + modifierFlags:0 + action:@selector(dismiss)], + // Reload + [UIKeyCommand keyCommandWithInput:@"r" + modifierFlags:UIKeyModifierCommand + action:@selector(reload)] + ]; +} + +@end diff --git a/React/React.xcodeproj/project.pbxproj b/React/React.xcodeproj/project.pbxproj index f67296a798cf9b..784a92e6a6bfd8 100644 --- a/React/React.xcodeproj/project.pbxproj +++ b/React/React.xcodeproj/project.pbxproj @@ -1192,6 +1192,8 @@ EBF21BFE1FC499840052F4D5 /* InspectorInterfaces.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = EBF21BBA1FC498270052F4D5 /* InspectorInterfaces.h */; }; EBF21BFF1FC4998E0052F4D5 /* InspectorInterfaces.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EBF21BBB1FC498270052F4D5 /* InspectorInterfaces.cpp */; }; EBF21C001FC499A80052F4D5 /* InspectorInterfaces.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EBF21BBB1FC498270052F4D5 /* InspectorInterfaces.cpp */; }; + FEFAAC9E1FDB89B50057BBE0 /* RCTRedBoxExtraDataViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FEFAAC9C1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.m */; }; + FEFAAC9F1FDB89B50057BBE0 /* RCTRedBoxExtraDataViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = FEFAAC9D1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -2268,6 +2270,8 @@ EBF21BBB1FC498270052F4D5 /* InspectorInterfaces.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorInterfaces.cpp; sourceTree = ""; }; EBF21BDC1FC498900052F4D5 /* libjsinspector.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libjsinspector.a; sourceTree = BUILT_PRODUCTS_DIR; }; EBF21BFA1FC4989A0052F4D5 /* libjsinspector-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libjsinspector-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + FEFAAC9C1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRedBoxExtraDataViewController.m; sourceTree = ""; }; + FEFAAC9D1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRedBoxExtraDataViewController.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2476,6 +2480,8 @@ 5960C1B41F0804A00066FD5B /* RCTLayoutAnimationGroup.m */, 13F17A831B8493E5007D4C75 /* RCTRedBox.h */, 13F17A841B8493E5007D4C75 /* RCTRedBox.m */, + FEFAAC9D1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.h */, + FEFAAC9C1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.m */, 000E6CE91AB0E97F000CDF4D /* RCTSourceCode.h */, 000E6CEA1AB0E980000CDF4D /* RCTSourceCode.m */, 13723B4E1A82FD3C00F88898 /* RCTStatusBarManager.h */, @@ -3464,6 +3470,7 @@ 3D80DA591DF820620028D040 /* RCTTiming.h in Headers */, 3D80DA5A1DF820620028D040 /* RCTUIManager.h in Headers */, 3D80DA5B1DF820620028D040 /* RCTFPSGraph.h in Headers */, + FEFAAC9F1FDB89B50057BBE0 /* RCTRedBoxExtraDataViewController.h in Headers */, 3D80DA5D1DF820620028D040 /* RCTMacros.h in Headers */, 3D80DA5E1DF820620028D040 /* RCTProfile.h in Headers */, 3D80DA5F1DF820620028D040 /* RCTActivityIndicatorView.h in Headers */, @@ -4410,6 +4417,7 @@ 1372B70A1AB030C200659ED6 /* RCTAppState.m in Sources */, 59A7B9FE1E577DBF0068EDBF /* RCTRootContentView.m in Sources */, 13E067591A70F44B002CDEE1 /* UIView+React.m in Sources */, + FEFAAC9E1FDB89B50057BBE0 /* RCTRedBoxExtraDataViewController.m in Sources */, 14F484561AABFCE100FDF6B9 /* RCTSliderManager.m in Sources */, CF2731C11E7B8DE40044CA4F /* RCTDeviceInfo.m in Sources */, 3D7AA9C41E548CD5001955CF /* NSDataBigString.mm in Sources */,