126ad19fcSEvan Bacon/** 226ad19fcSEvan Bacon * Copyright (c) 650 Industries. 326ad19fcSEvan Bacon * Copyright (c) Meta Platforms, Inc. and affiliates. 426ad19fcSEvan Bacon * 526ad19fcSEvan Bacon * This source code is licensed under the MIT license found in the 626ad19fcSEvan Bacon * LICENSE file in the root directory of this source tree. 726ad19fcSEvan Bacon */ 826ad19fcSEvan Baconimport React, { useState } from 'react'; 926ad19fcSEvan Baconimport { StyleSheet, Text, View } from 'react-native'; 1026ad19fcSEvan Bacon 11*8a424bebSJames Ideimport { LogBoxInspectorSection } from './LogBoxInspectorSection'; 12*8a424bebSJames Ideimport { LogBoxInspectorSourceMapStatus } from './LogBoxInspectorSourceMapStatus'; 13*8a424bebSJames Ideimport { LogBoxInspectorStackFrame } from './LogBoxInspectorStackFrame'; 1426ad19fcSEvan Baconimport type { StackType } from '../Data/LogBoxLog'; 1526ad19fcSEvan Baconimport type { Stack } from '../Data/LogBoxSymbolication'; 1626ad19fcSEvan Baconimport { useSelectedLog } from '../Data/LogContext'; 1726ad19fcSEvan Baconimport { LogBoxButton } from '../UI/LogBoxButton'; 1826ad19fcSEvan Baconimport * as LogBoxStyle from '../UI/LogBoxStyle'; 1926ad19fcSEvan Baconimport openFileInEditor from '../modules/openFileInEditor'; 2026ad19fcSEvan Bacon 2126ad19fcSEvan Bacontype Props = { 2226ad19fcSEvan Bacon type: StackType; 2326ad19fcSEvan Bacon onRetry: () => void; 2426ad19fcSEvan Bacon}; 2526ad19fcSEvan Bacon 2626ad19fcSEvan Baconexport function getCollapseMessage(stackFrames: Stack, collapsed: boolean): string { 2726ad19fcSEvan Bacon if (stackFrames.length === 0) { 2826ad19fcSEvan Bacon return 'No frames to show'; 2926ad19fcSEvan Bacon } 3026ad19fcSEvan Bacon 3126ad19fcSEvan Bacon const collapsedCount = stackFrames.reduce((count, { collapse }) => { 3226ad19fcSEvan Bacon if (collapse === true) { 3326ad19fcSEvan Bacon return count + 1; 3426ad19fcSEvan Bacon } 3526ad19fcSEvan Bacon 3626ad19fcSEvan Bacon return count; 3726ad19fcSEvan Bacon }, 0); 3826ad19fcSEvan Bacon 3926ad19fcSEvan Bacon if (collapsedCount === 0) { 4026ad19fcSEvan Bacon return 'Showing all frames'; 4126ad19fcSEvan Bacon } 4226ad19fcSEvan Bacon 4326ad19fcSEvan Bacon const framePlural = `frame${collapsedCount > 1 ? 's' : ''}`; 4426ad19fcSEvan Bacon if (collapsedCount === stackFrames.length) { 4526ad19fcSEvan Bacon return collapsed 4626ad19fcSEvan Bacon ? `See${collapsedCount > 1 ? ' all ' : ' '}${collapsedCount} collapsed ${framePlural}` 4726ad19fcSEvan Bacon : `Collapse${collapsedCount > 1 ? ' all ' : ' '}${collapsedCount} ${framePlural}`; 4826ad19fcSEvan Bacon } else { 4926ad19fcSEvan Bacon return collapsed 5026ad19fcSEvan Bacon ? `See ${collapsedCount} more ${framePlural}` 5126ad19fcSEvan Bacon : `Collapse ${collapsedCount} ${framePlural}`; 5226ad19fcSEvan Bacon } 5326ad19fcSEvan Bacon} 5426ad19fcSEvan Bacon 5526ad19fcSEvan Baconexport function LogBoxInspectorStackFrames({ onRetry, type }: Props) { 5626ad19fcSEvan Bacon const log = useSelectedLog(); 5726ad19fcSEvan Bacon 5826ad19fcSEvan Bacon const [collapsed, setCollapsed] = useState(() => { 5926ad19fcSEvan Bacon // Only collapse frames initially if some frames are not collapsed. 6026ad19fcSEvan Bacon return log.getAvailableStack(type)?.some(({ collapse }) => !collapse); 6126ad19fcSEvan Bacon }); 6226ad19fcSEvan Bacon 6326ad19fcSEvan Bacon function getStackList() { 6426ad19fcSEvan Bacon if (collapsed === true) { 6526ad19fcSEvan Bacon return log.getAvailableStack(type)?.filter(({ collapse }) => !collapse); 6626ad19fcSEvan Bacon } else { 6726ad19fcSEvan Bacon return log.getAvailableStack(type); 6826ad19fcSEvan Bacon } 6926ad19fcSEvan Bacon } 7026ad19fcSEvan Bacon 7126ad19fcSEvan Bacon if (log.getAvailableStack(type)?.length === 0) { 7226ad19fcSEvan Bacon return null; 7326ad19fcSEvan Bacon } 7426ad19fcSEvan Bacon 7526ad19fcSEvan Bacon return ( 7626ad19fcSEvan Bacon <LogBoxInspectorSection 7726ad19fcSEvan Bacon heading={type === 'component' ? 'Component Stack' : 'Call Stack'} 7826ad19fcSEvan Bacon action={ 7926ad19fcSEvan Bacon <LogBoxInspectorSourceMapStatus 8026ad19fcSEvan Bacon onPress={log.symbolicated[type].status === 'FAILED' ? onRetry : null} 8126ad19fcSEvan Bacon status={log.symbolicated[type].status} 8226ad19fcSEvan Bacon /> 8326ad19fcSEvan Bacon }> 8426ad19fcSEvan Bacon {log.symbolicated[type].status !== 'COMPLETE' && ( 8526ad19fcSEvan Bacon <View style={stackStyles.hintBox}> 8626ad19fcSEvan Bacon <Text style={stackStyles.hintText}> 8726ad19fcSEvan Bacon This call stack is not symbolicated. Some features are unavailable such as viewing the 8826ad19fcSEvan Bacon function name or tapping to open files. 8926ad19fcSEvan Bacon </Text> 9026ad19fcSEvan Bacon </View> 9126ad19fcSEvan Bacon )} 9226ad19fcSEvan Bacon <StackFrameList list={getStackList()!} status={log.symbolicated[type].status} /> 9326ad19fcSEvan Bacon <StackFrameFooter 9426ad19fcSEvan Bacon onPress={() => setCollapsed(!collapsed)} 9526ad19fcSEvan Bacon message={getCollapseMessage(log.getAvailableStack(type)!, !!collapsed)} 9626ad19fcSEvan Bacon /> 9726ad19fcSEvan Bacon </LogBoxInspectorSection> 9826ad19fcSEvan Bacon ); 9926ad19fcSEvan Bacon} 10026ad19fcSEvan Bacon 10126ad19fcSEvan Baconfunction StackFrameList({ 10226ad19fcSEvan Bacon list, 10326ad19fcSEvan Bacon status, 10426ad19fcSEvan Bacon}: { 10526ad19fcSEvan Bacon list: Stack; 10626ad19fcSEvan Bacon status: 'NONE' | 'PENDING' | 'COMPLETE' | 'FAILED'; 10726ad19fcSEvan Bacon}): any { 10826ad19fcSEvan Bacon return list.map((frame, index) => { 10926ad19fcSEvan Bacon const { file, lineNumber } = frame; 11026ad19fcSEvan Bacon return ( 11126ad19fcSEvan Bacon <LogBoxInspectorStackFrame 11226ad19fcSEvan Bacon key={index} 11326ad19fcSEvan Bacon frame={frame} 11426ad19fcSEvan Bacon onPress={ 11526ad19fcSEvan Bacon status === 'COMPLETE' && file != null && lineNumber != null 11626ad19fcSEvan Bacon ? () => openFileInEditor(file, lineNumber) 11726ad19fcSEvan Bacon : undefined 11826ad19fcSEvan Bacon } 11926ad19fcSEvan Bacon /> 12026ad19fcSEvan Bacon ); 12126ad19fcSEvan Bacon }); 12226ad19fcSEvan Bacon} 12326ad19fcSEvan Bacon 12426ad19fcSEvan Baconfunction StackFrameFooter({ message, onPress }: { message: string; onPress: () => void }) { 12526ad19fcSEvan Bacon return ( 12626ad19fcSEvan Bacon <View style={stackStyles.collapseContainer}> 12726ad19fcSEvan Bacon <LogBoxButton 12826ad19fcSEvan Bacon backgroundColor={{ 12926ad19fcSEvan Bacon default: 'transparent', 13026ad19fcSEvan Bacon pressed: LogBoxStyle.getBackgroundColor(1), 13126ad19fcSEvan Bacon }} 13226ad19fcSEvan Bacon onPress={onPress} 13326ad19fcSEvan Bacon style={stackStyles.collapseButton}> 13426ad19fcSEvan Bacon <Text style={stackStyles.collapse}>{message}</Text> 13526ad19fcSEvan Bacon </LogBoxButton> 13626ad19fcSEvan Bacon </View> 13726ad19fcSEvan Bacon ); 13826ad19fcSEvan Bacon} 13926ad19fcSEvan Bacon 14026ad19fcSEvan Baconconst stackStyles = StyleSheet.create({ 14126ad19fcSEvan Bacon section: { 14226ad19fcSEvan Bacon marginTop: 15, 14326ad19fcSEvan Bacon }, 14426ad19fcSEvan Bacon heading: { 14526ad19fcSEvan Bacon alignItems: 'center', 14626ad19fcSEvan Bacon flexDirection: 'row', 14726ad19fcSEvan Bacon paddingHorizontal: 12, 14826ad19fcSEvan Bacon marginBottom: 10, 14926ad19fcSEvan Bacon }, 15026ad19fcSEvan Bacon headingText: { 15126ad19fcSEvan Bacon color: LogBoxStyle.getTextColor(1), 15226ad19fcSEvan Bacon flex: 1, 15326ad19fcSEvan Bacon fontSize: 20, 15426ad19fcSEvan Bacon fontWeight: '600', 15526ad19fcSEvan Bacon includeFontPadding: false, 15626ad19fcSEvan Bacon lineHeight: 20, 15726ad19fcSEvan Bacon }, 15826ad19fcSEvan Bacon body: { 15926ad19fcSEvan Bacon paddingBottom: 10, 16026ad19fcSEvan Bacon }, 16126ad19fcSEvan Bacon bodyText: { 16226ad19fcSEvan Bacon color: LogBoxStyle.getTextColor(1), 16326ad19fcSEvan Bacon fontSize: 14, 16426ad19fcSEvan Bacon includeFontPadding: false, 16526ad19fcSEvan Bacon lineHeight: 18, 16626ad19fcSEvan Bacon fontWeight: '500', 16726ad19fcSEvan Bacon paddingHorizontal: 27, 16826ad19fcSEvan Bacon }, 16926ad19fcSEvan Bacon hintText: { 17026ad19fcSEvan Bacon color: LogBoxStyle.getTextColor(0.7), 17126ad19fcSEvan Bacon fontSize: 13, 17226ad19fcSEvan Bacon includeFontPadding: false, 17326ad19fcSEvan Bacon lineHeight: 18, 17426ad19fcSEvan Bacon fontWeight: '400', 17526ad19fcSEvan Bacon marginHorizontal: 10, 17626ad19fcSEvan Bacon }, 17726ad19fcSEvan Bacon hintBox: { 17826ad19fcSEvan Bacon backgroundColor: LogBoxStyle.getBackgroundColor(), 17926ad19fcSEvan Bacon marginHorizontal: 10, 18026ad19fcSEvan Bacon paddingHorizontal: 5, 18126ad19fcSEvan Bacon paddingVertical: 10, 18226ad19fcSEvan Bacon borderRadius: 5, 18326ad19fcSEvan Bacon marginBottom: 5, 18426ad19fcSEvan Bacon }, 18526ad19fcSEvan Bacon collapseContainer: { 18626ad19fcSEvan Bacon marginLeft: 15, 18726ad19fcSEvan Bacon flexDirection: 'row', 18826ad19fcSEvan Bacon }, 18926ad19fcSEvan Bacon collapseButton: { 19026ad19fcSEvan Bacon borderRadius: 5, 19126ad19fcSEvan Bacon }, 19226ad19fcSEvan Bacon collapse: { 19326ad19fcSEvan Bacon color: LogBoxStyle.getTextColor(0.7), 19426ad19fcSEvan Bacon fontSize: 12, 19526ad19fcSEvan Bacon fontWeight: '300', 19626ad19fcSEvan Bacon lineHeight: 20, 19726ad19fcSEvan Bacon marginTop: 0, 19826ad19fcSEvan Bacon paddingHorizontal: 10, 19926ad19fcSEvan Bacon paddingVertical: 5, 20026ad19fcSEvan Bacon }, 20126ad19fcSEvan Bacon}); 202