This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
532 lines
14 KiB
532 lines
14 KiB
/** |
|
* Copyright (c) 2015-present, Facebook, Inc. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
* |
|
* @providesModule YellowBox |
|
* @flow |
|
* @format |
|
*/ |
|
|
|
'use strict'; |
|
|
|
const EventEmitter = require('EventEmitter'); |
|
const Platform = require('Platform'); |
|
const React = require('React'); |
|
const SafeAreaView = require('SafeAreaView'); |
|
const StyleSheet = require('StyleSheet'); |
|
const RCTLog = require('RCTLog'); |
|
|
|
const infoLog = require('infoLog'); |
|
const openFileInEditor = require('openFileInEditor'); |
|
const parseErrorStack = require('parseErrorStack'); |
|
const stringifySafe = require('stringifySafe'); |
|
const symbolicateStackTrace = require('symbolicateStackTrace'); |
|
|
|
import type EmitterSubscription from 'EmitterSubscription'; |
|
import type {StackFrame} from 'parseErrorStack'; |
|
|
|
type WarningInfo = { |
|
count: number, |
|
stacktrace: Array<StackFrame>, |
|
symbolicated: boolean, |
|
}; |
|
|
|
const _warningEmitter = new EventEmitter(); |
|
const _warningMap: Map<string, WarningInfo> = new Map(); |
|
const IGNORED_WARNINGS: Array<string> = []; |
|
|
|
/** |
|
* YellowBox renders warnings at the bottom of the app being developed. |
|
* |
|
* Warnings help guard against subtle yet significant issues that can impact the |
|
* quality of the app. This "in your face" style of warning allows developers to |
|
* notice and correct these issues as quickly as possible. |
|
* |
|
* By default, the warning box is enabled in `__DEV__`. Set the following flag |
|
* to disable it (and call `console.warn` to update any rendered <YellowBox>): |
|
* |
|
* console.disableYellowBox = true; |
|
* console.warn('YellowBox is disabled.'); |
|
* |
|
* Ignore specific warnings by calling: |
|
* |
|
* YellowBox.ignoreWarnings(['Warning: ...']); |
|
* |
|
* (DEPRECATED) Warnings can be ignored programmatically by setting the array: |
|
* |
|
* console.ignoredYellowBox = ['Warning: ...']; |
|
* |
|
* Strings in `console.ignoredYellowBox` can be a prefix of the warning that |
|
* should be ignored. |
|
*/ |
|
|
|
if (__DEV__) { |
|
const {error, warn} = console; |
|
|
|
(console: any).error = function() { |
|
error.apply(console, arguments); |
|
// Show yellow box for the `warning` module. |
|
if ( |
|
typeof arguments[0] === 'string' && |
|
arguments[0].startsWith('Warning: ') |
|
) { |
|
updateWarningMap.apply(null, arguments); |
|
} |
|
}; |
|
|
|
(console: any).warn = function() { |
|
warn.apply(console, arguments); |
|
updateWarningMap.apply(null, arguments); |
|
}; |
|
|
|
if (Platform.isTesting) { |
|
(console: any).disableYellowBox = true; |
|
} |
|
|
|
RCTLog.setWarningHandler((...args) => { |
|
updateWarningMap.apply(null, args); |
|
}); |
|
} |
|
|
|
/** |
|
* Simple function for formatting strings. |
|
* |
|
* Replaces placeholders with values passed as extra arguments |
|
* |
|
* @param {string} format the base string |
|
* @param ...args the values to insert |
|
* @return {string} the replaced string |
|
*/ |
|
function sprintf(format, ...args) { |
|
let index = 0; |
|
return format.replace(/%s/g, match => args[index++]); |
|
} |
|
|
|
function updateWarningMap(...args): void { |
|
if (console.disableYellowBox) { |
|
return; |
|
} |
|
|
|
let warning; |
|
if (typeof args[0] === 'string') { |
|
const [format, ...formatArgs] = args; |
|
const argCount = (format.match(/%s/g) || []).length; |
|
warning = [ |
|
sprintf(format, ...formatArgs.slice(0, argCount).map(stringifySafe)), |
|
...formatArgs.slice(argCount).map(stringifySafe), |
|
].join(' '); |
|
} else { |
|
warning = args.map(stringifySafe).join(' '); |
|
} |
|
|
|
if (warning.startsWith('(ADVICE)')) { |
|
return; |
|
} |
|
|
|
const warningInfo = _warningMap.get(warning); |
|
if (warningInfo) { |
|
warningInfo.count += 1; |
|
} else { |
|
const error: any = new Error(); |
|
error.framesToPop = 2; |
|
|
|
_warningMap.set(warning, { |
|
count: 1, |
|
stacktrace: parseErrorStack(error), |
|
symbolicated: false, |
|
}); |
|
} |
|
|
|
_warningEmitter.emit('warning', _warningMap); |
|
} |
|
|
|
function ensureSymbolicatedWarning(warning: string): void { |
|
const prevWarningInfo = _warningMap.get(warning); |
|
if (!prevWarningInfo || prevWarningInfo.symbolicated) { |
|
return; |
|
} |
|
prevWarningInfo.symbolicated = true; |
|
|
|
symbolicateStackTrace(prevWarningInfo.stacktrace).then( |
|
stack => { |
|
const nextWarningInfo = _warningMap.get(warning); |
|
if (nextWarningInfo) { |
|
nextWarningInfo.stacktrace = stack; |
|
_warningEmitter.emit('warning', _warningMap); |
|
} |
|
}, |
|
error => { |
|
const nextWarningInfo = _warningMap.get(warning); |
|
if (nextWarningInfo) { |
|
infoLog('Failed to symbolicate warning, "%s":', warning, error); |
|
_warningEmitter.emit('warning', _warningMap); |
|
} |
|
}, |
|
); |
|
} |
|
|
|
function isWarningIgnored(warning: string): boolean { |
|
const isIgnored = IGNORED_WARNINGS.some((ignoredWarning: string) => |
|
warning.startsWith(ignoredWarning), |
|
); |
|
|
|
if (isIgnored) { |
|
return true; |
|
} |
|
|
|
// DEPRECATED |
|
return ( |
|
Array.isArray(console.ignoredYellowBox) && |
|
console.ignoredYellowBox.some(ignorePrefix => |
|
warning.startsWith(String(ignorePrefix)), |
|
) |
|
); |
|
} |
|
|
|
const WarningRow = ({count, warning, onPress}) => { |
|
const Text = require('Text'); |
|
const TouchableHighlight = require('TouchableHighlight'); |
|
const View = require('View'); |
|
|
|
const countText = |
|
count > 1 ? ( |
|
<Text style={styles.listRowCount}>{'(' + count + ') '}</Text> |
|
) : null; |
|
|
|
return ( |
|
<View style={styles.listRow}> |
|
<TouchableHighlight |
|
activeOpacity={0.5} |
|
onPress={onPress} |
|
style={styles.listRowContent} |
|
underlayColor="transparent"> |
|
<Text style={styles.listRowText} numberOfLines={2}> |
|
{countText} |
|
{warning} |
|
</Text> |
|
</TouchableHighlight> |
|
</View> |
|
); |
|
}; |
|
|
|
type StackRowProps = {frame: StackFrame}; |
|
const StackRow = ({frame}: StackRowProps) => { |
|
const Text = require('Text'); |
|
const TouchableHighlight = require('TouchableHighlight'); |
|
const {file, lineNumber} = frame; |
|
let fileName; |
|
if (file) { |
|
const fileParts = file.split('/'); |
|
fileName = fileParts[fileParts.length - 1]; |
|
} else { |
|
fileName = '<unknown file>'; |
|
} |
|
|
|
return ( |
|
<TouchableHighlight |
|
activeOpacity={0.5} |
|
style={styles.openInEditorButton} |
|
underlayColor="transparent" |
|
onPress={openFileInEditor.bind(null, file, lineNumber)}> |
|
<Text style={styles.inspectorCountText}> |
|
{fileName}:{lineNumber} |
|
</Text> |
|
</TouchableHighlight> |
|
); |
|
}; |
|
|
|
const WarningInspector = ({ |
|
warningInfo, |
|
warning, |
|
stacktraceVisible, |
|
onDismiss, |
|
onDismissAll, |
|
onMinimize, |
|
toggleStacktrace, |
|
}) => { |
|
const ScrollView = require('ScrollView'); |
|
const Text = require('Text'); |
|
const TouchableHighlight = require('TouchableHighlight'); |
|
const View = require('View'); |
|
const {count, stacktrace} = warningInfo || {}; |
|
|
|
const countSentence = |
|
'Warning encountered ' + count + ' time' + (count - 1 ? 's' : '') + '.'; |
|
|
|
let stacktraceList; |
|
if (stacktraceVisible && stacktrace) { |
|
stacktraceList = ( |
|
<View style={styles.stacktraceList}> |
|
{stacktrace.map((frame, ii) => <StackRow frame={frame} key={ii} />)} |
|
</View> |
|
); |
|
} |
|
|
|
return ( |
|
<View style={styles.inspector}> |
|
<SafeAreaView style={styles.safeArea}> |
|
<View style={styles.inspectorCount}> |
|
<Text style={styles.inspectorCountText}>{countSentence}</Text> |
|
<TouchableHighlight |
|
onPress={toggleStacktrace} |
|
underlayColor="transparent"> |
|
<Text style={styles.inspectorButtonText}> |
|
{stacktraceVisible ? '\u{25BC}' : '\u{25B6}'} Stacktrace |
|
</Text> |
|
</TouchableHighlight> |
|
</View> |
|
<ScrollView style={styles.inspectorWarning}> |
|
{stacktraceList} |
|
<Text style={styles.inspectorWarningText}>{warning}</Text> |
|
</ScrollView> |
|
<View style={styles.inspectorButtons}> |
|
<TouchableHighlight |
|
activeOpacity={0.5} |
|
onPress={onMinimize} |
|
style={styles.inspectorButton} |
|
underlayColor="transparent"> |
|
<Text style={styles.inspectorButtonText}>Minimize</Text> |
|
</TouchableHighlight> |
|
<TouchableHighlight |
|
activeOpacity={0.5} |
|
onPress={onDismiss} |
|
style={styles.inspectorButton} |
|
underlayColor="transparent"> |
|
<Text style={styles.inspectorButtonText}>Dismiss</Text> |
|
</TouchableHighlight> |
|
<TouchableHighlight |
|
activeOpacity={0.5} |
|
onPress={onDismissAll} |
|
style={styles.inspectorButton} |
|
underlayColor="transparent"> |
|
<Text style={styles.inspectorButtonText}>Dismiss All</Text> |
|
</TouchableHighlight> |
|
</View> |
|
</SafeAreaView> |
|
</View> |
|
); |
|
}; |
|
|
|
class YellowBox extends React.Component< |
|
mixed, |
|
{ |
|
stacktraceVisible: boolean, |
|
inspecting: ?string, |
|
warningMap: Map<any, any>, |
|
}, |
|
> { |
|
_listener: ?EmitterSubscription; |
|
dismissWarning: (warning: ?string) => void; |
|
|
|
constructor(props: mixed, context: mixed) { |
|
super(props, context); |
|
this.state = { |
|
inspecting: null, |
|
stacktraceVisible: false, |
|
warningMap: _warningMap, |
|
}; |
|
this.dismissWarning = warning => { |
|
const {inspecting, warningMap} = this.state; |
|
if (warning) { |
|
warningMap.delete(warning); |
|
} else { |
|
warningMap.clear(); |
|
} |
|
this.setState({ |
|
inspecting: warning && inspecting !== warning ? inspecting : null, |
|
warningMap, |
|
}); |
|
}; |
|
} |
|
|
|
static ignoreWarnings(warnings: Array<string>): void { |
|
warnings.forEach((warning: string) => { |
|
if (IGNORED_WARNINGS.indexOf(warning) === -1) { |
|
IGNORED_WARNINGS.push(warning); |
|
} |
|
}); |
|
} |
|
|
|
componentDidMount() { |
|
let scheduled = null; |
|
this._listener = _warningEmitter.addListener('warning', warningMap => { |
|
// Use `setImmediate` because warnings often happen during render, but |
|
// state cannot be set while rendering. |
|
scheduled = |
|
scheduled || |
|
setImmediate(() => { |
|
scheduled = null; |
|
this.setState({ |
|
warningMap, |
|
}); |
|
}); |
|
}); |
|
} |
|
|
|
componentDidUpdate() { |
|
const {inspecting} = this.state; |
|
if (inspecting != null) { |
|
ensureSymbolicatedWarning(inspecting); |
|
} |
|
} |
|
|
|
componentWillUnmount() { |
|
if (this._listener) { |
|
this._listener.remove(); |
|
} |
|
} |
|
|
|
render() { |
|
if (console.disableYellowBox || this.state.warningMap.size === 0) { |
|
return null; |
|
} |
|
const ScrollView = require('ScrollView'); |
|
const View = require('View'); |
|
|
|
const {inspecting, stacktraceVisible} = this.state; |
|
const inspector = |
|
inspecting !== null ? ( |
|
<WarningInspector |
|
warningInfo={this.state.warningMap.get(inspecting)} |
|
warning={inspecting} |
|
stacktraceVisible={stacktraceVisible} |
|
onDismiss={() => this.dismissWarning(inspecting)} |
|
onDismissAll={() => this.dismissWarning(null)} |
|
onMinimize={() => this.setState({inspecting: null})} |
|
toggleStacktrace={() => |
|
this.setState({stacktraceVisible: !stacktraceVisible}) |
|
} |
|
/> |
|
) : null; |
|
|
|
const rows = []; |
|
this.state.warningMap.forEach((warningInfo, warning) => { |
|
if (!isWarningIgnored(warning)) { |
|
rows.push( |
|
<WarningRow |
|
key={warning} |
|
count={warningInfo.count} |
|
warning={warning} |
|
onPress={() => this.setState({inspecting: warning})} |
|
onDismiss={() => this.dismissWarning(warning)} |
|
/>, |
|
); |
|
} |
|
}); |
|
|
|
const listStyle = [ |
|
styles.list, |
|
// Additional `0.4` so the 5th row can peek into view. |
|
{height: Math.min(rows.length, 4.4) * (rowGutter + rowHeight)}, |
|
]; |
|
return ( |
|
<View style={inspector ? styles.fullScreen : listStyle}> |
|
<ScrollView style={listStyle} scrollsToTop={false}> |
|
{rows} |
|
</ScrollView> |
|
{inspector} |
|
</View> |
|
); |
|
} |
|
} |
|
|
|
const backgroundColor = opacity => 'rgba(250, 186, 48, ' + opacity + ')'; |
|
const textColor = 'white'; |
|
const rowGutter = 1; |
|
const rowHeight = 46; |
|
|
|
// For unknown reasons, setting elevation: Number.MAX_VALUE causes remote debugging to |
|
// hang on iOS (some sort of overflow maybe). Setting it to Number.MAX_SAFE_INTEGER fixes the iOS issue, but since |
|
// elevation is an android-only style property we might as well remove it altogether for iOS. |
|
// See: https://github.com/facebook/react-native/issues/12223 |
|
const elevation = |
|
Platform.OS === 'android' ? Number.MAX_SAFE_INTEGER : undefined; |
|
|
|
var styles = StyleSheet.create({ |
|
fullScreen: { |
|
height: '100%', |
|
width: '100%', |
|
elevation: elevation, |
|
position: 'absolute', |
|
}, |
|
inspector: { |
|
backgroundColor: backgroundColor(0.95), |
|
height: '100%', |
|
paddingTop: 5, |
|
elevation: elevation, |
|
}, |
|
inspectorButtons: { |
|
flexDirection: 'row', |
|
}, |
|
inspectorButton: { |
|
flex: 1, |
|
paddingVertical: 22, |
|
backgroundColor: backgroundColor(1), |
|
}, |
|
safeArea: { |
|
flex: 1, |
|
}, |
|
stacktraceList: { |
|
paddingBottom: 5, |
|
}, |
|
inspectorButtonText: { |
|
color: textColor, |
|
fontSize: 14, |
|
opacity: 0.8, |
|
textAlign: 'center', |
|
}, |
|
openInEditorButton: { |
|
paddingTop: 5, |
|
paddingBottom: 5, |
|
}, |
|
inspectorCount: { |
|
padding: 15, |
|
paddingBottom: 0, |
|
flexDirection: 'row', |
|
justifyContent: 'space-between', |
|
}, |
|
inspectorCountText: { |
|
color: textColor, |
|
fontSize: 14, |
|
}, |
|
inspectorWarning: { |
|
flex: 1, |
|
paddingHorizontal: 15, |
|
}, |
|
inspectorWarningText: { |
|
color: textColor, |
|
fontSize: 16, |
|
fontWeight: '600', |
|
}, |
|
list: { |
|
backgroundColor: 'transparent', |
|
position: 'absolute', |
|
left: 0, |
|
right: 0, |
|
bottom: 0, |
|
elevation: elevation, |
|
}, |
|
listRow: { |
|
backgroundColor: backgroundColor(0.95), |
|
height: rowHeight, |
|
marginTop: rowGutter, |
|
}, |
|
listRowContent: { |
|
flex: 1, |
|
}, |
|
listRowCount: { |
|
color: 'rgba(255, 255, 255, 0.5)', |
|
}, |
|
listRowText: { |
|
color: textColor, |
|
position: 'absolute', |
|
left: 0, |
|
top: Platform.OS === 'android' ? 5 : 7, |
|
marginLeft: 15, |
|
marginRight: 15, |
|
}, |
|
}); |
|
|
|
module.exports = YellowBox;
|
|
|