mirror of
https://github.com/status-im/react-native.git
synced 2025-02-25 07:35:25 +00:00
Add extra data view to RN RedBox
Reviewed By: shergin Differential Revision: D6382976 fbshipit-source-id: 33568a241395b085a840ac52adab3c9dc463ea4c
This commit is contained in:
parent
e8eec24706
commit
ee521f9c05
@ -35,6 +35,7 @@ class BugReporting {
|
||||
static _extraSources: Map<string, SourceCallback> = new Map();
|
||||
static _fileSources: Map<string, SourceCallback> = 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 };
|
||||
}
|
||||
}
|
||||
|
@ -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 @@
|
||||
|
||||
- (void)redBoxWindow:(RCTRedBoxWindow *)redBoxWindow openStackFrameInEditor:(RCTJSStackFrame *)stackFrame;
|
||||
- (void)reloadFromRedBoxWindow:(RCTRedBoxWindow *)redBoxWindow;
|
||||
- (void)loadExtraDataViewController;
|
||||
|
||||
@end
|
||||
|
||||
@ -33,308 +36,332 @@
|
||||
|
||||
@implementation RCTRedBoxWindow
|
||||
{
|
||||
UITableView *_stackTraceTableView;
|
||||
NSString *_lastErrorMessage;
|
||||
NSArray<RCTJSStackFrame *> *_lastStackTrace;
|
||||
UITableView *_stackTraceTableView;
|
||||
NSString *_lastErrorMessage;
|
||||
NSArray<RCTJSStackFrame *> *_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];
|
||||
_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
|
||||
#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
|
||||
|
||||
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 *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:14];
|
||||
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];
|
||||
[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];
|
||||
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];
|
||||
|
||||
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 *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<RCTJSStackFrame *> *)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];
|
||||
}
|
||||
|
||||
for (RCTJSStackFrame *stackFrame in _lastStackTrace) {
|
||||
[fullStackTrace appendString:[NSString stringWithFormat:@"%@\n", stackFrame.methodName]];
|
||||
if (stackFrame.file) {
|
||||
[fullStackTrace appendFormat:@" %@\n", [self formatFrameSource:stackFrame]];
|
||||
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]];
|
||||
}
|
||||
}
|
||||
}
|
||||
#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] : @"<unknown file>";
|
||||
NSString *lineInfo = [NSString stringWithFormat:@"%@:%lld",
|
||||
fileName,
|
||||
(long long)stackFrame.lineNumber];
|
||||
NSString *fileName = RCTNilIfNull(stackFrame.file) ? [stackFrame.file lastPathComponent] : @"<unknown file>";
|
||||
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];
|
||||
}
|
||||
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;
|
||||
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<UIKeyCommand *> *)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 () <RCTInvalidating, RCTRedBoxWindowActionDelegate>
|
||||
@interface RCTRedBox () <RCTInvalidating, RCTRedBoxWindowActionDelegate, RCTRedBoxExtraDataActionDelegate>
|
||||
@end
|
||||
|
||||
@implementation RCTRedBox
|
||||
{
|
||||
RCTRedBoxWindow *_window;
|
||||
NSMutableArray<id<RCTErrorCustomizer>> *_errorCustomizers;
|
||||
RCTRedBoxWindow *_window;
|
||||
NSMutableArray<id<RCTErrorCustomizer>> *_errorCustomizers;
|
||||
RCTRedBoxExtraDataViewController *_extraDataViewController;
|
||||
}
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
@ -343,140 +370,165 @@ RCT_EXPORT_MODULE()
|
||||
|
||||
- (void)registerErrorCustomizer:(id<RCTErrorCustomizer>)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<RCTErrorCustomizer> customizer in self->_errorCustomizers) {
|
||||
RCTErrorInfo *newInfo = [customizer customizeErrorInfo:error];
|
||||
if (newInfo) {
|
||||
error = newInfo;
|
||||
RCTAssertMainQueue();
|
||||
if (!self->_errorCustomizers) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
return error;
|
||||
for (id<RCTErrorCustomizer> 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<RCTJSStackFrame *> *)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<RCTJSStackFrame *> *stack = [RCTJSStackFrame stackFramesWithLines:rawStack];
|
||||
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
|
||||
NSArray<RCTJSStackFrame *> *stack = [RCTJSStackFrame stackFramesWithLines:rawStack];
|
||||
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withStack:(NSArray<NSDictionary *> *)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<NSDictionary *> *)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<RCTJSStackFrame *> *)stack
|
||||
{
|
||||
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
|
||||
[self showErrorMessage:message withParsedStack:stack isUpdate:NO];
|
||||
}
|
||||
|
||||
- (void)updateErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)stack
|
||||
{
|
||||
[self showErrorMessage:message withParsedStack:stack isUpdate:YES];
|
||||
[self showErrorMessage:message withParsedStack:stack isUpdate:YES];
|
||||
}
|
||||
|
||||
- (void)showErrorMessage:(NSString *)message withParsedStack:(NSArray<RCTJSStackFrame *> *)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 @@ RCT_EXPORT_METHOD(dismiss)
|
||||
|
||||
- (RCTRedBox *)redBox
|
||||
{
|
||||
return [self moduleForClass:[RCTRedBox class]];
|
||||
return [self moduleForClass:[RCTRedBox class]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
22
React/Modules/RCTRedBoxExtraDataViewController.h
Normal file
22
React/Modules/RCTRedBoxExtraDataViewController.h
Normal file
@ -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 <UIKit/UIKit.h>
|
||||
|
||||
@protocol RCTRedBoxExtraDataActionDelegate <NSObject>
|
||||
- (void)reload;
|
||||
@end
|
||||
|
||||
@interface RCTRedBoxExtraDataViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
|
||||
|
||||
@property (nonatomic, weak) id<RCTRedBoxExtraDataActionDelegate> actionDelegate;
|
||||
|
||||
- (void)addExtraData:(NSDictionary *)data forIdentifier:(NSString *)identifier;
|
||||
|
||||
@end
|
288
React/Modules/RCTRedBoxExtraDataViewController.m
Normal file
288
React/Modules/RCTRedBoxExtraDataViewController.m
Normal file
@ -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<UIKeyCommand *> *)keyCommands
|
||||
{
|
||||
return @[
|
||||
// Dismiss
|
||||
[UIKeyCommand keyCommandWithInput:UIKeyInputEscape
|
||||
modifierFlags:0
|
||||
action:@selector(dismiss)],
|
||||
// Reload
|
||||
[UIKeyCommand keyCommandWithInput:@"r"
|
||||
modifierFlags:UIKeyModifierCommand
|
||||
action:@selector(reload)]
|
||||
];
|
||||
}
|
||||
|
||||
@end
|
@ -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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
FEFAAC9D1FDB89B40057BBE0 /* RCTRedBoxExtraDataViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRedBoxExtraDataViewController.h; sourceTree = "<group>"; };
|
||||
/* 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 */,
|
||||
|
Loading…
x
Reference in New Issue
Block a user