Updates from Tue 7 Apr
- [AdsManager] Correct back button functionality | Eric Vicenti - [ReactNative] Replace Backstack with BackAndroid | Eric Vicenti - [ReactNative] Better error message for EADDRINUSE | Alex Kotliarskyi - [ReactNative] npm install --save chalk | Alex Kotliarskyi - Removed redundant views and shadow views | Nick Lockwood - [ReactNative] Fix variable shadowing in RCTText | Tadeu Zagallo - [react-packager] check in image-size module | Amjad Masad - Refactored RCTLog and added facility to prepend extra data to the log message | Nick Lockwood - [ReactNative] Fix crash on image download | Tadeu Zagallo - [React Native] #WIP Modify RCTShadowText measure function to honor maxNumberOfLines property | Alex Akers - Add promise support to AsyncStorage | Spencer Ahrens - [ReactNative] Revert high-level Subscribable | Eric Vicenti - [ReactNative] wrong deprecated prop check in RCTConvert | Kevin Gozali - [ReactNative][MAdMan] Add type for image source, flowify AdsManagerObjectiveTypes | Philipp von Weitershausen
This commit is contained in:
parent
b1d3e442c8
commit
3f6943c9ab
|
@ -29,16 +29,17 @@ var COLORS = ['red', 'orange', 'yellow', 'green', 'blue'];
|
||||||
|
|
||||||
var BasicStorageExample = React.createClass({
|
var BasicStorageExample = React.createClass({
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
AsyncStorage.getItem(STORAGE_KEY, (error, value) => {
|
AsyncStorage.getItem(STORAGE_KEY)
|
||||||
if (error) {
|
.then((value) => {
|
||||||
this._appendMessage('AsyncStorage error: ' + error.message);
|
if (value !== null){
|
||||||
} else if (value !== null) {
|
this.setState({selectedValue: value});
|
||||||
this.setState({selectedValue: value});
|
this._appendMessage('Recovered selection from disk: ' + value);
|
||||||
this._appendMessage('Recovered selection from disk: ' + value);
|
} else {
|
||||||
} else {
|
this._appendMessage('Initialized with no selection on disk.');
|
||||||
this._appendMessage('Initialized with no selection on disk.');
|
}
|
||||||
}
|
})
|
||||||
});
|
.catch((error) => this._appendMessage('AsyncStorage error: ' + error.message))
|
||||||
|
.done();
|
||||||
},
|
},
|
||||||
getInitialState() {
|
getInitialState() {
|
||||||
return {
|
return {
|
||||||
|
@ -81,23 +82,17 @@ var BasicStorageExample = React.createClass({
|
||||||
|
|
||||||
_onValueChange(selectedValue) {
|
_onValueChange(selectedValue) {
|
||||||
this.setState({selectedValue});
|
this.setState({selectedValue});
|
||||||
AsyncStorage.setItem(STORAGE_KEY, selectedValue, (error) => {
|
AsyncStorage.setItem(STORAGE_KEY, selectedValue)
|
||||||
if (error) {
|
.then(() => this._appendMessage('Saved selection to disk: ' + selectedValue))
|
||||||
this._appendMessage('AsyncStorage error: ' + error.message);
|
.catch((error) => this._appendMessage('AsyncStorage error: ' + error.message))
|
||||||
} else {
|
.done();
|
||||||
this._appendMessage('Saved selection to disk: ' + selectedValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_removeStorage() {
|
_removeStorage() {
|
||||||
AsyncStorage.removeItem(STORAGE_KEY, (error) => {
|
AsyncStorage.removeItem(STORAGE_KEY)
|
||||||
if (error) {
|
.then(() => this._appendMessage('Selection removed from disk.'))
|
||||||
this._appendMessage('AsyncStorage error: ' + error.message);
|
.catch((error) => { this._appendMessage('AsyncStorage error: ' + error.message) })
|
||||||
} else {
|
.done();
|
||||||
this._appendMessage('Selection removed from disk.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_appendMessage(message) {
|
_appendMessage(message) {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
"scale" : "2x",
|
"scale" : "2x",
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
|
|
||||||
UIView *view = viewRegistry[reactTag];
|
UIView *view = viewRegistry[reactTag];
|
||||||
if (!view) {
|
if (!view) {
|
||||||
RCTLogWarn(@"React tag %@ is not registered with the view registry", reactTag);
|
RCTLogWarn(@"React tag #%@ is not registered with the view registry", reactTag);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,308 +11,25 @@
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribable wraps EventEmitter in a clean interface, and provides a mixin
|
|
||||||
* so components can easily subscribe to events and not worry about cleanup on
|
|
||||||
* unmount.
|
|
||||||
*
|
|
||||||
* Also acts as a basic store because it records the last data that it emitted,
|
|
||||||
* and provides a way to populate the initial data. The most recent data can be
|
|
||||||
* fetched from the Subscribable by calling `get()`
|
|
||||||
*
|
|
||||||
* Advantages over EventEmitter + Subscibable.Mixin.addListenerOn:
|
|
||||||
* - Cleaner usage: no strings to identify the event
|
|
||||||
* - Lifespan pattern enforces cleanup
|
|
||||||
* - More logical: Subscribable.Mixin now uses a Subscribable class
|
|
||||||
* - Subscribable saves the last data and makes it available with `.get()`
|
|
||||||
*
|
|
||||||
* Legacy Subscribable.Mixin.addListenerOn allowed automatic subscription to
|
|
||||||
* EventEmitters. Now we should avoid EventEmitters and wrap with Subscribable
|
|
||||||
* instead:
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* AppState.networkReachability = new Subscribable(
|
|
||||||
* RCTDeviceEventEmitter,
|
|
||||||
* 'reachabilityDidChange',
|
|
||||||
* (resp) => resp.network_reachability,
|
|
||||||
* RCTReachability.getCurrentReachability
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* var myComponent = React.createClass({
|
|
||||||
* mixins: [Subscribable.Mixin],
|
|
||||||
* getInitialState: function() {
|
|
||||||
* return {
|
|
||||||
* isConnected: AppState.networkReachability.get() !== 'none'
|
|
||||||
* };
|
|
||||||
* },
|
|
||||||
* componentDidMount: function() {
|
|
||||||
* this._reachSubscription = this.subscribeTo(
|
|
||||||
* AppState.networkReachability,
|
|
||||||
* (reachability) => {
|
|
||||||
* this.setState({ isConnected: reachability !== 'none' })
|
|
||||||
* }
|
|
||||||
* );
|
|
||||||
* },
|
|
||||||
* render: function() {
|
|
||||||
* return (
|
|
||||||
* <Text>
|
|
||||||
* {this.state.isConnected ? 'Network Connected' : 'No network'}
|
|
||||||
* </Text>
|
|
||||||
* <Text onPress={() => this._reachSubscription.remove()}>
|
|
||||||
* End reachability subscription
|
|
||||||
* </Text>
|
|
||||||
* );
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
|
|
||||||
var EventEmitter = require('EventEmitter');
|
var EventEmitter = require('EventEmitter');
|
||||||
|
|
||||||
var invariant = require('invariant');
|
/**
|
||||||
var logError = require('logError');
|
* Subscribable provides a mixin for safely subscribing a component to an
|
||||||
|
* eventEmitter
|
||||||
var SUBSCRIBABLE_INTERNAL_EVENT = 'subscriptionEvent';
|
*
|
||||||
|
* This will be replaced with the observe interface that will be coming soon to
|
||||||
type Data = Object;
|
* React Core
|
||||||
type EventMapping = (_: Data) => Data;
|
*/
|
||||||
|
|
||||||
class Subscribable {
|
|
||||||
_eventMapping: EventMapping;
|
|
||||||
_lastData: Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Subscribable object
|
|
||||||
*
|
|
||||||
* @param {EventEmitter} eventEmitter Emitter to trigger subscription events.
|
|
||||||
* @param {string} eventName Name of emitted event that triggers subscription
|
|
||||||
* events.
|
|
||||||
* @param {function} eventMapping (optional) Function to convert the output
|
|
||||||
* of the eventEmitter to the subscription output.
|
|
||||||
* @param {function} getInitData (optional) Async function to grab the initial
|
|
||||||
* data to publish. Signature `function(successCallback, errorCallback)`.
|
|
||||||
* The resolved data will be transformed with the eventMapping before it
|
|
||||||
* gets emitted.
|
|
||||||
*/
|
|
||||||
constructor(eventEmitter: EventEmitter, eventName: string, eventMapping?: EventMapping, getInitData?: Function) {
|
|
||||||
|
|
||||||
this._internalEmitter = new EventEmitter();
|
|
||||||
this._eventMapping = eventMapping || (data => data);
|
|
||||||
|
|
||||||
this._upstreamSubscription = eventEmitter.addListener(
|
|
||||||
eventName,
|
|
||||||
this._handleEmit,
|
|
||||||
this
|
|
||||||
);
|
|
||||||
|
|
||||||
// Asyncronously get the initial data, if provided
|
|
||||||
getInitData && getInitData(this._handleInitData.bind(this), logError);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the last data emitted from the Subscribable, or undefined
|
|
||||||
*/
|
|
||||||
get(): Data {
|
|
||||||
return this._lastData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsubscribe from the upstream EventEmitter
|
|
||||||
*/
|
|
||||||
cleanup() {
|
|
||||||
this._upstreamSubscription && this._upstreamSubscription.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a new listener to the subscribable. This should almost never be used
|
|
||||||
* directly, and instead through Subscribable.Mixin.subscribeTo
|
|
||||||
*
|
|
||||||
* @param {object} lifespan Object with `addUnmountCallback` that accepts
|
|
||||||
* a handler to be called when the component unmounts. This is required and
|
|
||||||
* desirable because it enforces cleanup. There is no easy way to leave the
|
|
||||||
* subsciption hanging
|
|
||||||
* {
|
|
||||||
* addUnmountCallback: function(newUnmountHanlder) {...},
|
|
||||||
* }
|
|
||||||
* @param {function} callback Handler to call when Subscribable has data
|
|
||||||
* updates
|
|
||||||
* @param {object} context Object to bind the handler on, as "this"
|
|
||||||
*
|
|
||||||
* @return {object} the subscription object:
|
|
||||||
* {
|
|
||||||
* remove: function() {...},
|
|
||||||
* }
|
|
||||||
* Call `remove` to terminate the subscription before unmounting
|
|
||||||
*/
|
|
||||||
subscribe(lifespan: { addUnmountCallback: Function }, callback: Function, context: Object) {
|
|
||||||
invariant(
|
|
||||||
typeof lifespan.addUnmountCallback === 'function',
|
|
||||||
'Must provide a valid lifespan, which provides a way to add a ' +
|
|
||||||
'callback for when subscription can be cleaned up. This is used ' +
|
|
||||||
'automatically by Subscribable.Mixin'
|
|
||||||
);
|
|
||||||
invariant(
|
|
||||||
typeof callback === 'function',
|
|
||||||
'Must provide a valid subscription handler.'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add a listener to the internal EventEmitter
|
|
||||||
var subscription = this._internalEmitter.addListener(
|
|
||||||
SUBSCRIBABLE_INTERNAL_EVENT,
|
|
||||||
callback,
|
|
||||||
context
|
|
||||||
);
|
|
||||||
|
|
||||||
// Clean up subscription upon the lifespan unmount callback
|
|
||||||
lifespan.addUnmountCallback(() => {
|
|
||||||
subscription.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
return subscription;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for the initial data resolution. Currently behaves the same as
|
|
||||||
* `_handleEmit`, but we may eventually want to keep track of the difference
|
|
||||||
*/
|
|
||||||
_handleInitData(dataInput: Data) {
|
|
||||||
var emitData = this._eventMapping(dataInput);
|
|
||||||
this._lastData = emitData;
|
|
||||||
this._internalEmitter.emit(SUBSCRIBABLE_INTERNAL_EVENT, emitData);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle new data emissions. Pass the data through our eventMapping
|
|
||||||
* transformation, store it for later `get()`ing, and emit it for subscribers
|
|
||||||
*/
|
|
||||||
_handleEmit(dataInput: Data) {
|
|
||||||
var emitData = this._eventMapping(dataInput);
|
|
||||||
this._lastData = emitData;
|
|
||||||
this._internalEmitter.emit(SUBSCRIBABLE_INTERNAL_EVENT, emitData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var Subscribable = {};
|
||||||
|
|
||||||
Subscribable.Mixin = {
|
Subscribable.Mixin = {
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {object} lifespan Object with `addUnmountCallback` that accepts
|
|
||||||
* a handler to be called when the component unmounts
|
|
||||||
* {
|
|
||||||
* addUnmountCallback: function(newUnmountHanlder) {...},
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
_getSubscribableLifespan: function() {
|
|
||||||
if (!this._subscribableLifespan) {
|
|
||||||
this._subscribableLifespan = {
|
|
||||||
addUnmountCallback: (cb) => {
|
|
||||||
this._endSubscribableLifespanCallbacks.push(cb);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return this._subscribableLifespan;
|
|
||||||
},
|
|
||||||
|
|
||||||
_endSubscribableLifespan: function() {
|
|
||||||
this._endSubscribableLifespanCallbacks.forEach(cb => cb());
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Components use `subscribeTo` for listening to Subscribable stores. Cleanup
|
|
||||||
* is automatic on component unmount.
|
|
||||||
*
|
|
||||||
* To stop listening to the subscribable and end the subscription early,
|
|
||||||
* components should store the returned subscription object and invoke the
|
|
||||||
* `remove()` function on it
|
|
||||||
*
|
|
||||||
* @param {Subscribable} subscription to subscribe to.
|
|
||||||
* @param {function} listener Function to invoke when event occurs.
|
|
||||||
* @param {object} context Object to bind the handler on, as "this"
|
|
||||||
*
|
|
||||||
* @return {object} the subscription object:
|
|
||||||
* {
|
|
||||||
* remove: function() {...},
|
|
||||||
* }
|
|
||||||
* Call `remove` to terminate the subscription before unmounting
|
|
||||||
*/
|
|
||||||
subscribeTo: function(subscribable, handler, context) {
|
|
||||||
invariant(
|
|
||||||
subscribable instanceof Subscribable,
|
|
||||||
'Must provide a Subscribable'
|
|
||||||
);
|
|
||||||
return subscribable.subscribe(
|
|
||||||
this._getSubscribableLifespan(),
|
|
||||||
handler,
|
|
||||||
context
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a Subscribable store, scoped to the component, that can be passed to
|
|
||||||
* children. The component will automatically clean up the subscribable's
|
|
||||||
* subscription to the eventEmitter when unmounting.
|
|
||||||
*
|
|
||||||
* `provideSubscribable` will always return the same Subscribable for any
|
|
||||||
* particular emitter/eventName combo, so it can be called directly from
|
|
||||||
* render, and it will never create duplicate Subscribables.
|
|
||||||
*
|
|
||||||
* @param {EventEmitter} eventEmitter Emitter to trigger subscription events.
|
|
||||||
* @param {string} eventName Name of emitted event that triggers subscription
|
|
||||||
* events.
|
|
||||||
* @param {function} eventMapping (optional) Function to convert the output
|
|
||||||
* of the eventEmitter to the subscription output.
|
|
||||||
* @param {function} getInitData (optional) Async function to grab the initial
|
|
||||||
* data to publish. Signature `function(successCallback, errorCallback)`.
|
|
||||||
* The resolved data will be transformed with the eventMapping before it
|
|
||||||
* gets emitted.
|
|
||||||
*/
|
|
||||||
provideSubscribable: function(eventEmitter, eventName, eventMapping, getInitData) {
|
|
||||||
this._localSubscribables = this._localSubscribables || {};
|
|
||||||
this._localSubscribables[eventEmitter] =
|
|
||||||
this._localSubscribables[eventEmitter] || {};
|
|
||||||
if (!this._localSubscribables[eventEmitter][eventName]) {
|
|
||||||
this._localSubscribables[eventEmitter][eventName] =
|
|
||||||
new Subscribable(eventEmitter, eventName, eventMapping, getInitData);
|
|
||||||
}
|
|
||||||
return this._localSubscribables[eventEmitter][eventName];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes any local Subscribables created with `provideSubscribable`, so the
|
|
||||||
* component can unmount without leaving any dangling listeners on
|
|
||||||
* eventEmitters
|
|
||||||
*/
|
|
||||||
_cleanupLocalSubscribables: function() {
|
|
||||||
if (!this._localSubscribables) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Object.keys(this._localSubscribables).forEach((eventEmitter) => {
|
|
||||||
var emitterSubscribables = this._localSubscribables[eventEmitter];
|
|
||||||
if (emitterSubscribables) {
|
|
||||||
Object.keys(emitterSubscribables).forEach((eventName) => {
|
|
||||||
emitterSubscribables[eventName].cleanup();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this._localSubscribables = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this._endSubscribableLifespanCallbacks = [];
|
|
||||||
|
|
||||||
// DEPRECATED addListenerOn* usage:
|
|
||||||
this._subscribableSubscriptions = [];
|
this._subscribableSubscriptions = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
// Resolve the lifespan, which will cause Subscribable to clean any
|
|
||||||
// remaining subscriptions
|
|
||||||
this._endSubscribableLifespan && this._endSubscribableLifespan();
|
|
||||||
|
|
||||||
this._cleanupLocalSubscribables();
|
|
||||||
|
|
||||||
// DEPRECATED addListenerOn* usage uses _subscribableSubscriptions array
|
|
||||||
// instead of lifespan
|
|
||||||
this._subscribableSubscriptions.forEach(
|
this._subscribableSubscriptions.forEach(
|
||||||
(subscription) => subscription.remove()
|
(subscription) => subscription.remove()
|
||||||
);
|
);
|
||||||
|
@ -320,9 +37,6 @@ Subscribable.Mixin = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DEPRECATED - Use `Subscribable` and `Mixin.subscribeTo` instead.
|
|
||||||
* `addListenerOn` subscribes the component to an `EventEmitter`.
|
|
||||||
*
|
|
||||||
* Special form of calling `addListener` that *guarantees* that a
|
* Special form of calling `addListener` that *guarantees* that a
|
||||||
* subscription *must* be tied to a component instance, and therefore will
|
* subscription *must* be tied to a component instance, and therefore will
|
||||||
* be cleaned up when the component is unmounted. It is impossible to create
|
* be cleaned up when the component is unmounted. It is impossible to create
|
||||||
|
@ -335,7 +49,12 @@ Subscribable.Mixin = {
|
||||||
* @param {function} listener Function to invoke when event occurs.
|
* @param {function} listener Function to invoke when event occurs.
|
||||||
* @param {object} context Object to use as listener context.
|
* @param {object} context Object to use as listener context.
|
||||||
*/
|
*/
|
||||||
addListenerOn: function(eventEmitter, eventType, listener, context) {
|
addListenerOn: function(
|
||||||
|
eventEmitter: EventEmitter,
|
||||||
|
eventType: string,
|
||||||
|
listener: Function,
|
||||||
|
context: Object
|
||||||
|
) {
|
||||||
this._subscribableSubscriptions.push(
|
this._subscribableSubscriptions.push(
|
||||||
eventEmitter.addListener(eventType, listener, context)
|
eventEmitter.addListener(eventType, listener, context)
|
||||||
);
|
);
|
||||||
|
|
|
@ -27,12 +27,14 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule;
|
var AnimationsDebugModule = require('NativeModules').AnimationsDebugModule;
|
||||||
var Backstack = require('Backstack');
|
var BackAndroid = require('BackAndroid');
|
||||||
var Dimensions = require('Dimensions');
|
var Dimensions = require('Dimensions');
|
||||||
var InteractionMixin = require('InteractionMixin');
|
var InteractionMixin = require('InteractionMixin');
|
||||||
var NavigatorSceneConfigs = require('NavigatorSceneConfigs');
|
|
||||||
var NavigatorNavigationBar = require('NavigatorNavigationBar');
|
|
||||||
var NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar');
|
var NavigatorBreadcrumbNavigationBar = require('NavigatorBreadcrumbNavigationBar');
|
||||||
|
var NavigatorInterceptor = require('NavigatorInterceptor');
|
||||||
|
var NavigatorNavigationBar = require('NavigatorNavigationBar');
|
||||||
|
var NavigatorSceneConfigs = require('NavigatorSceneConfigs');
|
||||||
|
var NavigatorStaticContextContainer = require('NavigatorStaticContextContainer');
|
||||||
var PanResponder = require('PanResponder');
|
var PanResponder = require('PanResponder');
|
||||||
var React = require('React');
|
var React = require('React');
|
||||||
var StaticContainer = require('StaticContainer.react');
|
var StaticContainer = require('StaticContainer.react');
|
||||||
|
@ -41,6 +43,7 @@ var Subscribable = require('Subscribable');
|
||||||
var TimerMixin = require('react-timer-mixin');
|
var TimerMixin = require('react-timer-mixin');
|
||||||
var View = require('View');
|
var View = require('View');
|
||||||
|
|
||||||
|
var getNavigatorContext = require('getNavigatorContext');
|
||||||
var clamp = require('clamp');
|
var clamp = require('clamp');
|
||||||
var invariant = require('invariant');
|
var invariant = require('invariant');
|
||||||
var keyMirror = require('keyMirror');
|
var keyMirror = require('keyMirror');
|
||||||
|
@ -59,8 +62,6 @@ function getuid() {
|
||||||
return __uid++;
|
return __uid++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nextComponentUid = 0;
|
|
||||||
|
|
||||||
// styles moved to the top of the file so getDefaultProps can refer to it
|
// styles moved to the top of the file so getDefaultProps can refer to it
|
||||||
var styles = StyleSheet.create({
|
var styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -185,12 +186,11 @@ var Navigator = React.createClass({
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required function which renders the scene for a given route. Will be
|
* Required function which renders the scene for a given route. Will be
|
||||||
* invoked with the route, the navigator object, and a ref handler that
|
* invoked with the route and the navigator object
|
||||||
* will allow a ref to your scene to be provided by props.onItemRef
|
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* (route, navigator, onRef) =>
|
* (route, navigator) =>
|
||||||
* <MySceneComponent title={route.title} ref={onRef} />
|
* <MySceneComponent title={route.title} />
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
renderScene: PropTypes.func.isRequired,
|
renderScene: PropTypes.func.isRequired,
|
||||||
|
@ -242,19 +242,18 @@ var Navigator = React.createClass({
|
||||||
* Styles to apply to the container of each scene
|
* Styles to apply to the container of each scene
|
||||||
*/
|
*/
|
||||||
sceneStyle: View.propTypes.style,
|
sceneStyle: View.propTypes.style,
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
contextTypes: {
|
||||||
* Should the backstack back button "jump" back instead of pop? Set to true
|
navigator: PropTypes.object,
|
||||||
* if a jump forward might happen after the android back button is pressed,
|
|
||||||
* so the scenes will remain mounted
|
|
||||||
*/
|
|
||||||
shouldJumpOnBackstackPop: PropTypes.bool,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
statics: {
|
statics: {
|
||||||
BreadcrumbNavigationBar: NavigatorBreadcrumbNavigationBar,
|
BreadcrumbNavigationBar: NavigatorBreadcrumbNavigationBar,
|
||||||
NavigationBar: NavigatorNavigationBar,
|
NavigationBar: NavigatorNavigationBar,
|
||||||
SceneConfigs: NavigatorSceneConfigs,
|
SceneConfigs: NavigatorSceneConfigs,
|
||||||
|
Interceptor: NavigatorInterceptor,
|
||||||
|
getContext: getNavigatorContext,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [TimerMixin, InteractionMixin, Subscribable.Mixin],
|
mixins: [TimerMixin, InteractionMixin, Subscribable.Mixin],
|
||||||
|
@ -303,7 +302,20 @@ var Navigator = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
this.navigatorActions = {
|
this.parentNavigator = getNavigatorContext(this) || this.props.navigator;
|
||||||
|
this.navigatorContext = {
|
||||||
|
setHandlerForRoute: this.setHandlerForRoute,
|
||||||
|
request: this.request,
|
||||||
|
|
||||||
|
parentNavigator: this.parentNavigator,
|
||||||
|
getCurrentRoutes: this.getCurrentRoutes,
|
||||||
|
// We want to bubble focused routes to the top navigation stack. If we
|
||||||
|
// are a child navigator, this allows us to call props.navigator.on*Focus
|
||||||
|
// of the topmost Navigator
|
||||||
|
onWillFocus: this.props.onWillFocus,
|
||||||
|
onDidFocus: this.props.onDidFocus,
|
||||||
|
|
||||||
|
// Legacy, imperitive nav actions. Use request when possible.
|
||||||
jumpBack: this.jumpBack,
|
jumpBack: this.jumpBack,
|
||||||
jumpForward: this.jumpForward,
|
jumpForward: this.jumpForward,
|
||||||
jumpTo: this.jumpTo,
|
jumpTo: this.jumpTo,
|
||||||
|
@ -317,14 +329,8 @@ var Navigator = React.createClass({
|
||||||
resetTo: this.resetTo,
|
resetTo: this.resetTo,
|
||||||
popToRoute: this.popToRoute,
|
popToRoute: this.popToRoute,
|
||||||
popToTop: this.popToTop,
|
popToTop: this.popToTop,
|
||||||
parentNavigator: this.props.navigator,
|
|
||||||
getCurrentRoutes: this.getCurrentRoutes,
|
|
||||||
// We want to bubble focused routes to the top navigation stack. If we
|
|
||||||
// are a child navigator, this allows us to call props.navigator.on*Focus
|
|
||||||
// of the topmost Navigator
|
|
||||||
onWillFocus: this.props.onWillFocus,
|
|
||||||
onDidFocus: this.props.onDidFocus,
|
|
||||||
};
|
};
|
||||||
|
this._handlers = {};
|
||||||
|
|
||||||
this.panGesture = PanResponder.create({
|
this.panGesture = PanResponder.create({
|
||||||
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture,
|
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture,
|
||||||
|
@ -336,17 +342,50 @@ var Navigator = React.createClass({
|
||||||
});
|
});
|
||||||
this._itemRefs = {};
|
this._itemRefs = {};
|
||||||
this._interactionHandle = null;
|
this._interactionHandle = null;
|
||||||
this._backstackComponentKey = 'jsnavstack' + nextComponentUid;
|
|
||||||
nextComponentUid++;
|
|
||||||
|
|
||||||
Backstack.eventEmitter && this.addListenerOn(
|
|
||||||
Backstack.eventEmitter,
|
|
||||||
'popNavigation',
|
|
||||||
this._onBackstackPopState);
|
|
||||||
|
|
||||||
this._emitWillFocus(this.state.presentedIndex);
|
this._emitWillFocus(this.state.presentedIndex);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
request: function(action, arg1, arg2) {
|
||||||
|
if (this.parentNavigator) {
|
||||||
|
return this.parentNavigator.request.apply(null, arguments);
|
||||||
|
}
|
||||||
|
return this._handleRequest.apply(null, arguments);
|
||||||
|
},
|
||||||
|
|
||||||
|
_handleRequest: function(action, arg1, arg2) {
|
||||||
|
var childHandler = this._handlers[this.state.presentedIndex];
|
||||||
|
if (childHandler && childHandler(action, arg1, arg2)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (action) {
|
||||||
|
case 'pop':
|
||||||
|
return this._handlePop();
|
||||||
|
case 'push':
|
||||||
|
return this._handlePush(arg1);
|
||||||
|
default:
|
||||||
|
invariant(false, 'Unsupported request type ' + action);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_handlePop: function() {
|
||||||
|
if (this.state.presentedIndex === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.pop();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
_handlePush: function(route) {
|
||||||
|
this.push(route);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
setHandlerForRoute: function(route, handler) {
|
||||||
|
this._handlers[this.state.routeStack.indexOf(route)] = handler;
|
||||||
|
},
|
||||||
|
|
||||||
_configureSpring: function(animationConfig) {
|
_configureSpring: function(animationConfig) {
|
||||||
var config = this.spring.getSpringConfig();
|
var config = this.spring.getSpringConfig();
|
||||||
config.friction = animationConfig.springFriction;
|
config.friction = animationConfig.springFriction;
|
||||||
|
@ -361,30 +400,28 @@ var Navigator = React.createClass({
|
||||||
animationConfig && this._configureSpring(animationConfig);
|
animationConfig && this._configureSpring(animationConfig);
|
||||||
this.spring.addListener(this);
|
this.spring.addListener(this);
|
||||||
this.onSpringUpdate();
|
this.onSpringUpdate();
|
||||||
|
|
||||||
// Fill up the Backstack with routes that have been provided in
|
|
||||||
// initialRouteStack
|
|
||||||
this._fillBackstackRange(0, this.state.routeStack.indexOf(this.props.initialRoute));
|
|
||||||
this._emitDidFocus(this.state.presentedIndex);
|
this._emitDidFocus(this.state.presentedIndex);
|
||||||
|
if (this.parentNavigator) {
|
||||||
|
this.parentNavigator.setHandler(this._handleRequest);
|
||||||
|
} else {
|
||||||
|
// There is no navigator in our props or context, so this is the
|
||||||
|
// top-level navigator. We will handle back button presses here
|
||||||
|
BackAndroid.addEventListener('hardwareBackPress', this._handleBackPress);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillUnmount: function() {
|
componentWillUnmount: function() {
|
||||||
Backstack.removeComponentHistory(this._backstackComponentKey);
|
if (this.parentNavigator) {
|
||||||
|
this.parentNavigator.setHandler(null);
|
||||||
|
} else {
|
||||||
|
BackAndroid.removeEventListener('hardwareBackPress', this._handleBackPress);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_onBackstackPopState: function(componentKey, stateKey, state) {
|
_handleBackPress: function() {
|
||||||
if (componentKey !== this._backstackComponentKey) {
|
var didPop = this.request('pop');
|
||||||
return;
|
if (!didPop) {
|
||||||
}
|
BackAndroid.exitApp();
|
||||||
if (!this._canNavigate()) {
|
|
||||||
// A bit hacky: if we can't actually handle the pop, just push it back on the stack
|
|
||||||
Backstack.pushNavigation(componentKey, stateKey, state);
|
|
||||||
} else {
|
|
||||||
if (this.props.shouldJumpOnBackstackPop) {
|
|
||||||
this._jumpToWithoutBackstack(state.fromRoute);
|
|
||||||
} else {
|
|
||||||
this._popToRouteWithoutBackstack(state.fromRoute);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -737,41 +774,6 @@ var Navigator = React.createClass({
|
||||||
return !this.state.isAnimating;
|
return !this.state.isAnimating;
|
||||||
},
|
},
|
||||||
|
|
||||||
_jumpNWithoutBackstack: function(n) {
|
|
||||||
var destIndex = this._getDestIndexWithinBounds(n);
|
|
||||||
if (!this._canNavigate()) {
|
|
||||||
return; // It's busy animating or transitioning.
|
|
||||||
}
|
|
||||||
var requestTransitionAndResetUpdatingRange = () => {
|
|
||||||
this._requestTransitionTo(destIndex);
|
|
||||||
this._resetUpdatingRange();
|
|
||||||
};
|
|
||||||
this.setState({
|
|
||||||
updatingRangeStart: destIndex,
|
|
||||||
updatingRangeLength: 1,
|
|
||||||
toIndex: destIndex,
|
|
||||||
}, requestTransitionAndResetUpdatingRange);
|
|
||||||
},
|
|
||||||
|
|
||||||
_fillBackstackRange: function(start, end) {
|
|
||||||
invariant(
|
|
||||||
start <= end,
|
|
||||||
'Can only fill the backstack forward. Provide end index greater than start'
|
|
||||||
);
|
|
||||||
for (var i = 0; i < (end - start); i++) {
|
|
||||||
var fromIndex = start + i;
|
|
||||||
var toIndex = start + i + 1;
|
|
||||||
Backstack.pushNavigation(
|
|
||||||
this._backstackComponentKey,
|
|
||||||
toIndex,
|
|
||||||
{
|
|
||||||
fromRoute: this.state.routeStack[fromIndex],
|
|
||||||
toRoute: this.state.routeStack[toIndex],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_getDestIndexWithinBounds: function(n) {
|
_getDestIndexWithinBounds: function(n) {
|
||||||
var currentIndex = this.state.presentedIndex;
|
var currentIndex = this.state.presentedIndex;
|
||||||
var destIndex = currentIndex + n;
|
var destIndex = currentIndex + n;
|
||||||
|
@ -788,20 +790,19 @@ var Navigator = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_jumpN: function(n) {
|
_jumpN: function(n) {
|
||||||
var currentIndex = this.state.presentedIndex;
|
var destIndex = this._getDestIndexWithinBounds(n);
|
||||||
if (!this._canNavigate()) {
|
if (!this._canNavigate()) {
|
||||||
return; // It's busy animating or transitioning.
|
return; // It's busy animating or transitioning.
|
||||||
}
|
}
|
||||||
if (n > 0) {
|
var requestTransitionAndResetUpdatingRange = () => {
|
||||||
this._fillBackstackRange(currentIndex, currentIndex + n);
|
this._requestTransitionTo(destIndex);
|
||||||
} else {
|
this._resetUpdatingRange();
|
||||||
var landingBeforeIndex = currentIndex + n + 1;
|
};
|
||||||
Backstack.resetToBefore(
|
this.setState({
|
||||||
this._backstackComponentKey,
|
updatingRangeStart: destIndex,
|
||||||
landingBeforeIndex
|
updatingRangeLength: 1,
|
||||||
);
|
toIndex: destIndex,
|
||||||
}
|
}, requestTransitionAndResetUpdatingRange);
|
||||||
this._jumpNWithoutBackstack(n);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
jumpTo: function(route) {
|
jumpTo: function(route) {
|
||||||
|
@ -813,15 +814,6 @@ var Navigator = React.createClass({
|
||||||
this._jumpN(destIndex - this.state.presentedIndex);
|
this._jumpN(destIndex - this.state.presentedIndex);
|
||||||
},
|
},
|
||||||
|
|
||||||
_jumpToWithoutBackstack: function(route) {
|
|
||||||
var destIndex = this.state.routeStack.indexOf(route);
|
|
||||||
invariant(
|
|
||||||
destIndex !== -1,
|
|
||||||
'Cannot jump to route that is not in the route stack'
|
|
||||||
);
|
|
||||||
this._jumpNWithoutBackstack(destIndex - this.state.presentedIndex);
|
|
||||||
},
|
|
||||||
|
|
||||||
jumpForward: function() {
|
jumpForward: function() {
|
||||||
this._jumpN(1);
|
this._jumpN(1);
|
||||||
},
|
},
|
||||||
|
@ -852,11 +844,6 @@ var Navigator = React.createClass({
|
||||||
toRoute: route,
|
toRoute: route,
|
||||||
fromRoute: this.state.routeStack[this.state.routeStack.length - 1],
|
fromRoute: this.state.routeStack[this.state.routeStack.length - 1],
|
||||||
};
|
};
|
||||||
Backstack.pushNavigation(
|
|
||||||
this._backstackComponentKey,
|
|
||||||
this.state.routeStack.length,
|
|
||||||
navigationState);
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
idStack: nextIDStack,
|
idStack: nextIDStack,
|
||||||
routeStack: nextStack,
|
routeStack: nextStack,
|
||||||
|
@ -867,14 +854,7 @@ var Navigator = React.createClass({
|
||||||
}, requestTransitionAndResetUpdatingRange);
|
}, requestTransitionAndResetUpdatingRange);
|
||||||
},
|
},
|
||||||
|
|
||||||
_manuallyPopBackstack: function(n) {
|
popN: function(n) {
|
||||||
Backstack.resetToBefore(this._backstackComponentKey, this.state.routeStack.length - n);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like popN, but doesn't also update the Backstack.
|
|
||||||
*/
|
|
||||||
_popNWithoutBackstack: function(n) {
|
|
||||||
if (n === 0 || !this._canNavigate()) {
|
if (n === 0 || !this._canNavigate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -888,17 +868,10 @@ var Navigator = React.createClass({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
popN: function(n) {
|
|
||||||
if (n === 0 || !this._canNavigate()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._popNWithoutBackstack(n);
|
|
||||||
this._manuallyPopBackstack(n);
|
|
||||||
},
|
|
||||||
|
|
||||||
pop: function() {
|
pop: function() {
|
||||||
if (this.props.navigator && this.state.routeStack.length === 1) {
|
// TODO (t6707686): remove this parentNavigator call after transitioning call sites to `.request('pop')`
|
||||||
return this.props.navigator.pop();
|
if (this.parentNavigator && this.state.routeStack.length === 1) {
|
||||||
|
return this.parentNavigator.pop();
|
||||||
}
|
}
|
||||||
this.popN(1);
|
this.popN(1);
|
||||||
},
|
},
|
||||||
|
@ -970,14 +943,6 @@ var Navigator = React.createClass({
|
||||||
return this.state.routeStack.length - indexOfRoute - 1;
|
return this.state.routeStack.length - indexOfRoute - 1;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Like popToRoute, but doesn't update the Backstack, presumably because it's already up to date.
|
|
||||||
*/
|
|
||||||
_popToRouteWithoutBackstack: function(route) {
|
|
||||||
var numToPop = this._getNumToPopForRoute(route);
|
|
||||||
this._popNWithoutBackstack(numToPop);
|
|
||||||
},
|
|
||||||
|
|
||||||
popToRoute: function(route) {
|
popToRoute: function(route) {
|
||||||
var numToPop = this._getNumToPopForRoute(route);
|
var numToPop = this._getNumToPopForRoute(route);
|
||||||
this.popN(numToPop);
|
this.popN(numToPop);
|
||||||
|
@ -1003,7 +968,7 @@ var Navigator = React.createClass({
|
||||||
return this.state.routeStack;
|
return this.state.routeStack;
|
||||||
},
|
},
|
||||||
|
|
||||||
_onItemRef: function(itemId, ref) {
|
_handleItemRef: function(itemId, ref) {
|
||||||
this._itemRefs[itemId] = ref;
|
this._itemRefs[itemId] = ref;
|
||||||
var itemIndex = this.state.idStack.indexOf(itemId);
|
var itemIndex = this.state.idStack.indexOf(itemId);
|
||||||
if (itemIndex === -1) {
|
if (itemIndex === -1) {
|
||||||
|
@ -1036,23 +1001,33 @@ var Navigator = React.createClass({
|
||||||
this.state.updatingRangeLength !== 0 &&
|
this.state.updatingRangeLength !== 0 &&
|
||||||
i >= this.state.updatingRangeStart &&
|
i >= this.state.updatingRangeStart &&
|
||||||
i <= this.state.updatingRangeStart + this.state.updatingRangeLength;
|
i <= this.state.updatingRangeStart + this.state.updatingRangeLength;
|
||||||
|
var sceneNavigatorContext = {
|
||||||
|
...this.navigatorContext,
|
||||||
|
route,
|
||||||
|
setHandler: (handler) => {
|
||||||
|
this.navigatorContext.setHandlerForRoute(route, handler);
|
||||||
|
},
|
||||||
|
};
|
||||||
var child = this.props.renderScene(
|
var child = this.props.renderScene(
|
||||||
route,
|
route,
|
||||||
this.navigatorActions,
|
sceneNavigatorContext
|
||||||
this._onItemRef.bind(null, this.state.idStack[i])
|
|
||||||
);
|
);
|
||||||
|
|
||||||
var initialSceneStyle =
|
var initialSceneStyle =
|
||||||
i === this.state.presentedIndex ? styles.presentNavItem : styles.futureNavItem;
|
i === this.state.presentedIndex ? styles.presentNavItem : styles.futureNavItem;
|
||||||
return (
|
return (
|
||||||
<StaticContainer key={'nav' + i} shouldUpdate={shouldUpdateChild}>
|
<NavigatorStaticContextContainer
|
||||||
|
navigatorContext={sceneNavigatorContext}
|
||||||
|
key={'nav' + i}
|
||||||
|
shouldUpdate={shouldUpdateChild}>
|
||||||
<View
|
<View
|
||||||
key={this.state.idStack[i]}
|
key={this.state.idStack[i]}
|
||||||
ref={'scene_' + i}
|
ref={'scene_' + i}
|
||||||
style={[initialSceneStyle, this.props.sceneStyle]}>
|
style={[initialSceneStyle, this.props.sceneStyle]}>
|
||||||
{child}
|
{React.cloneElement(child, {
|
||||||
|
ref: this._handleItemRef.bind(null, this.state.idStack[i]),
|
||||||
|
})}
|
||||||
</View>
|
</View>
|
||||||
</StaticContainer>
|
</NavigatorStaticContextContainer>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1081,7 +1056,7 @@ var Navigator = React.createClass({
|
||||||
}
|
}
|
||||||
return React.cloneElement(this.props.navigationBar, {
|
return React.cloneElement(this.props.navigationBar, {
|
||||||
ref: (navBar) => { this._navBar = navBar; },
|
ref: (navBar) => { this._navBar = navBar; },
|
||||||
navigator: this.navigatorActions,
|
navigator: this.navigatorContext,
|
||||||
navState: this.state,
|
navState: this.state,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||||
|
* all intellectual property and other proprietary rights, in and to the React
|
||||||
|
* Native CustomComponents software (the “Software”). Subject to your
|
||||||
|
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||||
|
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||||
|
* and (2) reproduce and distribute the Software as part of your own software
|
||||||
|
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||||
|
* you in this license agreement.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||||
|
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
|
||||||
|
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
|
||||||
|
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* @providesModule NavigatorInterceptor
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('React');
|
||||||
|
|
||||||
|
var getNavigatorContext = require('getNavigatorContext');
|
||||||
|
|
||||||
|
var NavigatorInterceptor = React.createClass({
|
||||||
|
|
||||||
|
contextTypes: {
|
||||||
|
navigator: React.PropTypes.object,
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
this.navigator = getNavigatorContext(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
this.navigator.setHandler(this._navigatorHandleRequest);
|
||||||
|
},
|
||||||
|
|
||||||
|
childContextTypes: {
|
||||||
|
navigator: React.PropTypes.object,
|
||||||
|
},
|
||||||
|
|
||||||
|
getChildContext: function() {
|
||||||
|
return {
|
||||||
|
navigator: {
|
||||||
|
...this.navigator,
|
||||||
|
setHandler: (handler) => {
|
||||||
|
this._childNavigationHandler = handler;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
this.navigator.setHandler(null);
|
||||||
|
},
|
||||||
|
|
||||||
|
_navigatorHandleRequest: function(action, arg1, arg2) {
|
||||||
|
if (this._interceptorHandle(action, arg1, arg2)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (this._childNavigationHandler && this._childNavigationHandler(action, arg1, arg2)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_interceptorHandle: function(action, arg1, arg2) {
|
||||||
|
if (this.props.onRequest && this.props.onRequest(action, arg1, arg2)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (action) {
|
||||||
|
case 'pop':
|
||||||
|
return this.props.onPopRequest && this.props.onPopRequest(action, arg1, arg2);
|
||||||
|
case 'push':
|
||||||
|
return this.props.onPushRequest && this.props.onPushRequest(action, arg1, arg2);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return this.props.children;
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = NavigatorInterceptor;
|
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||||
|
* all intellectual property and other proprietary rights, in and to the React
|
||||||
|
* Native CustomComponents software (the “Software”). Subject to your
|
||||||
|
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||||
|
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||||
|
* and (2) reproduce and distribute the Software as part of your own software
|
||||||
|
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||||
|
* you in this license agreement.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||||
|
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
|
||||||
|
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
|
||||||
|
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* @providesModule NavigatorStaticContextContainer
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var React = require('React');
|
||||||
|
var StaticContainer = require('StaticContainer.react');
|
||||||
|
|
||||||
|
var PropTypes = React.PropTypes;
|
||||||
|
|
||||||
|
var NavigatorStaticContextContainer = React.createClass({
|
||||||
|
|
||||||
|
childContextTypes: {
|
||||||
|
navigator: PropTypes.object,
|
||||||
|
},
|
||||||
|
|
||||||
|
getChildContext: function() {
|
||||||
|
return {
|
||||||
|
navigator: this.props.navigatorContext,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
return (
|
||||||
|
<StaticContainer {...this.props} />
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = NavigatorStaticContextContainer;
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015, Facebook, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Facebook, Inc. (“Facebook”) owns all right, title and interest, including
|
||||||
|
* all intellectual property and other proprietary rights, in and to the React
|
||||||
|
* Native CustomComponents software (the “Software”). Subject to your
|
||||||
|
* compliance with these terms, you are hereby granted a non-exclusive,
|
||||||
|
* worldwide, royalty-free copyright license to (1) use and copy the Software;
|
||||||
|
* and (2) reproduce and distribute the Software as part of your own software
|
||||||
|
* (“Your Software”). Facebook reserves all rights not expressly granted to
|
||||||
|
* you in this license agreement.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS
|
||||||
|
* OR IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED.
|
||||||
|
* IN NO EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICERS, DIRECTORS OR
|
||||||
|
* EMPLOYEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
||||||
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||||
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||||
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* @providesModule getNavigatorContext
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
var ReactInstanceMap = require('ReactInstanceMap');
|
||||||
|
|
||||||
|
function getNavigatorContext(el) {
|
||||||
|
// TODO (t6707746): replace with `el.context.navigator` when parent context is supported
|
||||||
|
return ReactInstanceMap.get(el)._context.navigator;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getNavigatorContext;
|
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @providesModule ImageSource
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
type ImageSource = {
|
||||||
|
uri: string;
|
||||||
|
isStatic: boolean;
|
||||||
|
};
|
|
@ -77,13 +77,14 @@ static NSString *RCTCacheKeyForURL(NSURL *url)
|
||||||
|
|
||||||
__weak RCTImageDownloader *weakSelf = self;
|
__weak RCTImageDownloader *weakSelf = self;
|
||||||
RCTCachedDataDownloadBlock runBlocks = ^(BOOL cached, NSData *data, NSError *error) {
|
RCTCachedDataDownloadBlock runBlocks = ^(BOOL cached, NSData *data, NSError *error) {
|
||||||
|
dispatch_async(_processingQueue, ^{
|
||||||
RCTImageDownloader *strongSelf = weakSelf;
|
RCTImageDownloader *strongSelf = weakSelf;
|
||||||
NSArray *blocks = strongSelf->_pendingBlocks[cacheKey];
|
NSArray *blocks = strongSelf->_pendingBlocks[cacheKey];
|
||||||
[strongSelf->_pendingBlocks removeObjectForKey:cacheKey];
|
[strongSelf->_pendingBlocks removeObjectForKey:cacheKey];
|
||||||
for (RCTCachedDataDownloadBlock block in blocks) {
|
for (RCTCachedDataDownloadBlock block in blocks) {
|
||||||
block(cached, data, error);
|
block(cached, data, error);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if ([_cache hasDataForKey:cacheKey]) {
|
if ([_cache hasDataForKey:cacheKey]) {
|
||||||
|
|
|
@ -119,7 +119,9 @@ typedef void (^WSMessageCallback)(NSError *error, NSDictionary *reply);
|
||||||
|
|
||||||
[_jsQueue addOperationWithBlock:^{
|
[_jsQueue addOperationWithBlock:^{
|
||||||
if (!self.valid) {
|
if (!self.valid) {
|
||||||
NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{NSLocalizedDescriptionKey:@"socket closed"}];
|
NSError *error = [NSError errorWithDomain:@"WS" code:1 userInfo:@{
|
||||||
|
NSLocalizedDescriptionKey: @"socket closed"
|
||||||
|
}];
|
||||||
callback(error, nil);
|
callback(error, nil);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,49 +27,73 @@ var RCTAsyncStorage = RCTAsyncRocksDBStorage || RCTAsyncLocalStorage;
|
||||||
* operates globally.
|
* operates globally.
|
||||||
*
|
*
|
||||||
* This JS code is a simple facad over the native iOS implementation to provide
|
* This JS code is a simple facad over the native iOS implementation to provide
|
||||||
* a clear JS API, real Error objects, and simple non-multi functions.
|
* a clear JS API, real Error objects, and simple non-multi functions. Each
|
||||||
|
* method returns a `Promise` object.
|
||||||
*/
|
*/
|
||||||
var AsyncStorage = {
|
var AsyncStorage = {
|
||||||
/**
|
/**
|
||||||
* Fetches `key` and passes the result to `callback`, along with an `Error` if
|
* Fetches `key` and passes the result to `callback`, along with an `Error` if
|
||||||
* there is any.
|
* there is any. Returns a `Promise` object.
|
||||||
*/
|
*/
|
||||||
getItem: function(
|
getItem: function(
|
||||||
key: string,
|
key: string,
|
||||||
callback: (error: ?Error, result: ?string) => void
|
callback: (error: ?Error, result: ?string) => void
|
||||||
): void {
|
): Promise {
|
||||||
RCTAsyncStorage.multiGet([key], function(errors, result) {
|
return new Promise((resolve, reject) => {
|
||||||
// Unpack result to get value from [[key,value]]
|
RCTAsyncStorage.multiGet([key], function(errors, result) {
|
||||||
var value = (result && result[0] && result[0][1]) ? result[0][1] : null;
|
// Unpack result to get value from [[key,value]]
|
||||||
callback((errors && convertError(errors[0])) || null, value);
|
var value = (result && result[0] && result[0][1]) ? result[0][1] : null;
|
||||||
|
callback && callback((errors && convertError(errors[0])) || null, value);
|
||||||
|
if (errors) {
|
||||||
|
reject(convertError(errors[0]));
|
||||||
|
} else {
|
||||||
|
resolve(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets `value` for `key` and calls `callback` on completion, along with an
|
* Sets `value` for `key` and calls `callback` on completion, along with an
|
||||||
* `Error` if there is any.
|
* `Error` if there is any. Returns a `Promise` object.
|
||||||
*/
|
*/
|
||||||
setItem: function(
|
setItem: function(
|
||||||
key: string,
|
key: string,
|
||||||
value: string,
|
value: string,
|
||||||
callback: ?(error: ?Error) => void
|
callback: ?(error: ?Error) => void
|
||||||
): void {
|
): Promise {
|
||||||
RCTAsyncStorage.multiSet([[key,value]], function(errors) {
|
return new Promise((resolve, reject) => {
|
||||||
callback && callback((errors && convertError(errors[0])) || null);
|
RCTAsyncStorage.multiSet([[key,value]], function(errors) {
|
||||||
|
callback && callback((errors && convertError(errors[0])) || null);
|
||||||
|
if (errors) {
|
||||||
|
reject(convertError(errors[0]));
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Returns a `Promise` object.
|
||||||
|
*/
|
||||||
removeItem: function(
|
removeItem: function(
|
||||||
key: string,
|
key: string,
|
||||||
callback: ?(error: ?Error) => void
|
callback: ?(error: ?Error) => void
|
||||||
): void {
|
): Promise {
|
||||||
RCTAsyncStorage.multiRemove([key], function(errors) {
|
return new Promise((resolve, reject) => {
|
||||||
callback && callback((errors && convertError(errors[0])) || null);
|
RCTAsyncStorage.multiRemove([key], function(errors) {
|
||||||
|
callback && callback((errors && convertError(errors[0])) || null);
|
||||||
|
if (errors) {
|
||||||
|
reject(convertError(errors[0]));
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges existing value with input value, assuming they are stringified json.
|
* Merges existing value with input value, assuming they are stringified json. Returns a `Promise` object.
|
||||||
*
|
*
|
||||||
* Not supported by all native implementations.
|
* Not supported by all native implementations.
|
||||||
*/
|
*/
|
||||||
|
@ -77,29 +101,50 @@ var AsyncStorage = {
|
||||||
key: string,
|
key: string,
|
||||||
value: string,
|
value: string,
|
||||||
callback: ?(error: ?Error) => void
|
callback: ?(error: ?Error) => void
|
||||||
): void {
|
): Promise {
|
||||||
RCTAsyncStorage.multiMerge([[key,value]], function(errors) {
|
return new Promise((resolve, reject) => {
|
||||||
callback && callback((errors && convertError(errors[0])) || null);
|
RCTAsyncStorage.multiMerge([[key,value]], function(errors) {
|
||||||
|
callback && callback((errors && convertError(errors[0])) || null);
|
||||||
|
if (errors) {
|
||||||
|
reject(convertError(errors[0]));
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erases *all* AsyncStorage for all clients, libraries, etc. You probably
|
* Erases *all* AsyncStorage for all clients, libraries, etc. You probably
|
||||||
* don't want to call this - use removeItem or multiRemove to clear only your
|
* don't want to call this - use removeItem or multiRemove to clear only your
|
||||||
* own keys instead.
|
* own keys instead. Returns a `Promise` object.
|
||||||
*/
|
*/
|
||||||
clear: function(callback: ?(error: ?Error) => void) {
|
clear: function(callback: ?(error: ?Error) => void): Promise {
|
||||||
RCTAsyncStorage.clear(function(error) {
|
return new Promise((resolve, reject) => {
|
||||||
callback && callback(convertError(error));
|
RCTAsyncStorage.clear(function(error) {
|
||||||
|
callback && callback(convertError(error));
|
||||||
|
if (error && convertError(error)){
|
||||||
|
reject(convertError(error));
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets *all* keys known to the system, for all callers, libraries, etc.
|
* Gets *all* keys known to the system, for all callers, libraries, etc. Returns a `Promise` object.
|
||||||
*/
|
*/
|
||||||
getAllKeys: function(callback: (error: ?Error) => void) {
|
getAllKeys: function(callback: (error: ?Error) => void): Promise {
|
||||||
RCTAsyncStorage.getAllKeys(function(error, keys) {
|
return new Promise((resolve, reject) => {
|
||||||
callback(convertError(error), keys);
|
RCTAsyncStorage.getAllKeys(function(error, keys) {
|
||||||
|
callback && callback(convertError(error), keys);
|
||||||
|
if (error) {
|
||||||
|
reject(convertError(error));
|
||||||
|
} else {
|
||||||
|
resolve(keys);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -115,67 +160,90 @@ var AsyncStorage = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* multiGet invokes callback with an array of key-value pair arrays that
|
* multiGet invokes callback with an array of key-value pair arrays that
|
||||||
* matches the input format of multiSet.
|
* matches the input format of multiSet. Returns a `Promise` object.
|
||||||
*
|
*
|
||||||
* multiGet(['k1', 'k2'], cb) -> cb([['k1', 'val1'], ['k2', 'val2']])
|
* multiGet(['k1', 'k2'], cb) -> cb([['k1', 'val1'], ['k2', 'val2']])
|
||||||
*/
|
*/
|
||||||
multiGet: function(
|
multiGet: function(
|
||||||
keys: Array<string>,
|
keys: Array<string>,
|
||||||
callback: (errors: ?Array<Error>, result: ?Array<Array<string>>) => void
|
callback: (errors: ?Array<Error>, result: ?Array<Array<string>>) => void
|
||||||
): void {
|
): Promise {
|
||||||
RCTAsyncStorage.multiGet(keys, function(errors, result) {
|
return new Promise((resolve, reject) => {
|
||||||
callback(
|
RCTAsyncStorage.multiGet(keys, function(errors, result) {
|
||||||
(errors && errors.map((error) => convertError(error))) || null,
|
var error = (errors && errors.map((error) => convertError(error))) || null;
|
||||||
result
|
callback && callback(error, result);
|
||||||
);
|
if (errors) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* multiSet and multiMerge take arrays of key-value array pairs that match
|
* multiSet and multiMerge take arrays of key-value array pairs that match
|
||||||
* the output of multiGet, e.g.
|
* the output of multiGet, e.g. Returns a `Promise` object.
|
||||||
*
|
*
|
||||||
* multiSet([['k1', 'val1'], ['k2', 'val2']], cb);
|
* multiSet([['k1', 'val1'], ['k2', 'val2']], cb);
|
||||||
*/
|
*/
|
||||||
multiSet: function(
|
multiSet: function(
|
||||||
keyValuePairs: Array<Array<string>>,
|
keyValuePairs: Array<Array<string>>,
|
||||||
callback: ?(errors: ?Array<Error>) => void
|
callback: ?(errors: ?Array<Error>) => void
|
||||||
): void {
|
): Promise {
|
||||||
RCTAsyncStorage.multiSet(keyValuePairs, function(errors) {
|
return new Promise((resolve, reject) => {
|
||||||
callback && callback(
|
RCTAsyncStorage.multiSet(keyValuePairs, function(errors) {
|
||||||
(errors && errors.map((error) => convertError(error))) || null
|
var error = (errors && errors.map((error) => convertError(error))) || null;
|
||||||
);
|
callback && callback(error);
|
||||||
|
if (errors) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete all the keys in the `keys` array.
|
* Delete all the keys in the `keys` array. Returns a `Promise` object.
|
||||||
*/
|
*/
|
||||||
multiRemove: function(
|
multiRemove: function(
|
||||||
keys: Array<string>,
|
keys: Array<string>,
|
||||||
callback: ?(errors: ?Array<Error>) => void
|
callback: ?(errors: ?Array<Error>) => void
|
||||||
): void {
|
): Promise {
|
||||||
RCTAsyncStorage.multiRemove(keys, function(errors) {
|
return new Promise((resolve, reject) => {
|
||||||
callback && callback(
|
RCTAsyncStorage.multiRemove(keys, function(errors) {
|
||||||
(errors && errors.map((error) => convertError(error))) || null
|
var error = (errors && errors.map((error) => convertError(error))) || null;
|
||||||
);
|
callback && callback(error);
|
||||||
|
if (errors) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges existing values with input values, assuming they are stringified
|
* Merges existing values with input values, assuming they are stringified
|
||||||
* json.
|
* json. Returns a `Promise` object.
|
||||||
*
|
*
|
||||||
* Not supported by all native implementations.
|
* Not supported by all native implementations.
|
||||||
*/
|
*/
|
||||||
multiMerge: function(
|
multiMerge: function(
|
||||||
keyValuePairs: Array<Array<string>>,
|
keyValuePairs: Array<Array<string>>,
|
||||||
callback: ?(errors: ?Array<Error>) => void
|
callback: ?(errors: ?Array<Error>) => void
|
||||||
): void {
|
): Promise {
|
||||||
RCTAsyncStorage.multiMerge(keyValuePairs, function(errors) {
|
return new Promise((resolve, reject) => {
|
||||||
callback && callback(
|
RCTAsyncStorage.multiMerge(keyValuePairs, function(errors) {
|
||||||
(errors && errors.map((error) => convertError(error))) || null
|
var error = (errors && errors.map((error) => convertError(error))) || null;
|
||||||
);
|
callback && callback(error);
|
||||||
|
if (errors) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
- (UIView *)view
|
- (UIView *)view
|
||||||
{
|
{
|
||||||
return [[UIView alloc] init];
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (RCTShadowView *)shadowView
|
- (RCTShadowView *)shadowView
|
||||||
|
@ -26,4 +26,3 @@
|
||||||
RCT_EXPORT_SHADOW_PROPERTY(text, NSString)
|
RCT_EXPORT_SHADOW_PROPERTY(text, NSString)
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ extern NSString *const RCTReactTagAttributeName;
|
||||||
@property (nonatomic, copy) NSString *fontStyle;
|
@property (nonatomic, copy) NSString *fontStyle;
|
||||||
@property (nonatomic, assign) BOOL isHighlighted;
|
@property (nonatomic, assign) BOOL isHighlighted;
|
||||||
@property (nonatomic, assign) CGFloat lineHeight;
|
@property (nonatomic, assign) CGFloat lineHeight;
|
||||||
@property (nonatomic, assign) NSInteger maxNumberOfLines;
|
@property (nonatomic, assign) NSUInteger maximumNumberOfLines;
|
||||||
@property (nonatomic, assign) CGSize shadowOffset;
|
@property (nonatomic, assign) CGSize shadowOffset;
|
||||||
@property (nonatomic, assign) NSTextAlignment textAlign;
|
@property (nonatomic, assign) NSTextAlignment textAlign;
|
||||||
|
|
||||||
|
@ -31,6 +31,8 @@ extern NSString *const RCTReactTagAttributeName;
|
||||||
@property (nonatomic, strong) UIFont *font;
|
@property (nonatomic, strong) UIFont *font;
|
||||||
@property (nonatomic, assign) NSLineBreakMode truncationMode;
|
@property (nonatomic, assign) NSLineBreakMode truncationMode;
|
||||||
|
|
||||||
- (NSAttributedString *)attributedString;
|
@property (nonatomic, copy, readonly) NSAttributedString *attributedString;
|
||||||
|
@property (nonatomic, strong, readonly) NSLayoutManager *layoutManager;
|
||||||
|
@property (nonatomic, strong, readonly) NSTextContainer *textContainer;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -20,9 +20,17 @@ NSString *const RCTReactTagAttributeName = @"ReactTagAttributeName";
|
||||||
static css_dim_t RCTMeasure(void *context, float width)
|
static css_dim_t RCTMeasure(void *context, float width)
|
||||||
{
|
{
|
||||||
RCTShadowText *shadowText = (__bridge RCTShadowText *)context;
|
RCTShadowText *shadowText = (__bridge RCTShadowText *)context;
|
||||||
CGSize computedSize = [[shadowText attributedString] boundingRectWithSize:(CGSize){isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX}
|
|
||||||
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
|
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:[shadowText attributedString]];
|
||||||
context:nil].size;
|
[textStorage addLayoutManager:shadowText.layoutManager];
|
||||||
|
|
||||||
|
shadowText.textContainer.size = CGSizeMake(isnan(width) ? CGFLOAT_MAX : width, CGFLOAT_MAX);
|
||||||
|
shadowText.layoutManager.textStorage = textStorage;
|
||||||
|
[shadowText.layoutManager ensureLayoutForTextContainer:shadowText.textContainer];
|
||||||
|
|
||||||
|
CGSize computedSize = [shadowText.layoutManager usedRectForTextContainer:shadowText.textContainer].size;
|
||||||
|
|
||||||
|
[textStorage removeLayoutManager:shadowText.layoutManager];
|
||||||
|
|
||||||
css_dim_t result;
|
css_dim_t result;
|
||||||
result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width);
|
result.dimensions[CSS_WIDTH] = RCTCeilPixelValue(computedSize.width);
|
||||||
|
@ -30,8 +38,9 @@ static css_dim_t RCTMeasure(void *context, float width)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@implementation RCTShadowText
|
@implementation RCTShadowText {
|
||||||
{
|
NSLayoutManager *_layoutManager;
|
||||||
|
NSTextContainer *_textContainer;
|
||||||
NSAttributedString *_cachedAttributedString;
|
NSAttributedString *_cachedAttributedString;
|
||||||
UIFont *_font;
|
UIFont *_font;
|
||||||
}
|
}
|
||||||
|
@ -41,7 +50,15 @@ static css_dim_t RCTMeasure(void *context, float width)
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
_fontSize = NAN;
|
_fontSize = NAN;
|
||||||
_isHighlighted = NO;
|
_isHighlighted = NO;
|
||||||
|
|
||||||
|
_textContainer = [[NSTextContainer alloc] init];
|
||||||
|
_textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
|
||||||
|
_textContainer.lineFragmentPadding = 0.0;
|
||||||
|
|
||||||
|
_layoutManager = [[NSLayoutManager alloc] init];
|
||||||
|
[_layoutManager addTextContainer:_textContainer];
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,11 +218,31 @@ RCT_TEXT_PROPERTY(FontFamily, _fontFamily, NSString *);
|
||||||
RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat);
|
RCT_TEXT_PROPERTY(FontSize, _fontSize, CGFloat);
|
||||||
RCT_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *);
|
RCT_TEXT_PROPERTY(FontWeight, _fontWeight, NSString *);
|
||||||
RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat);
|
RCT_TEXT_PROPERTY(LineHeight, _lineHeight, CGFloat);
|
||||||
RCT_TEXT_PROPERTY(MaxNumberOfLines, _maxNumberOfLines, NSInteger);
|
|
||||||
RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize);
|
RCT_TEXT_PROPERTY(ShadowOffset, _shadowOffset, CGSize);
|
||||||
RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment);
|
RCT_TEXT_PROPERTY(TextAlign, _textAlign, NSTextAlignment);
|
||||||
RCT_TEXT_PROPERTY(TruncationMode, _truncationMode, NSLineBreakMode);
|
|
||||||
RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL);
|
RCT_TEXT_PROPERTY(IsHighlighted, _isHighlighted, BOOL);
|
||||||
RCT_TEXT_PROPERTY(Font, _font, UIFont *);
|
RCT_TEXT_PROPERTY(Font, _font, UIFont *);
|
||||||
|
|
||||||
|
- (NSLineBreakMode)truncationMode
|
||||||
|
{
|
||||||
|
return _textContainer.lineBreakMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setTruncationMode:(NSLineBreakMode)truncationMode
|
||||||
|
{
|
||||||
|
_textContainer.lineBreakMode = truncationMode;
|
||||||
|
[self dirtyText];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSUInteger)maximumNumberOfLines
|
||||||
|
{
|
||||||
|
return _textContainer.maximumNumberOfLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines
|
||||||
|
{
|
||||||
|
_textContainer.maximumNumberOfLines = maximumNumberOfLines;
|
||||||
|
[self dirtyText];
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -11,9 +11,10 @@
|
||||||
|
|
||||||
@interface RCTText : UIView
|
@interface RCTText : UIView
|
||||||
|
|
||||||
|
@property (nonatomic, strong) NSLayoutManager *layoutManager;
|
||||||
|
@property (nonatomic, strong) NSTextContainer *textContainer;
|
||||||
@property (nonatomic, copy) NSAttributedString *attributedText;
|
@property (nonatomic, copy) NSAttributedString *attributedText;
|
||||||
@property (nonatomic, assign) NSLineBreakMode lineBreakMode;
|
|
||||||
@property (nonatomic, assign) NSUInteger numberOfLines;
|
|
||||||
@property (nonatomic, assign) UIEdgeInsets contentInset;
|
@property (nonatomic, assign) UIEdgeInsets contentInset;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -23,15 +23,7 @@
|
||||||
- (instancetype)initWithFrame:(CGRect)frame
|
- (instancetype)initWithFrame:(CGRect)frame
|
||||||
{
|
{
|
||||||
if ((self = [super initWithFrame:frame])) {
|
if ((self = [super initWithFrame:frame])) {
|
||||||
_textContainer = [[NSTextContainer alloc] init];
|
|
||||||
_textContainer.lineBreakMode = NSLineBreakByTruncatingTail;
|
|
||||||
_textContainer.lineFragmentPadding = 0.0;
|
|
||||||
|
|
||||||
_layoutManager = [[NSLayoutManager alloc] init];
|
|
||||||
[_layoutManager addTextContainer:_textContainer];
|
|
||||||
|
|
||||||
_textStorage = [[NSTextStorage alloc] init];
|
_textStorage = [[NSTextStorage alloc] init];
|
||||||
[_textStorage addLayoutManager:_layoutManager];
|
|
||||||
|
|
||||||
self.contentMode = UIViewContentModeRedraw;
|
self.contentMode = UIViewContentModeRedraw;
|
||||||
}
|
}
|
||||||
|
@ -50,25 +42,31 @@
|
||||||
[self setNeedsDisplay];
|
[self setNeedsDisplay];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)numberOfLines
|
- (void)setTextContainer:(NSTextContainer *)textContainer
|
||||||
{
|
{
|
||||||
return _textContainer.maximumNumberOfLines;
|
if ([_textContainer isEqual:textContainer]) return;
|
||||||
}
|
|
||||||
|
_textContainer = textContainer;
|
||||||
|
|
||||||
|
for (NSInteger i = _layoutManager.textContainers.count - 1; i >= 0; i--) {
|
||||||
|
[_layoutManager removeTextContainerAtIndex:i];
|
||||||
|
}
|
||||||
|
[_layoutManager addTextContainer:_textContainer];
|
||||||
|
|
||||||
- (void)setNumberOfLines:(NSUInteger)numberOfLines
|
|
||||||
{
|
|
||||||
_textContainer.maximumNumberOfLines = numberOfLines;
|
|
||||||
[self setNeedsDisplay];
|
[self setNeedsDisplay];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSLineBreakMode)lineBreakMode
|
- (void)setLayoutManager:(NSLayoutManager *)layoutManager
|
||||||
{
|
{
|
||||||
return _textContainer.lineBreakMode;
|
if ([_layoutManager isEqual:layoutManager]) return;
|
||||||
}
|
|
||||||
|
_layoutManager = layoutManager;
|
||||||
|
|
||||||
|
for (NSLayoutManager *existingLayoutManager in _textStorage.layoutManagers) {
|
||||||
|
[_textStorage removeLayoutManager:existingLayoutManager];
|
||||||
|
}
|
||||||
|
[_textStorage addLayoutManager:_layoutManager];
|
||||||
|
|
||||||
- (void)setLineBreakMode:(NSLineBreakMode)lineBreakMode
|
|
||||||
{
|
|
||||||
_textContainer.lineBreakMode = lineBreakMode;
|
|
||||||
[self setNeedsDisplay];
|
[self setNeedsDisplay];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,15 +33,6 @@
|
||||||
#pragma mark - View properties
|
#pragma mark - View properties
|
||||||
|
|
||||||
RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor, UIColor)
|
RCT_REMAP_VIEW_PROPERTY(containerBackgroundColor, backgroundColor, UIColor)
|
||||||
RCT_CUSTOM_VIEW_PROPERTY(numberOfLines, NSInteger, RCTText)
|
|
||||||
{
|
|
||||||
NSLineBreakMode truncationMode = NSLineBreakByClipping;
|
|
||||||
view.numberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.numberOfLines;
|
|
||||||
if (view.numberOfLines > 0) {
|
|
||||||
truncationMode = NSLineBreakByTruncatingTail;
|
|
||||||
}
|
|
||||||
view.lineBreakMode = truncationMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Shadow properties
|
#pragma mark - Shadow properties
|
||||||
|
|
||||||
|
@ -65,8 +56,8 @@ RCT_CUSTOM_SHADOW_PROPERTY(containerBackgroundColor, UIColor, RCTShadowText)
|
||||||
RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
|
RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
|
||||||
{
|
{
|
||||||
NSLineBreakMode truncationMode = NSLineBreakByClipping;
|
NSLineBreakMode truncationMode = NSLineBreakByClipping;
|
||||||
view.maxNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maxNumberOfLines;
|
view.maximumNumberOfLines = json ? [RCTConvert NSInteger:json] : defaultView.maximumNumberOfLines;
|
||||||
if (view.maxNumberOfLines > 0) {
|
if (view.maximumNumberOfLines > 0) {
|
||||||
truncationMode = NSLineBreakByTruncatingTail;
|
truncationMode = NSLineBreakByTruncatingTail;
|
||||||
}
|
}
|
||||||
view.truncationMode = truncationMode;
|
view.truncationMode = truncationMode;
|
||||||
|
@ -124,12 +115,16 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
|
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowText *)shadowView
|
||||||
{
|
{
|
||||||
NSNumber *reactTag = shadowView.reactTag;
|
NSNumber *reactTag = shadowView.reactTag;
|
||||||
UIEdgeInsets padding = shadowView.paddingAsInsets;
|
UIEdgeInsets padding = shadowView.paddingAsInsets;
|
||||||
|
|
||||||
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||||
((RCTText *)viewRegistry[reactTag]).contentInset = padding;
|
RCTText *text = (RCTText *)viewRegistry[reactTag];
|
||||||
|
text.contentInset = padding;
|
||||||
|
text.layoutManager = shadowView.layoutManager;
|
||||||
|
text.textContainer = shadowView.textContainer;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* iOS stub for BackAndroid.android.js
|
||||||
|
*
|
||||||
|
* @providesModule BackAndroid
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var warning = require('warning');
|
||||||
|
|
||||||
|
function platformWarn() {
|
||||||
|
warning(false, 'BackAndroid is not supported on this platform.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var BackAndroid = {
|
||||||
|
exitApp: platformWarn,
|
||||||
|
addEventListener: platformWarn,
|
||||||
|
removeEventListener: platformWarn,
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = BackAndroid;
|
|
@ -1,16 +0,0 @@
|
||||||
/**
|
|
||||||
* To lower the risk of breaking things on iOS, we are stubbing out the
|
|
||||||
* BackStack for now. See Backstack.android.js
|
|
||||||
*
|
|
||||||
* @providesModule Backstack
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var Backstack = {
|
|
||||||
pushNavigation: () => {},
|
|
||||||
resetToBefore: () => {},
|
|
||||||
removeComponentHistory: () => {},
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = Backstack;
|
|
|
@ -28,6 +28,11 @@ typedef NSArray *(^RCTBridgeModuleProviderBlock)(void);
|
||||||
|
|
||||||
extern NSString *const RCTReloadBridge;
|
extern NSString *const RCTReloadBridge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function returns the module name for a given class.
|
||||||
|
*/
|
||||||
|
extern NSString *RCTBridgeModuleNameForClass(Class bridgeModuleClass);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Async batched bridge used to communicate with the JavaScript application.
|
* Async batched bridge used to communicate with the JavaScript application.
|
||||||
*/
|
*/
|
||||||
|
@ -81,14 +86,6 @@ extern NSString *const RCTReloadBridge;
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, readonly) dispatch_queue_t shadowQueue;
|
@property (nonatomic, readonly) dispatch_queue_t shadowQueue;
|
||||||
|
|
||||||
/**
|
|
||||||
* Global logging function that will print to both xcode and JS debugger consoles.
|
|
||||||
*
|
|
||||||
* NOTE: Use via RCTLog* macros defined in RCTLog.h
|
|
||||||
* TODO (#5906496): should log function be exposed here, or could it be a module?
|
|
||||||
*/
|
|
||||||
+ (void)log:(NSArray *)objects level:(NSString *)level;
|
|
||||||
|
|
||||||
@property (nonatomic, copy, readonly) NSDictionary *launchOptions;
|
@property (nonatomic, copy, readonly) NSDictionary *launchOptions;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#import "RCTJavaScriptLoader.h"
|
#import "RCTJavaScriptLoader.h"
|
||||||
#import "RCTKeyCommands.h"
|
#import "RCTKeyCommands.h"
|
||||||
#import "RCTLog.h"
|
#import "RCTLog.h"
|
||||||
|
#import "RCTRedBox.h"
|
||||||
#import "RCTRootView.h"
|
#import "RCTRootView.h"
|
||||||
#import "RCTSparseArray.h"
|
#import "RCTSparseArray.h"
|
||||||
#import "RCTUtils.h"
|
#import "RCTUtils.h"
|
||||||
|
@ -41,10 +42,7 @@ typedef NS_ENUM(NSUInteger, RCTBridgeFields) {
|
||||||
|
|
||||||
NSString *const RCTReloadBridge = @"RCTReloadBridge";
|
NSString *const RCTReloadBridge = @"RCTReloadBridge";
|
||||||
|
|
||||||
/**
|
NSString *RCTBridgeModuleNameForClass(Class cls)
|
||||||
* This function returns the module name for a given class.
|
|
||||||
*/
|
|
||||||
static NSString *RCTModuleNameForClass(Class cls)
|
|
||||||
{
|
{
|
||||||
return [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls);
|
return [cls respondsToSelector:@selector(moduleName)] ? [cls moduleName] : NSStringFromClass(cls);
|
||||||
}
|
}
|
||||||
|
@ -92,7 +90,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
||||||
[(NSMutableArray *)modules addObject:cls];
|
[(NSMutableArray *)modules addObject:cls];
|
||||||
|
|
||||||
// Add module name
|
// Add module name
|
||||||
NSString *moduleName = RCTModuleNameForClass(cls);
|
NSString *moduleName = RCTBridgeModuleNameForClass(cls);
|
||||||
[(NSMutableArray *)RCTModuleNamesByID addObject:moduleName];
|
[(NSMutableArray *)RCTModuleNamesByID addObject:moduleName];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -187,7 +185,7 @@ static Class _globalExecutorClass;
|
||||||
RCT_ARG_BLOCK( \
|
RCT_ARG_BLOCK( \
|
||||||
if (json && ![json isKindOfClass:[_class class]]) { \
|
if (json && ![json isKindOfClass:[_class class]]) { \
|
||||||
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
|
RCTLogError(@"Argument %tu (%@) of %@.%@ should be of type %@", index, \
|
||||||
json, RCTModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
|
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, [_class class]); \
|
||||||
return; \
|
return; \
|
||||||
} \
|
} \
|
||||||
_logic \
|
_logic \
|
||||||
|
@ -203,7 +201,7 @@ static Class _globalExecutorClass;
|
||||||
RCT_ARG_BLOCK( \
|
RCT_ARG_BLOCK( \
|
||||||
if (json && ![json respondsToSelector:@selector(_selector)]) { \
|
if (json && ![json respondsToSelector:@selector(_selector)]) { \
|
||||||
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
|
RCTLogError(@"Argument %tu (%@) of %@.%@ does not respond to selector: %@", \
|
||||||
index, json, RCTModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
|
index, json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName, @#_selector); \
|
||||||
return; \
|
return; \
|
||||||
} \
|
} \
|
||||||
_type value = [json _selector]; \
|
_type value = [json _selector]; \
|
||||||
|
@ -231,7 +229,7 @@ static Class _globalExecutorClass;
|
||||||
RCT_ARG_BLOCK(
|
RCT_ARG_BLOCK(
|
||||||
if (json && ![json isKindOfClass:[NSNumber class]]) {
|
if (json && ![json isKindOfClass:[NSNumber class]]) {
|
||||||
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
|
RCTLogError(@"Argument %tu (%@) of %@.%@ should be a number", index,
|
||||||
json, RCTModuleNameForClass(_moduleClass), _JSMethodName);
|
json, RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
// Marked as autoreleasing, because NSInvocation doesn't retain arguments
|
||||||
|
@ -268,7 +266,7 @@ static Class _globalExecutorClass;
|
||||||
// Safety check
|
// Safety check
|
||||||
if (arguments.count != _argumentBlocks.count) {
|
if (arguments.count != _argumentBlocks.count) {
|
||||||
RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd",
|
RCTLogError(@"%@.%@ was called with %zd arguments, but expects %zd",
|
||||||
RCTModuleNameForClass(_moduleClass), _JSMethodName,
|
RCTBridgeModuleNameForClass(_moduleClass), _JSMethodName,
|
||||||
arguments.count, _argumentBlocks.count);
|
arguments.count, _argumentBlocks.count);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -544,7 +542,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
// Register passed-in module instances
|
// Register passed-in module instances
|
||||||
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
|
||||||
for (id<RCTBridgeModule> module in _moduleProvider ? _moduleProvider() : nil) {
|
for (id<RCTBridgeModule> module in _moduleProvider ? _moduleProvider() : nil) {
|
||||||
preregisteredModules[RCTModuleNameForClass([module class])] = module;
|
preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate modules
|
// Instantiate modules
|
||||||
|
@ -895,27 +893,18 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
||||||
return (_latestJSExecutor != nil && [_latestJSExecutor isValid]);
|
return (_latestJSExecutor != nil && [_latestJSExecutor isValid]);
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (void)log:(NSArray *)objects level:(NSString *)level
|
+ (void)logMessage:(NSString *)message level:(NSString *)level
|
||||||
{
|
{
|
||||||
if (!_latestJSExecutor || ![_latestJSExecutor isValid]) {
|
if (!_latestJSExecutor || ![_latestJSExecutor isValid]) {
|
||||||
RCTLogError(@"ERROR: No valid JS executor to log %@.", objects);
|
RCTLogError(@"ERROR: No valid JS executor to log '%@'.", message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
NSMutableArray *args = [NSMutableArray arrayWithObject:level];
|
|
||||||
|
|
||||||
// TODO (#5906496): Find out and document why we skip the first object
|
// Note: the js executor could get invalidated while we're trying to call
|
||||||
for (id ob in [objects subarrayWithRange:(NSRange){1, [objects count] - 1}]) {
|
// this...need to watch out for that.
|
||||||
if ([NSJSONSerialization isValidJSONObject:@[ob]]) {
|
|
||||||
[args addObject:ob];
|
|
||||||
} else {
|
|
||||||
[args addObject:[ob description]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: the js executor could get invalidated while we're trying to call this...need to watch out for that.
|
|
||||||
[_latestJSExecutor executeJSCall:@"RCTLog"
|
[_latestJSExecutor executeJSCall:@"RCTLog"
|
||||||
method:@"logIfNoNativeHook"
|
method:@"logIfNoNativeHook"
|
||||||
arguments:args
|
arguments:@[level, message]
|
||||||
callback:^(id json, NSError *error) {}];
|
callback:^(id json, NSError *error) {}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ RCT_CUSTOM_CONVERTER(type, name, [json getter])
|
||||||
} \
|
} \
|
||||||
@catch (__unused NSException *e) { \
|
@catch (__unused NSException *e) { \
|
||||||
RCTLogError(@"JSON value '%@' of type '%@' cannot be converted to '%s'", \
|
RCTLogError(@"JSON value '%@' of type '%@' cannot be converted to '%s'", \
|
||||||
json, [json class], #type); \
|
json, [json classForCoder], #type); \
|
||||||
json = nil; \
|
json = nil; \
|
||||||
return code; \
|
return code; \
|
||||||
} \
|
} \
|
||||||
|
@ -181,7 +181,8 @@ RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter])
|
||||||
return default; \
|
return default; \
|
||||||
} \
|
} \
|
||||||
if (![json isKindOfClass:[NSString class]]) { \
|
if (![json isKindOfClass:[NSString class]]) { \
|
||||||
RCTLogError(@"Expected NSNumber or NSString for %s, received %@: %@", #type, [json class], json); \
|
RCTLogError(@"Expected NSNumber or NSString for %s, received %@: %@", \
|
||||||
|
#type, [json classForCoder], json); \
|
||||||
} \
|
} \
|
||||||
id value = mapping[json]; \
|
id value = mapping[json]; \
|
||||||
if(!value && [json description].length > 0) { \
|
if(!value && [json description].length > 0) { \
|
||||||
|
|
|
@ -45,7 +45,7 @@ RCT_CONVERTER(NSString *, NSString, description)
|
||||||
}
|
}
|
||||||
return number;
|
return number;
|
||||||
} else if (json && json != [NSNull null]) {
|
} else if (json && json != [NSNull null]) {
|
||||||
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a number", json, [json class]);
|
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a number", json, [json classForCoder]);
|
||||||
}
|
}
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ RCT_CONVERTER(NSString *, NSString, description)
|
||||||
+ (NSURL *)NSURL:(id)json
|
+ (NSURL *)NSURL:(id)json
|
||||||
{
|
{
|
||||||
if (![json isKindOfClass:[NSString class]]) {
|
if (![json isKindOfClass:[NSString class]]) {
|
||||||
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json class], json);
|
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json classForCoder], json);
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ RCT_CONVERTER(NSString *, NSString, description)
|
||||||
}
|
}
|
||||||
return date;
|
return date;
|
||||||
} else if (json && json != [NSNull null]) {
|
} else if (json && json != [NSNull null]) {
|
||||||
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a date", json, [json class]);
|
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a date", json, [json classForCoder]);
|
||||||
}
|
}
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,7 @@ RCT_ENUM_CONVERTER(UIBarStyle, (@{
|
||||||
json = [json mutableCopy]; \
|
json = [json mutableCopy]; \
|
||||||
for (NSString *alias in aliases) { \
|
for (NSString *alias in aliases) { \
|
||||||
NSString *key = aliases[alias]; \
|
NSString *key = aliases[alias]; \
|
||||||
NSNumber *number = json[key]; \
|
NSNumber *number = json[alias]; \
|
||||||
if (number) { \
|
if (number) { \
|
||||||
RCTLogWarn(@"Using deprecated '%@' property for '%s'. Use '%@' instead.", alias, #type, key); \
|
RCTLogWarn(@"Using deprecated '%@' property for '%s'. Use '%@' instead.", alias, #type, key); \
|
||||||
((NSMutableDictionary *)json)[key] = number; \
|
((NSMutableDictionary *)json)[key] = number; \
|
||||||
|
@ -234,7 +234,8 @@ RCT_ENUM_CONVERTER(UIBarStyle, (@{
|
||||||
((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \
|
((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \
|
||||||
} \
|
} \
|
||||||
} else if (json && json != [NSNull null]) { \
|
} else if (json && json != [NSNull null]) { \
|
||||||
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \
|
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", \
|
||||||
|
#type, [json classForCoder], json); \
|
||||||
} \
|
} \
|
||||||
return result; \
|
return result; \
|
||||||
} \
|
} \
|
||||||
|
@ -511,8 +512,8 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
||||||
|
|
||||||
} else if (json && ![json isKindOfClass:[NSNull class]]) {
|
} else if (json && ![json isKindOfClass:[NSNull class]]) {
|
||||||
|
|
||||||
RCTLogError(@"Expected NSArray, NSDictionary or NSString for UIColor, \
|
RCTLogError(@"Expected NSArray, NSDictionary or NSString for UIColor, received %@: %@",
|
||||||
received %@: %@", [json class], json);
|
[json classForCoder], json);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default color
|
// Default color
|
||||||
|
@ -538,7 +539,7 @@ RCT_CGSTRUCT_CONVERTER(CGAffineTransform, (@[
|
||||||
// image itself) so as to reduce overhead on subsequent checks of the same input
|
// image itself) so as to reduce overhead on subsequent checks of the same input
|
||||||
|
|
||||||
if (![json isKindOfClass:[NSString class]]) {
|
if (![json isKindOfClass:[NSString class]]) {
|
||||||
RCTLogError(@"Expected NSString for UIImage, received %@: %@", [json class], json);
|
RCTLogError(@"Expected NSString for UIImage, received %@: %@", [json classForCoder], json);
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,58 +7,138 @@
|
||||||
* of patent rights can be found in the PATENTS file in the same directory.
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
#import "RCTAssert.h"
|
#import "RCTAssert.h"
|
||||||
#import "RCTRedBox.h"
|
|
||||||
|
|
||||||
#define RCTLOG_INFO 1
|
|
||||||
#define RCTLOG_WARN 2
|
|
||||||
#define RCTLOG_ERROR 3
|
|
||||||
#define RCTLOG_MUSTFIX 4
|
|
||||||
|
|
||||||
// If set to e.g. `RCTLOG_ERROR`, will assert after logging the first error.
|
|
||||||
#if DEBUG
|
|
||||||
#define RCTLOG_FATAL_LEVEL RCTLOG_MUSTFIX
|
|
||||||
#define RCTLOG_REDBOX_LEVEL RCTLOG_ERROR
|
|
||||||
#else
|
|
||||||
#define RCTLOG_FATAL_LEVEL (RCTLOG_MUSTFIX + 1)
|
|
||||||
#define RCTLOG_REDBOX_LEVEL (RCTLOG_MUSTFIX + 1)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// If defined, only log messages that match this regex will fatal
|
|
||||||
#define RCTLOG_FATAL_REGEX nil
|
|
||||||
|
|
||||||
extern __unsafe_unretained NSString *RCTLogLevels[];
|
|
||||||
|
|
||||||
#define _RCTLog(_level, ...) do { \
|
|
||||||
NSString *__RCTLog__levelStr = RCTLogLevels[_level - 1]; \
|
|
||||||
NSString *__RCTLog__msg = RCTLogObjects(RCTLogFormat(__FILE__, __LINE__, __PRETTY_FUNCTION__, __VA_ARGS__), __RCTLog__levelStr); \
|
|
||||||
if (_level >= RCTLOG_FATAL_LEVEL) { \
|
|
||||||
BOOL __RCTLog__fail = YES; \
|
|
||||||
if (RCTLOG_FATAL_REGEX) { \
|
|
||||||
NSRegularExpression *__RCTLog__regex = [NSRegularExpression regularExpressionWithPattern:RCTLOG_FATAL_REGEX options:0 error:NULL]; \
|
|
||||||
__RCTLog__fail = [__RCTLog__regex numberOfMatchesInString:__RCTLog__msg options:0 range:NSMakeRange(0, [__RCTLog__msg length])] > 0; \
|
|
||||||
} \
|
|
||||||
RCTCAssert(!__RCTLog__fail, @"RCTLOG_FATAL_LEVEL %@: %@", __RCTLog__levelStr, __RCTLog__msg); \
|
|
||||||
} \
|
|
||||||
if (_level >= RCTLOG_REDBOX_LEVEL) { \
|
|
||||||
[[RCTRedBox sharedInstance] showErrorMessage:__RCTLog__msg]; \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define RCTLog(...) _RCTLog(RCTLOG_INFO, __VA_ARGS__)
|
|
||||||
#define RCTLogInfo(...) _RCTLog(RCTLOG_INFO, __VA_ARGS__)
|
|
||||||
#define RCTLogWarn(...) _RCTLog(RCTLOG_WARN, __VA_ARGS__)
|
|
||||||
#define RCTLogError(...) _RCTLog(RCTLOG_ERROR, __VA_ARGS__)
|
|
||||||
#define RCTLogMustFix(...) _RCTLog(RCTLOG_MUSTFIX, __VA_ARGS__)
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
NSString *RCTLogObjects(NSArray *objects, NSString *level);
|
/**
|
||||||
NSArray *RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...) NS_FORMAT_FUNCTION(4,5);
|
* Thresholds for logs to raise an assertion, or display redbox, respectively.
|
||||||
|
* You can override these values when debugging in order to tweak the default
|
||||||
|
* logging behavior.
|
||||||
|
*/
|
||||||
|
#define RCTLOG_FATAL_LEVEL RCTLogLevelMustFix
|
||||||
|
#define RCTLOG_REDBOX_LEVEL RCTLogLevelError
|
||||||
|
|
||||||
void RCTInjectLogFunction(void (^logFunction)(NSString *msg));
|
/**
|
||||||
|
* A regular expression that can be used to selectively limit the throwing of
|
||||||
|
* a exception to specific log contents.
|
||||||
|
*/
|
||||||
|
#define RCTLOG_FATAL_REGEX nil
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enum representing the severity of the log message.
|
||||||
|
*/
|
||||||
|
typedef NS_ENUM(NSInteger, RCTLogLevel) {
|
||||||
|
RCTLogLevelInfo = 1,
|
||||||
|
RCTLogLevelWarning = 2,
|
||||||
|
RCTLogLevelError = 3,
|
||||||
|
RCTLogLevelMustFix = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A block signature to be used for custom logging functions. In most cases you
|
||||||
|
* will want to pass these arguments to the RCTFormatLog function in order to
|
||||||
|
* generate a string, or use the RCTSimpleLogFunction() constructor to register
|
||||||
|
* a simple function that does not use all of the arguments.
|
||||||
|
*/
|
||||||
|
typedef void (^RCTLogFunction)(
|
||||||
|
RCTLogLevel level,
|
||||||
|
NSString *fileName,
|
||||||
|
NSNumber *lineNumber,
|
||||||
|
NSString *message
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A method to generate a string from a collection of log data. To omit any
|
||||||
|
* particular data from the log, just pass nil or zero for the argument.
|
||||||
|
*/
|
||||||
|
NSString *RCTFormatLog(
|
||||||
|
NSDate *timestamp,
|
||||||
|
NSThread *thread,
|
||||||
|
RCTLogLevel level,
|
||||||
|
NSString *fileName,
|
||||||
|
NSNumber *lineNumber,
|
||||||
|
NSString *message
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A method to generate a log function from a block with a much simpler
|
||||||
|
* template. The message passed to the simpler block is equivalent to the
|
||||||
|
* output of the RCTFormatLog() function.
|
||||||
|
*/
|
||||||
|
RCTLogFunction RCTSimpleLogFunction(void (^logFunction)(RCTLogLevel level, NSString *message));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default logging function used by RCTLogXX.
|
||||||
|
*/
|
||||||
|
extern RCTLogFunction RCTDefaultLogFunction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These methods get and set the current logging threshold. This is the level
|
||||||
|
* below which logs will be ignored. Default is RCTLogLevelInfo for debug and
|
||||||
|
* RCTLogLevelError for production.
|
||||||
|
*/
|
||||||
|
void RCTSetLogThreshold(RCTLogLevel threshold);
|
||||||
|
RCTLogLevel RCTGetLogThreshold(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These methods get and set the current logging function called by the RCTLogXX
|
||||||
|
* macros. You can use these to replace the standard behavior with custom log
|
||||||
|
* functionality.
|
||||||
|
*/
|
||||||
|
void RCTSetLogFunction(RCTLogFunction logFunction);
|
||||||
|
RCTLogFunction RCTGetLogFunction(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This appends additional code to the existing log function, without replacing
|
||||||
|
* the existing functionality. Useful if you just want to forward logs to an
|
||||||
|
* extra service without changing the default behavior.
|
||||||
|
*/
|
||||||
|
void RCTAddLogFunction(RCTLogFunction logFunction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method adds a conditional prefix to any messages logged within the scope
|
||||||
|
* of the passed block. This is useful for adding additional context to log
|
||||||
|
* messages. The block will be performed synchronously on the current thread.
|
||||||
|
*/
|
||||||
|
void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private logging functions - ignore these.
|
||||||
|
*/
|
||||||
|
void _RCTLogFormat(RCTLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4,5);
|
||||||
|
#define _RCTLog(lvl, ...) do { \
|
||||||
|
NSString *msg = [NSString stringWithFormat:__VA_ARGS__]; \
|
||||||
|
if (lvl >= RCTLOG_FATAL_LEVEL) { \
|
||||||
|
BOOL fail = YES; \
|
||||||
|
if (RCTLOG_FATAL_REGEX) { \
|
||||||
|
if ([msg rangeOfString:RCTLOG_FATAL_REGEX options:NSRegularExpressionSearch].length) { \
|
||||||
|
fail = NO; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
RCTCAssert(!fail, @"FATAL ERROR: %@", msg); \
|
||||||
|
}\
|
||||||
|
_RCTLogFormat(lvl, __FILE__, __LINE__, __VA_ARGS__); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy injection function - don't use this.
|
||||||
|
*/
|
||||||
|
void RCTInjectLogFunction(void (^)(NSString *msg));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logging macros. Use these to log information, warnings and errors in your
|
||||||
|
* own code.
|
||||||
|
*/
|
||||||
|
#define RCTLog(...) _RCTLog(RCTLogLevelInfo, __VA_ARGS__)
|
||||||
|
#define RCTLogInfo(...) _RCTLog(RCTLogLevelInfo, __VA_ARGS__)
|
||||||
|
#define RCTLogWarn(...) _RCTLog(RCTLogLevelWarning, __VA_ARGS__)
|
||||||
|
#define RCTLogError(...) _RCTLog(RCTLogLevelError, __VA_ARGS__)
|
||||||
|
#define RCTLogMustFix(...) _RCTLog(RCTLogLevelMustFix, __VA_ARGS__)
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,85 +9,218 @@
|
||||||
|
|
||||||
#import "RCTLog.h"
|
#import "RCTLog.h"
|
||||||
|
|
||||||
|
#import "RCTAssert.h"
|
||||||
#import "RCTBridge.h"
|
#import "RCTBridge.h"
|
||||||
|
#import "RCTRedBox.h"
|
||||||
|
|
||||||
__unsafe_unretained NSString *RCTLogLevels[] = {
|
@interface RCTBridge (Logging)
|
||||||
@"info",
|
|
||||||
@"warn",
|
+ (void)logMessage:(NSString *)message level:(NSString *)level;
|
||||||
@"error",
|
|
||||||
@"mustfix"
|
@end
|
||||||
|
|
||||||
|
static NSString *const RCTLogPrefixStack = @"RCTLogPrefixStack";
|
||||||
|
|
||||||
|
const char *RCTLogLevels[] = {
|
||||||
|
"info",
|
||||||
|
"warn",
|
||||||
|
"error",
|
||||||
|
"mustfix"
|
||||||
};
|
};
|
||||||
|
|
||||||
static void (^RCTInjectedLogFunction)(NSString *msg);
|
static RCTLogFunction RCTCurrentLogFunction;
|
||||||
|
static RCTLogLevel RCTCurrentLogThreshold;
|
||||||
|
|
||||||
void RCTInjectLogFunction(void (^logFunction)(NSString *msg)) {
|
void RCTLogSetup(void) __attribute__((constructor));
|
||||||
RCTInjectedLogFunction = logFunction;
|
void RCTLogSetup()
|
||||||
}
|
|
||||||
|
|
||||||
static inline NSString *_RCTLogPreamble(const char *file, int lineNumber, const char *funcName)
|
|
||||||
{
|
{
|
||||||
NSString *threadName = [[NSThread currentThread] name];
|
RCTCurrentLogFunction = RCTDefaultLogFunction;
|
||||||
NSString *fileName=[[NSString stringWithUTF8String:file] lastPathComponent];
|
|
||||||
if (!threadName || threadName.length <= 0) {
|
|
||||||
threadName = [NSString stringWithFormat:@"%p", [NSThread currentThread]];
|
|
||||||
}
|
|
||||||
return [NSString stringWithFormat:@"[RCTLog][tid:%@][%@:%d]>", threadName, fileName, lineNumber];
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO (#5906496): Does this need to be tied to RCTBridge?
|
#if DEBUG
|
||||||
NSString *RCTLogObjects(NSArray *objects, NSString *level)
|
RCTCurrentLogThreshold = RCTLogLevelInfo - 1;
|
||||||
{
|
#else
|
||||||
NSString *str = objects[0];
|
RCTCurrentLogThreshold = RCTLogLevelError;
|
||||||
#if TARGET_IPHONE_SIMULATOR
|
|
||||||
if ([RCTBridge hasValidJSExecutor]) {
|
|
||||||
fprintf(stderr, "%s\n", [str UTF8String]); // don't print timestamps and other junk
|
|
||||||
[RCTBridge log:objects level:level];
|
|
||||||
} else
|
|
||||||
#endif
|
#endif
|
||||||
{
|
|
||||||
// Print normal errors with timestamps when not in simulator.
|
|
||||||
// Non errors are already compiled out above, so log as error here.
|
|
||||||
if (RCTInjectedLogFunction) {
|
|
||||||
RCTInjectedLogFunction(str);
|
|
||||||
} else {
|
|
||||||
NSLog(@">\n %@", str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns array of objects. First arg is a simple string to print, remaining args
|
RCTLogFunction RCTDefaultLogFunction = ^(
|
||||||
// are objects to pass through to the debugger so they are inspectable in the console.
|
RCTLogLevel level,
|
||||||
NSArray *RCTLogFormat(const char *file, int lineNumber, const char *funcName, NSString *format, ...)
|
NSString *fileName,
|
||||||
|
NSNumber *lineNumber,
|
||||||
|
NSString *message
|
||||||
|
)
|
||||||
{
|
{
|
||||||
va_list args;
|
NSString *log = RCTFormatLog(
|
||||||
va_start(args, format);
|
[NSDate date], [NSThread currentThread], level, fileName, lineNumber, message
|
||||||
NSString *preamble = _RCTLogPreamble(file, lineNumber, funcName);
|
);
|
||||||
|
fprintf(stderr, "%s\n", log.UTF8String);
|
||||||
|
};
|
||||||
|
|
||||||
// Pull out NSObjects so we can pass them through as inspectable objects to the js debugger
|
void RCTSetLogFunction(RCTLogFunction logFunction)
|
||||||
NSArray *formatParts = [format componentsSeparatedByString:@"%"];
|
{
|
||||||
NSMutableArray *objects = [NSMutableArray arrayWithObject:preamble];
|
RCTCurrentLogFunction = logFunction;
|
||||||
BOOL valid = YES;
|
}
|
||||||
for (int i = 0; i < formatParts.count; i++) {
|
|
||||||
if (i == 0) { // first part is always a string
|
RCTLogFunction RCTGetLogFunction()
|
||||||
[objects addObject:formatParts[i]];
|
{
|
||||||
|
return RCTCurrentLogFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RCTAddLogFunction(RCTLogFunction logFunction)
|
||||||
|
{
|
||||||
|
RCTLogFunction existing = RCTCurrentLogFunction;
|
||||||
|
if (existing) {
|
||||||
|
RCTCurrentLogFunction = ^(RCTLogLevel level,
|
||||||
|
NSString *fileName,
|
||||||
|
NSNumber *lineNumber,
|
||||||
|
NSString *message) {
|
||||||
|
|
||||||
|
existing(level, fileName, lineNumber, message);
|
||||||
|
logFunction(level, fileName, lineNumber, message);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
RCTCurrentLogFunction = logFunction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RCTPerformBlockWithLogPrefix(void (^block)(void), NSString *prefix)
|
||||||
|
{
|
||||||
|
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
|
||||||
|
NSMutableArray *prefixStack = threadDictionary[RCTLogPrefixStack];
|
||||||
|
if (!prefixStack) {
|
||||||
|
prefixStack = [[NSMutableArray alloc] init];
|
||||||
|
threadDictionary[RCTLogPrefixStack] = prefixStack;
|
||||||
|
}
|
||||||
|
[prefixStack addObject:prefix];
|
||||||
|
block();
|
||||||
|
[prefixStack removeLastObject];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *RCTFormatLog(
|
||||||
|
NSDate *timestamp,
|
||||||
|
NSThread *thread,
|
||||||
|
RCTLogLevel level,
|
||||||
|
NSString *fileName,
|
||||||
|
NSNumber *lineNumber,
|
||||||
|
NSString *message
|
||||||
|
)
|
||||||
|
{
|
||||||
|
NSMutableString *log = [[NSMutableString alloc] init];
|
||||||
|
if (timestamp) {
|
||||||
|
static NSDateFormatter *formatter;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
formatter = [[NSDateFormatter alloc] init];
|
||||||
|
formatter.dateFormat = formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS ";
|
||||||
|
});
|
||||||
|
[log appendString:[formatter stringFromDate:timestamp]];
|
||||||
|
}
|
||||||
|
[log appendString:@"[react]"];
|
||||||
|
if (level) {
|
||||||
|
[log appendFormat:@"[%s]", RCTLogLevels[level - 1]];
|
||||||
|
}
|
||||||
|
if (thread) {
|
||||||
|
NSString *threadName = thread.name;
|
||||||
|
if (threadName.length == 0) {
|
||||||
|
#if DEBUG
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
threadName = @(dispatch_queue_get_label(dispatch_get_current_queue()));
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
#else
|
||||||
|
threadName = [NSString stringWithFormat:@"%p", thread];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
[log appendFormat:@"[tid:%@]", threadName];
|
||||||
|
}
|
||||||
|
if (fileName) {
|
||||||
|
fileName = [fileName lastPathComponent];
|
||||||
|
if (lineNumber) {
|
||||||
|
[log appendFormat:@"[%@:%@]", fileName, lineNumber];
|
||||||
} else {
|
} else {
|
||||||
if (valid && [formatParts[i] length] && [formatParts[i] characterAtIndex:0] == '@') {
|
[log appendFormat:@"[%@]", fileName];
|
||||||
id obj = va_arg(args, id);
|
|
||||||
[objects addObject:obj ?: @"null"];
|
|
||||||
[objects addObject:[formatParts[i] substringFromIndex:1]]; // remove formatting char
|
|
||||||
} else {
|
|
||||||
// We could determine the type (double, int?) of the va_arg by parsing the formatPart, but for now we just bail.
|
|
||||||
valid = NO;
|
|
||||||
[objects addObject:[NSString stringWithFormat:@"unknown object for %%%@", formatParts[i]]];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
va_end(args);
|
if (message) {
|
||||||
va_start(args, format);
|
[log appendString:@" "];
|
||||||
NSString *strOut = [preamble stringByAppendingString:[[NSString alloc] initWithFormat:format arguments:args]];
|
[log appendString:message];
|
||||||
va_end(args);
|
}
|
||||||
NSMutableArray *objectsOut = [NSMutableArray arrayWithObject:strOut];
|
return log;
|
||||||
[objectsOut addObjectsFromArray:objects];
|
}
|
||||||
return objectsOut;
|
|
||||||
|
RCTLogFunction RCTSimpleLogFunction(void (^logFunction)(RCTLogLevel level, NSString *message))
|
||||||
|
{
|
||||||
|
return ^(RCTLogLevel level,
|
||||||
|
NSString *fileName,
|
||||||
|
NSNumber *lineNumber,
|
||||||
|
NSString *message) {
|
||||||
|
|
||||||
|
logFunction(level, RCTFormatLog(
|
||||||
|
[NSDate date], [NSThread currentThread], level, fileName, lineNumber, message
|
||||||
|
));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void _RCTLogFormat(RCTLogLevel level, const char *fileName, int lineNumber, NSString *format, ...)
|
||||||
|
{
|
||||||
|
if (RCTCurrentLogFunction && level >= RCTCurrentLogThreshold) {
|
||||||
|
|
||||||
|
// Get message
|
||||||
|
va_list args;
|
||||||
|
va_start(args, format);
|
||||||
|
__block NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
// Add prefix
|
||||||
|
NSMutableDictionary *threadDictionary = [NSThread currentThread].threadDictionary;
|
||||||
|
NSArray *prefixStack = threadDictionary[RCTLogPrefixStack];
|
||||||
|
NSString *prefix = [prefixStack lastObject];
|
||||||
|
if (prefix) {
|
||||||
|
message = [prefix stringByAppendingString:message];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call log function
|
||||||
|
RCTCurrentLogFunction(
|
||||||
|
level, fileName ? @(fileName) : nil, (lineNumber >= 0) ? @(lineNumber) : nil, message
|
||||||
|
);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
|
||||||
|
// Log to red box
|
||||||
|
if (level >= RCTLOG_REDBOX_LEVEL) {
|
||||||
|
[[RCTRedBox sharedInstance] showErrorMessage:message];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log to JS executor
|
||||||
|
if ([RCTBridge hasValidJSExecutor]) {
|
||||||
|
[RCTBridge logMessage:message level:level ? @(RCTLogLevels[level - 1]) : @"info"];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Deprecated
|
||||||
|
|
||||||
|
void RCTInjectLogFunction(void (^logFunction)(NSString *msg))
|
||||||
|
{
|
||||||
|
RCTSetLogFunction(^(RCTLogLevel level,
|
||||||
|
NSString *fileName,
|
||||||
|
NSNumber *lineNumber,
|
||||||
|
NSString *message) {
|
||||||
|
|
||||||
|
if (level > RCTLogLevelError) {
|
||||||
|
|
||||||
|
// Use custom log function
|
||||||
|
NSString *loc = fileName ? [NSString stringWithFormat:@"[%@:%@] ", fileName, lineNumber] : @"";
|
||||||
|
logFunction([loc stringByAppendingString:message]);
|
||||||
|
|
||||||
|
} else if (RCTDefaultLogFunction && level >= RCTCurrentLogThreshold) {
|
||||||
|
|
||||||
|
// Use default logger
|
||||||
|
RCTDefaultLogFunction(level, fileName, lineNumber, message);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,9 @@ extern NSString *const RCTReloadViewsNotification;
|
||||||
|
|
||||||
@interface RCTRootView : UIView <RCTInvalidating>
|
@interface RCTRootView : UIView <RCTInvalidating>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* - Designated initializer -
|
||||||
|
*/
|
||||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||||
moduleName:(NSString *)moduleName NS_DESIGNATED_INITIALIZER;
|
moduleName:(NSString *)moduleName NS_DESIGNATED_INITIALIZER;
|
||||||
|
|
||||||
|
@ -39,6 +42,10 @@ extern NSString *const RCTReloadViewsNotification;
|
||||||
*/
|
*/
|
||||||
@property (nonatomic, copy, readonly) NSString *moduleName;
|
@property (nonatomic, copy, readonly) NSString *moduleName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The bridge used by the root view. Bridges can be shared between multiple
|
||||||
|
* root views, so you can use this property to initialize another RCTRootView.
|
||||||
|
*/
|
||||||
@property (nonatomic, strong, readonly) RCTBridge *bridge;
|
@property (nonatomic, strong, readonly) RCTBridge *bridge;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
#import "RCTEventDispatcher.h"
|
#import "RCTEventDispatcher.h"
|
||||||
#import "RCTKeyCommands.h"
|
#import "RCTKeyCommands.h"
|
||||||
#import "RCTLog.h"
|
#import "RCTLog.h"
|
||||||
#import "RCTRedBox.h"
|
|
||||||
#import "RCTSourceCode.h"
|
#import "RCTSourceCode.h"
|
||||||
#import "RCTTouchHandler.h"
|
#import "RCTTouchHandler.h"
|
||||||
#import "RCTUIManager.h"
|
#import "RCTUIManager.h"
|
||||||
|
|
|
@ -38,23 +38,18 @@ static JSValueRef RCTNativeLoggingHook(JSContextRef context, JSObjectRef object,
|
||||||
if (!string) {
|
if (!string) {
|
||||||
return JSValueMakeUndefined(context);
|
return JSValueMakeUndefined(context);
|
||||||
}
|
}
|
||||||
|
NSString *message = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, string);
|
||||||
NSString *str = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, string);
|
JSStringRelease(string);
|
||||||
NSError *error = nil;
|
|
||||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:
|
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:
|
||||||
@"( stack: )?([_a-z0-9]*)@?(http://|file:///)[a-z.0-9:/_-]+/([a-z0-9_]+).includeRequire.runModule.bundle(:[0-9]+:[0-9]+)"
|
@"( stack: )?([_a-z0-9]*)@?(http://|file:///)[a-z.0-9:/_-]+/([a-z0-9_]+).includeRequire.runModule.bundle(:[0-9]+:[0-9]+)"
|
||||||
options:NSRegularExpressionCaseInsensitive
|
options:NSRegularExpressionCaseInsensitive
|
||||||
error:&error];
|
error:NULL];
|
||||||
NSString *modifiedString = [regex stringByReplacingMatchesInString:str options:0 range:NSMakeRange(0, [str length]) withTemplate:@"[$4$5] \t$2"];
|
message = [regex stringByReplacingMatchesInString:message
|
||||||
|
options:0
|
||||||
|
range:(NSRange){0, message.length}
|
||||||
|
withTemplate:@"[$4$5] \t$2"];
|
||||||
|
|
||||||
modifiedString = [@"RCTJSLog> " stringByAppendingString:modifiedString];
|
_RCTLogFormat(0, NULL, -1, @"%@", message);
|
||||||
#if TARGET_IPHONE_SIMULATOR
|
|
||||||
fprintf(stderr, "%s\n", [modifiedString UTF8String]); // don't print timestamps and other junk
|
|
||||||
#else
|
|
||||||
// Print normal errors with timestamps to files when not in simulator.
|
|
||||||
RCTLogObjects(@[modifiedString], @"log");
|
|
||||||
#endif
|
|
||||||
JSStringRelease(string);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSValueMakeUndefined(context);
|
return JSValueMakeUndefined(context);
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#import "RCTAlertManager.h"
|
#import "RCTAlertManager.h"
|
||||||
|
|
||||||
|
#import "RCTAssert.h"
|
||||||
#import "RCTLog.h"
|
#import "RCTLog.h"
|
||||||
|
|
||||||
@interface RCTAlertManager() <UIAlertViewDelegate>
|
@interface RCTAlertManager() <UIAlertViewDelegate>
|
||||||
|
|
|
@ -197,6 +197,11 @@ static UIViewAnimationCurve UIViewAnimationCurveFromRCTAnimationType(RCTAnimatio
|
||||||
|
|
||||||
@synthesize bridge = _bridge;
|
@synthesize bridge = _bridge;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declared in RCTBridge.
|
||||||
|
*/
|
||||||
|
extern NSString *RCTBridgeModuleNameForClass(Class cls);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function derives the view name automatically
|
* This function derives the view name automatically
|
||||||
* from the module name.
|
* from the module name.
|
||||||
|
@ -334,7 +339,7 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
|
||||||
|
|
||||||
dispatch_async(_bridge.shadowQueue, ^{
|
dispatch_async(_bridge.shadowQueue, ^{
|
||||||
RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag];
|
RCTShadowView *rootShadowView = _shadowViewRegistry[reactTag];
|
||||||
RCTAssert(rootShadowView != nil, @"Could not locate root view with tag %@", reactTag);
|
RCTAssert(rootShadowView != nil, @"Could not locate root view with tag #%@", reactTag);
|
||||||
rootShadowView.frame = frame;
|
rootShadowView.frame = frame;
|
||||||
[rootShadowView updateLayout];
|
[rootShadowView updateLayout];
|
||||||
|
|
||||||
|
@ -672,7 +677,7 @@ static NSString *RCTViewNameForModuleName(NSString *moduleName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView, RCTViewManager *manager)
|
static BOOL RCTCallPropertySetter(NSString *key, SEL setter, id value, id view, id defaultView, RCTViewManager *manager)
|
||||||
{
|
{
|
||||||
// TODO: cache respondsToSelector tests
|
// TODO: cache respondsToSelector tests
|
||||||
if ([manager respondsToSelector:setter]) {
|
if ([manager respondsToSelector:setter]) {
|
||||||
|
@ -681,7 +686,25 @@ static BOOL RCTCallPropertySetter(SEL setter, id value, id view, id defaultView,
|
||||||
value = nil;
|
value = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
((void (*)(id, SEL, id, id, id))objc_msgSend)(manager, setter, value, view, defaultView);
|
void (^block)() = ^{
|
||||||
|
((void (*)(id, SEL, id, id, id))objc_msgSend)(manager, setter, value, view, defaultView);
|
||||||
|
};
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
|
||||||
|
NSString *viewName = RCTViewNameForModuleName(RCTBridgeModuleNameForClass([manager class]));
|
||||||
|
NSString *logPrefix = [NSString stringWithFormat:
|
||||||
|
@"Error setting property '%@' of %@ with tag #%@: ",
|
||||||
|
key, viewName, [view reactTag]];
|
||||||
|
|
||||||
|
RCTPerformBlockWithLogPrefix(block, logPrefix);
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
block();
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
return NO;
|
return NO;
|
||||||
|
@ -693,7 +716,7 @@ static void RCTSetViewProps(NSDictionary *props, UIView *view,
|
||||||
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
|
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
|
||||||
|
|
||||||
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forView:withDefaultView:", key]);
|
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forView:withDefaultView:", key]);
|
||||||
RCTCallPropertySetter(setter, obj, view, defaultView, manager);
|
RCTCallPropertySetter(key, setter, obj, view, defaultView, manager);
|
||||||
|
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -704,7 +727,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
||||||
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
|
[props enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
|
||||||
|
|
||||||
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forShadowView:withDefaultView:", key]);
|
SEL setter = NSSelectorFromString([NSString stringWithFormat:@"set_%@:forShadowView:withDefaultView:", key]);
|
||||||
RCTCallPropertySetter(setter, obj, shadowView, defaultView, manager);
|
RCTCallPropertySetter(key, setter, obj, shadowView, defaultView, manager);
|
||||||
|
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
@ -727,44 +750,47 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
||||||
// Register manager
|
// Register manager
|
||||||
_viewManagerRegistry[reactTag] = manager;
|
_viewManagerRegistry[reactTag] = manager;
|
||||||
|
|
||||||
// Generate default view, used for resetting default props
|
|
||||||
if (!_defaultShadowViews[viewName]) {
|
|
||||||
_defaultShadowViews[viewName] = [manager shadowView];
|
|
||||||
}
|
|
||||||
|
|
||||||
RCTShadowView *shadowView = [manager shadowView];
|
RCTShadowView *shadowView = [manager shadowView];
|
||||||
shadowView.viewName = viewName;
|
if (shadowView) {
|
||||||
shadowView.reactTag = reactTag;
|
|
||||||
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], manager);
|
// Generate default view, used for resetting default props
|
||||||
_shadowViewRegistry[shadowView.reactTag] = shadowView;
|
if (!_defaultShadowViews[viewName]) {
|
||||||
|
_defaultShadowViews[viewName] = [manager shadowView];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set properties
|
||||||
|
shadowView.viewName = viewName;
|
||||||
|
shadowView.reactTag = reactTag;
|
||||||
|
RCTSetShadowViewProps(props, shadowView, _defaultShadowViews[viewName], manager);
|
||||||
|
}
|
||||||
|
_shadowViewRegistry[reactTag] = shadowView;
|
||||||
|
|
||||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry){
|
||||||
RCTCAssertMainThread();
|
RCTCAssertMainThread();
|
||||||
|
|
||||||
// Generate default view, used for resetting default props
|
|
||||||
if (!uiManager->_defaultViews[viewName]) {
|
|
||||||
// Note the default is setup after the props are read for the first time ever
|
|
||||||
// for this className - this is ok because we only use the default for restoring
|
|
||||||
// defaults, which never happens on first creation.
|
|
||||||
uiManager->_defaultViews[viewName] = [manager view];
|
|
||||||
}
|
|
||||||
|
|
||||||
UIView *view = [manager view];
|
UIView *view = [manager view];
|
||||||
if (view) {
|
if (view) {
|
||||||
|
|
||||||
// Set required properties
|
// Generate default view, used for resetting default props
|
||||||
view.reactTag = reactTag;
|
if (!uiManager->_defaultViews[viewName]) {
|
||||||
view.multipleTouchEnabled = YES;
|
// Note the default is setup after the props are read for the first time ever
|
||||||
view.userInteractionEnabled = YES; // required for touch handling
|
// for this className - this is ok because we only use the default for restoring
|
||||||
view.layer.allowsGroupOpacity = YES; // required for touch handling
|
// defaults, which never happens on first creation.
|
||||||
|
uiManager->_defaultViews[viewName] = [manager view];
|
||||||
|
}
|
||||||
|
|
||||||
// Set custom properties
|
// Set properties
|
||||||
|
view.reactTag = reactTag;
|
||||||
|
if ([view isKindOfClass:[UIView class]]) {
|
||||||
|
view.multipleTouchEnabled = YES;
|
||||||
|
view.userInteractionEnabled = YES; // required for touch handling
|
||||||
|
view.layer.allowsGroupOpacity = YES; // required for touch handling
|
||||||
|
}
|
||||||
RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], manager);
|
RCTSetViewProps(props, view, uiManager->_defaultViews[viewName], manager);
|
||||||
}
|
}
|
||||||
viewRegistry[view.reactTag] = view;
|
viewRegistry[reactTag] = view;
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove viewName param as it isn't needed
|
// TODO: remove viewName param as it isn't needed
|
||||||
- (void)updateView:(NSNumber *)reactTag viewName:(__unused NSString *)_ props:(NSDictionary *)props
|
- (void)updateView:(NSNumber *)reactTag viewName:(__unused NSString *)_ props:(NSDictionary *)props
|
||||||
{
|
{
|
||||||
|
@ -875,7 +901,7 @@ static void RCTSetShadowViewProps(NSDictionary *props, RCTShadowView *shadowView
|
||||||
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
[self addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||||
UIView *view = viewRegistry[reactTag];
|
UIView *view = viewRegistry[reactTag];
|
||||||
if (!view) {
|
if (!view) {
|
||||||
RCTLogError(@"measure cannot find view with tag %@", reactTag);
|
RCTLogError(@"measure cannot find view with tag #%@", reactTag);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CGRect frame = view.frame;
|
CGRect frame = view.frame;
|
||||||
|
@ -1039,7 +1065,7 @@ static void RCTMeasureLayout(RCTShadowView *view,
|
||||||
uiManager.mainScrollView = (id<RCTScrollableProtocol>)rkObject;
|
uiManager.mainScrollView = (id<RCTScrollableProtocol>)rkObject;
|
||||||
((id<RCTScrollableProtocol>)rkObject).nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate;
|
((id<RCTScrollableProtocol>)rkObject).nativeMainScrollDelegate = uiManager.nativeMainScrollDelegate;
|
||||||
} else {
|
} else {
|
||||||
RCTCAssert(NO, @"Tag %@ does not conform to RCTScrollableProtocol", reactTag);
|
RCTCAssert(NO, @"Tag #%@ does not conform to RCTScrollableProtocol", reactTag);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uiManager.mainScrollView = nil;
|
uiManager.mainScrollView = nil;
|
||||||
|
@ -1056,7 +1082,7 @@ static void RCTMeasureLayout(RCTShadowView *view,
|
||||||
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
||||||
[(id<RCTScrollableProtocol>)view scrollToOffset:CGPointMake([offsetX floatValue], [offsetY floatValue]) animated:YES];
|
[(id<RCTScrollableProtocol>)view scrollToOffset:CGPointMake([offsetX floatValue], [offsetY floatValue]) animated:YES];
|
||||||
} else {
|
} else {
|
||||||
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag %@", view, reactTag);
|
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -1070,7 +1096,7 @@ static void RCTMeasureLayout(RCTShadowView *view,
|
||||||
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
||||||
[(id<RCTScrollableProtocol>)view scrollToOffset:CGPointMake([offsetX floatValue], [offsetY floatValue]) animated:NO];
|
[(id<RCTScrollableProtocol>)view scrollToOffset:CGPointMake([offsetX floatValue], [offsetY floatValue]) animated:NO];
|
||||||
} else {
|
} else {
|
||||||
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag %@", view, reactTag);
|
RCTLogError(@"tried to scrollToOffset: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
@ -1084,7 +1110,7 @@ static void RCTMeasureLayout(RCTShadowView *view,
|
||||||
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) {
|
||||||
[(id<RCTScrollableProtocol>)view zoomToRect:[RCTConvert CGRect:rectDict] animated:YES];
|
[(id<RCTScrollableProtocol>)view zoomToRect:[RCTConvert CGRect:rectDict] animated:YES];
|
||||||
} else {
|
} else {
|
||||||
RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ with tag %@", view, reactTag);
|
RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ with tag #%@", view, reactTag);
|
||||||
}
|
}
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ RCT_DEPRECATED_VIEW_PROPERTY(throttleScrollCallbackMS, scrollEventThrottle)
|
||||||
|
|
||||||
UIView *view = viewRegistry[reactTag];
|
UIView *view = viewRegistry[reactTag];
|
||||||
if (!view) {
|
if (!view) {
|
||||||
RCTLogError(@"Cannot find view with tag %@", reactTag);
|
RCTLogError(@"Cannot find view with tag #%@", reactTag);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
package.json
18
package.json
|
@ -26,7 +26,6 @@
|
||||||
"Examples/SampleApp",
|
"Examples/SampleApp",
|
||||||
"Libraries",
|
"Libraries",
|
||||||
"packager",
|
"packager",
|
||||||
"local-cli",
|
|
||||||
"cli.js",
|
"cli.js",
|
||||||
"init.sh",
|
"init.sh",
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
|
@ -42,25 +41,26 @@
|
||||||
"react-native-start": "packager/packager.sh"
|
"react-native-start": "packager/packager.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"absolute-path": "0.0.0",
|
||||||
|
"bluebird": "^2.9.21",
|
||||||
|
"chalk": "^1.0.0",
|
||||||
"connect": "2.8.3",
|
"connect": "2.8.3",
|
||||||
|
"debug": "~2.1.0",
|
||||||
|
"joi": "~5.1.0",
|
||||||
"jstransform": "10.1.0",
|
"jstransform": "10.1.0",
|
||||||
|
"module-deps": "3.5.6",
|
||||||
|
"optimist": "0.6.1",
|
||||||
"react-timer-mixin": "^0.13.1",
|
"react-timer-mixin": "^0.13.1",
|
||||||
"react-tools": "0.13.1",
|
"react-tools": "0.13.1",
|
||||||
"rebound": "^0.0.12",
|
"rebound": "^0.0.12",
|
||||||
|
"sane": "1.0.1",
|
||||||
"source-map": "0.1.31",
|
"source-map": "0.1.31",
|
||||||
"stacktrace-parser": "0.1.1",
|
"stacktrace-parser": "0.1.1",
|
||||||
"absolute-path": "0.0.0",
|
|
||||||
"debug": "~2.1.0",
|
|
||||||
"joi": "~5.1.0",
|
|
||||||
"module-deps": "3.5.6",
|
|
||||||
"optimist": "0.6.1",
|
|
||||||
"sane": "1.0.1",
|
|
||||||
"uglify-js": "~2.4.16",
|
"uglify-js": "~2.4.16",
|
||||||
"underscore": "1.7.0",
|
"underscore": "1.7.0",
|
||||||
"worker-farm": "1.1.0",
|
"worker-farm": "1.1.0",
|
||||||
"yargs": "1.3.2",
|
|
||||||
"ws": "0.4.31",
|
"ws": "0.4.31",
|
||||||
"bluebird": "^2.9.21"
|
"yargs": "1.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest-cli": "0.2.1",
|
"jest-cli": "0.2.1",
|
||||||
|
|
|
@ -23,6 +23,7 @@ if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) {
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var chalk = require('chalk');
|
||||||
var connect = require('connect');
|
var connect = require('connect');
|
||||||
var ReactPackager = require('./react-packager');
|
var ReactPackager = require('./react-packager');
|
||||||
var blacklist = require('./blacklist.js');
|
var blacklist = require('./blacklist.js');
|
||||||
|
@ -88,13 +89,35 @@ console.log('\n' +
|
||||||
' ===============================================================\n'
|
' ===============================================================\n'
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('Looking for JS files in\n ', options.projectRoots.join('\n '));
|
console.log(
|
||||||
|
'Looking for JS files in\n ',
|
||||||
|
chalk.dim(options.projectRoots.join('\n ')),
|
||||||
|
'\n'
|
||||||
|
);
|
||||||
|
|
||||||
process.on('uncaughtException', function(e) {
|
process.on('uncaughtException', function(e) {
|
||||||
console.error(e);
|
if (e.code === 'EADDRINUSE') {
|
||||||
console.error(e.stack);
|
console.log(
|
||||||
console.error('\n >>> ERROR: could not create packager - please shut down ' +
|
chalk.bgRed.bold(' ERROR '),
|
||||||
'any existing instances that are already running.\n\n');
|
chalk.red('Packager can\'t listen on port', chalk.bold(options.port))
|
||||||
|
);
|
||||||
|
console.log('Most likely another process is already using this port');
|
||||||
|
console.log('Run the following command to find out which process:');
|
||||||
|
console.log('\n ', chalk.bold('lsof -n -i4TCP:' + options.port), '\n');
|
||||||
|
console.log('You can either shut down the other process:');
|
||||||
|
console.log('\n ', chalk.bold('kill -9 <PID>'), '\n');
|
||||||
|
console.log('or run packager on different port.');
|
||||||
|
} else {
|
||||||
|
console.log(chalk.bgRed.bold(' ERROR '), chalk.red(e.message));
|
||||||
|
var errorAttributes = JSON.stringify(e);
|
||||||
|
if (errorAttributes !== '{}') {
|
||||||
|
console.error(chalk.red(errorAttributes));
|
||||||
|
}
|
||||||
|
console.error(chalk.red(e.stack));
|
||||||
|
}
|
||||||
|
console.log('\nSee', chalk.underline('http://facebook.github.io/react-native/docs/troubleshooting.html'));
|
||||||
|
console.log('for common problems and solutions.');
|
||||||
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
var server = runServer(options, function() {
|
var server = runServer(options, function() {
|
||||||
|
@ -151,13 +174,13 @@ function getDevToolsLauncher(options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// A status page so the React/project.pbxproj build script
|
// A status page so the React/project.pbxproj build script
|
||||||
// can verify that packager is running on 8081 and not
|
// can verify that packager is running on 8081 and not
|
||||||
// another program / service.
|
// another program / service.
|
||||||
function statusPageMiddleware(req, res, next) {
|
function statusPageMiddleware(req, res, next) {
|
||||||
if (req.url === '/status') {
|
if (req.url === '/status') {
|
||||||
res.end('packager-status:running');
|
res.end('packager-status:running');
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue