On behalf of @erikschlegel - Adding top level integration functionality for react-native
This commit is contained in:
Родитель
a3cfd23bd4
Коммит
8a312b5f42
|
@ -32,6 +32,7 @@ namespace ReactNative.Tests.Bridge
|
|||
Registry = registry,
|
||||
JavaScriptModulesConfig = jsConfig,
|
||||
JavaScriptExecutor = executor,
|
||||
BundleLoader = new JavaScriptBundleLoader(),
|
||||
NativeModuleCallExceptionHandler = _ => { }
|
||||
};
|
||||
|
||||
|
@ -63,6 +64,7 @@ namespace ReactNative.Tests.Bridge
|
|||
Registry = registry,
|
||||
JavaScriptModulesConfig = jsConfig,
|
||||
JavaScriptExecutor = executor,
|
||||
BundleLoader = new JavaScriptBundleLoader(),
|
||||
NativeModuleCallExceptionHandler = _ => { },
|
||||
};
|
||||
|
||||
|
@ -108,6 +110,7 @@ namespace ReactNative.Tests.Bridge
|
|||
Registry = registry,
|
||||
JavaScriptModulesConfig = jsConfig,
|
||||
JavaScriptExecutor = executor,
|
||||
BundleLoader = new JavaScriptBundleLoader(),
|
||||
NativeModuleCallExceptionHandler = handler,
|
||||
};
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ using ReactNative.Bridge;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using ReactNative.Bridge.Queue;
|
||||
|
||||
namespace ReactNative.Tests
|
||||
{
|
||||
|
@ -32,6 +33,14 @@ namespace ReactNative.Tests
|
|||
_function = function;
|
||||
}
|
||||
|
||||
public bool IsDisposed
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<INativeModule> NativeModules
|
||||
{
|
||||
get
|
||||
|
@ -40,6 +49,14 @@ namespace ReactNative.Tests
|
|||
}
|
||||
}
|
||||
|
||||
public ICatalystQueueConfiguration QueueConfiguration
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public T GetNativeModule<T>() where T : INativeModule
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
@ -64,5 +81,10 @@ namespace ReactNative.Tests
|
|||
{
|
||||
_function(moduleId, methodId, arguments, tracingName);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,11 @@ namespace ReactNative.Tests
|
|||
return _onCall(moduleName, methodName, arguments);
|
||||
}
|
||||
|
||||
public void RunScript(string script)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetGlobalVariable(string propertyName, JToken value)
|
||||
{
|
||||
_onSetGlobalVariable(propertyName, value);
|
||||
|
@ -40,6 +45,5 @@ namespace ReactNative.Tests
|
|||
{
|
||||
_onDispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using ReactNative.UIManager;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ReactNative.Tests.UIManager
|
||||
{
|
||||
|
@ -21,7 +21,7 @@ namespace ReactNative.Tests.UIManager
|
|||
});
|
||||
|
||||
var appKey = "foo";
|
||||
var appParameters = new JObject();
|
||||
var appParameters = new Dictionary<string, object>();
|
||||
module.runApplication(appKey, appParameters);
|
||||
Assert.AreEqual(nameof(AppRegistry.runApplication), name);
|
||||
Assert.AreEqual(2, args.Length);
|
||||
|
|
|
@ -12,11 +12,17 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace ReactNative.Bridge
|
||||
{
|
||||
/// <summary>
|
||||
/// A higher level API on top of the <see cref="IJavaScriptExecutor" /> and module registries. This provides an
|
||||
/// environment allowing the invocation of JavaScript methods.
|
||||
/// </summary>
|
||||
class CatalystInstance : ICatalystInstance, IDisposable
|
||||
{
|
||||
private readonly NativeModuleRegistry _registry;
|
||||
private readonly JavaScriptModuleRegistry _jsRegistry;
|
||||
private readonly IJavaScriptExecutor _jsExecutor;
|
||||
private readonly JavaScriptModuleRegistry _jsModuleRegistry;
|
||||
private readonly JavaScriptBundleLoader _bundleLoader;
|
||||
private readonly JavaScriptModulesConfig _jsModulesConfig;
|
||||
private readonly Action<Exception> _nativeModuleCallExceptionHandler;
|
||||
|
||||
|
@ -29,12 +35,14 @@ namespace ReactNative.Bridge
|
|||
IJavaScriptExecutor jsExecutor,
|
||||
NativeModuleRegistry registry,
|
||||
JavaScriptModulesConfig jsModulesConfig,
|
||||
Action<Exception> nativeModuleCallsExceptionHandler)
|
||||
JavaScriptBundleLoader bundleLoader,
|
||||
Action<Exception> nativeModuleCallExceptionHandler)
|
||||
{
|
||||
_registry = registry;
|
||||
_jsExecutor = jsExecutor;
|
||||
_jsModulesConfig = jsModulesConfig;
|
||||
_nativeModuleCallsExceptionHandler = nativeModuleCallsExceptionHandler;
|
||||
_nativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
|
||||
_bundleLoader = bundleLoader;
|
||||
_jsRegistry = new JavaScriptModuleRegistry(this, _jsModulesConfig);
|
||||
|
||||
QueueConfiguration = CatalystQueueConfiguration.Create(
|
||||
|
@ -145,6 +153,14 @@ namespace ReactNative.Bridge
|
|||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the main javascript bundle script
|
||||
/// </summary>
|
||||
public async Task RunJSBundleAsync()
|
||||
{
|
||||
await _bundleLoader.invokeJavaScripts(_jsExecutor, QueueConfiguration.JSQueueThread);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DispatcherHelpers.AssertOnDispatcher();
|
||||
|
@ -203,7 +219,7 @@ namespace ReactNative.Bridge
|
|||
|
||||
private void HandleException(Exception ex)
|
||||
{
|
||||
_nativeModuleCallsExceptionHandler(ex);
|
||||
_nativeModuleCallExceptionHandler(ex);
|
||||
QueueConfiguration.DispatcherQueueThread.RunOnQueue(Dispose);
|
||||
}
|
||||
|
||||
|
@ -213,6 +229,7 @@ namespace ReactNative.Bridge
|
|||
private NativeModuleRegistry _registry;
|
||||
private JavaScriptModulesConfig _jsModulesConfig;
|
||||
private IJavaScriptExecutor _jsExecutor;
|
||||
private JavaScriptBundleLoader _bundleLoader;
|
||||
private Action<Exception> _nativeModuleCallExceptionHandler;
|
||||
|
||||
public CatalystQueueConfigurationSpec QueueConfigurationSpec
|
||||
|
@ -247,6 +264,14 @@ namespace ReactNative.Bridge
|
|||
}
|
||||
}
|
||||
|
||||
public JavaScriptBundleLoader BundleLoader
|
||||
{
|
||||
set
|
||||
{
|
||||
_bundleLoader = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Action<Exception> NativeModuleCallExceptionHandler
|
||||
{
|
||||
set
|
||||
|
@ -261,6 +286,7 @@ namespace ReactNative.Bridge
|
|||
AssertNotNull(_jsExecutor, nameof(IJavaScriptExecutor));
|
||||
AssertNotNull(_registry, nameof(Registry));
|
||||
AssertNotNull(_jsModulesConfig, nameof(JavaScriptModulesConfig));
|
||||
AssertNotNull(_bundleLoader, nameof(BundleLoader));
|
||||
AssertNotNull(_nativeModuleCallExceptionHandler, nameof(NativeModuleCallExceptionHandler));
|
||||
|
||||
return new CatalystInstance(
|
||||
|
@ -268,6 +294,7 @@ namespace ReactNative.Bridge
|
|||
_jsExecutor,
|
||||
_registry,
|
||||
_jsModulesConfig,
|
||||
_bundleLoader,
|
||||
_nativeModuleCallExceptionHandler);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using ReactNative.Bridge.Queue;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reactive.Disposables;
|
||||
|
||||
namespace ReactNative.Bridge
|
||||
{
|
||||
|
@ -10,13 +10,21 @@ namespace ReactNative.Bridge
|
|||
/// environment allowing the invocation of JavaScript methods and lets a
|
||||
/// set of native APIs be invokable from JavaScript as well.
|
||||
/// </summary>
|
||||
public interface ICatalystInstance
|
||||
public interface ICatalystInstance : ICancelable
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumerates the available native modules.
|
||||
/// </summary>
|
||||
IEnumerable<INativeModule> NativeModules { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The catalyst queue configuration.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// TODO: is it okay for this to be public?
|
||||
/// </remarks>
|
||||
ICatalystQueueConfiguration QueueConfiguration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the instance.
|
||||
/// </summary>
|
||||
|
@ -51,12 +59,5 @@ namespace ReactNative.Bridge
|
|||
/// <typeparam name="T">Type of native module.</typeparam>
|
||||
/// <returns>The native module instance.</returns>
|
||||
T GetNativeModule<T>() where T : INativeModule;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a JavaScript module instance.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of JavaScript module.</typeparam>
|
||||
/// <returns>The JavaScript module instance.</returns>
|
||||
T GetJavaScriptModule<T>() where T : IJavaScriptModule;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,5 +23,11 @@ namespace ReactNative.Bridge
|
|||
/// <param name="propertyName">The global variable name.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
void SetGlobalVariable(string propertyName, JToken value);
|
||||
|
||||
/// <summary>
|
||||
/// Runs the given script.
|
||||
/// </summary>
|
||||
/// <param name="script">The script.</param>
|
||||
void RunScript(string script);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
using ReactNative.Bridge.Queue;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
|
||||
namespace ReactNative.Bridge
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is responsible for reading and invoking the contents of a list of scripts on the JS engine
|
||||
/// </summary>
|
||||
public class JavaScriptBundleLoader
|
||||
{
|
||||
private readonly List<string> scriptContentList;
|
||||
|
||||
public static class Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// The instance builder for <see cref="JavaScriptBundleLoader" />
|
||||
/// </summary>
|
||||
/// <param name="scripts">The array of URI scripts to read</param>
|
||||
/// <returns></returns>
|
||||
public static async Task<JavaScriptBundleLoader> Build(Uri[] scripts)
|
||||
{
|
||||
var jsBuilder = new JavaScriptBundleLoader();
|
||||
await jsBuilder.readScript(scripts);
|
||||
|
||||
return jsBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the script contents on the JS engine
|
||||
/// </summary>
|
||||
/// <param name="executor">The Javascript engine</param>
|
||||
/// <param name="jsQueueThread">The message queue thread to incoke the scripts on</param>
|
||||
/// <returns></returns>
|
||||
public async Task invokeJavaScripts(IJavaScriptExecutor executor, IMessageQueueThread jsQueueThread)
|
||||
{
|
||||
await jsQueueThread.CallOnQueue(() =>
|
||||
{
|
||||
foreach (var script in scriptContentList)
|
||||
{
|
||||
executor.RunScript(script);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an array of URI's and loads the contents into the scriptContentList
|
||||
/// </summary>
|
||||
/// <param name="scripts">The array of URIs</param>
|
||||
/// <returns></returns>
|
||||
private async Task readScript(Uri[] scripts)
|
||||
{
|
||||
foreach(var script in scripts) {
|
||||
var storageFile = await StorageFile.GetFileFromApplicationUriAsync(script);
|
||||
using (var stream = await storageFile.OpenStreamForReadAsync())
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
scriptContentList.Add(reader.ReadToEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ReactNative.Bridge
|
||||
{
|
||||
public interface LifecycleEventListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when host (activity/service) receives resume event (e.g. {@link Activity#onResume}
|
||||
/// </summary>
|
||||
void onHostResume();
|
||||
|
||||
/// <summary>
|
||||
/// Called when host (activity/service) receives destroy event (e.g. {@link Activity#onDestroy}
|
||||
/// </summary>
|
||||
void onHostDestroy();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
void onHostPause();
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ReactNative.Bridge
|
||||
{
|
||||
public interface NativeModuleCallExceptionHandler
|
||||
{
|
||||
void handleException(Exception e);
|
||||
}
|
||||
}
|
|
@ -8,8 +8,7 @@ namespace ReactNative.Bridge.Queue
|
|||
/// modules, and JS). Some of these queue *may* be the same but should be
|
||||
/// coded against as if they are different.
|
||||
/// </summary>
|
||||
public class CatalystQueueConfiguration : ICatalystQueueConfiguration, IDisposable
|
||||
|
||||
public class CatalystQueueConfiguration : ICatalystQueueConfiguration
|
||||
{
|
||||
private readonly MessageQueueThread _dispatcherQueueThread;
|
||||
private readonly MessageQueueThread _nativeModulesQueueThread;
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
namespace ReactNative.Bridge
|
||||
{
|
||||
/// <summary>
|
||||
/// This class serves as a base class on top of <see cref="ReactContext"/>.
|
||||
/// </summary>
|
||||
public class ReactApplicationContext : ReactContext
|
||||
{
|
||||
public ReactApplicationContext()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ namespace ReactNative.Bridge
|
|||
private readonly List<ILifecycleEventListener> _lifecycleEventListeners =
|
||||
new List<ILifecycleEventListener>();
|
||||
|
||||
private CatalystInstance _catalystInstance;
|
||||
private ICatalystInstance _catalystInstance;
|
||||
|
||||
/// <summary>
|
||||
/// The catalyst instance associated with the context.
|
||||
|
@ -286,7 +286,7 @@ namespace ReactNative.Bridge
|
|||
/// <remarks>
|
||||
/// This method should be called exactly once.
|
||||
/// </remarks>
|
||||
internal void InitializeWithInstance(CatalystInstance instance)
|
||||
internal void InitializeWithInstance(ICatalystInstance instance)
|
||||
{
|
||||
if (instance == null)
|
||||
throw new ArgumentNullException(nameof(instance));
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
|
||||
namespace ReactNative.Core
|
||||
{
|
||||
using Modules.Core;
|
||||
using ReactNative.Bridge;
|
||||
|
||||
/// <summary>
|
||||
/// This class is managing instances of {@link CatalystInstance}. It expose a way to configure
|
||||
/// catalyst instance using {@link ReactPackage} and keeps track of the lifecycle of that
|
||||
/// instance.It also sets up connection between the instance and developers support functionality
|
||||
/// of the framework.
|
||||
///
|
||||
/// An instance of this manager is required to start JS application in { @link ReactRootView} (see
|
||||
/// {@link ReactRootView#startReactApplication} for more info).
|
||||
///
|
||||
/// The lifecycle of the instance of {@link ReactInstanceManager} should be bound to the activity
|
||||
/// that owns the { @link ReactRootView } that is used to render react application using this
|
||||
/// instance manager (see {@link ReactRootView#startReactApplication}). It's required to pass
|
||||
/// owning activity's lifecycle events to the instance manager (see {@link #onPause},
|
||||
/// {@link #onDestroy} and {@link #onResume}).
|
||||
/// </summary>
|
||||
public abstract class ReactInstanceManager
|
||||
{
|
||||
public interface ReactInstanceEventListener
|
||||
{
|
||||
/**
|
||||
* Called when the react context is initialized (all modules registered). Always called on the
|
||||
* UI thread.
|
||||
*/
|
||||
void onReactContextInitialized(ReactContext context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger react context initialization asynchronously in a background async task.
|
||||
/// </summary>
|
||||
public abstract void createReactContextInBackground();
|
||||
|
||||
/// <summary>
|
||||
/// return whether createReactContextInBackground has been called
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract bool hasStartedCreatingInitialContext();
|
||||
|
||||
public abstract void onBackPressed();
|
||||
public abstract void onPause();
|
||||
|
||||
public abstract void onResume(DefaultHardwareBackBtnHandler defaultBackButtonImpl);
|
||||
|
||||
public abstract void onDestroy();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL where the last bundle was loaded from.
|
||||
/// </summary>
|
||||
/// <returns>URL where the last bundle was loaded from.</returns>
|
||||
public abstract string getSourceUrl();
|
||||
|
||||
/// <summary>
|
||||
/// Attach given {@param rootView} to a catalyst instance manager and start JS application
|
||||
/// </summary>
|
||||
/// <param name="rootView"></param>
|
||||
public abstract void attachMeasuredRootView(ReactRootView rootView);
|
||||
|
||||
/// <summary>
|
||||
/// Detach given rootView from current catalyst instance.
|
||||
/// </summary>
|
||||
/// <param name="rootView"></param>
|
||||
public abstract void detachRootView(ReactRootView rootView);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
|
||||
namespace ReactNative.Core
|
||||
{
|
||||
using Newtonsoft.Json.Linq;
|
||||
using ReactNative.UIManager;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Default root view for catalyst apps. Provides the ability to listen for size changes so that a UI manager can re-layout its elements
|
||||
/// </summary>
|
||||
public class ReactRootView : SizeMonitoringFrameLayout, RootView
|
||||
{
|
||||
private ReactInstanceManager mReactInstanceManager;
|
||||
private string mJSModuleName;
|
||||
private bool mIsAttachedToWindow = false;
|
||||
private bool mIsAttachedToInstance = false;
|
||||
|
||||
public void startReactApplication(ReactInstanceManager reactInstanceManager, string moduleName)
|
||||
{
|
||||
//TODO: Add thread queue impl hook
|
||||
//UiThreadUtil.assertOnUiThread();
|
||||
|
||||
mReactInstanceManager = reactInstanceManager;
|
||||
mJSModuleName = moduleName;
|
||||
|
||||
if (!mReactInstanceManager.hasStartedCreatingInitialContext())
|
||||
{
|
||||
mReactInstanceManager.createReactContextInBackground();
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Implement this method for emitting events
|
||||
private void sendEvent(string eventName, JArray parameters)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
if (mReactInstanceManager != null)
|
||||
{
|
||||
// mReactInstanceManager.getCurrentReactContext()
|
||||
// .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
//.emit(eventName, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
|
||||
using ReactNative.Bridge;
|
||||
using ReactNative.Modules.Core;
|
||||
using ReactNative.UIManager;
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace ReactNative
|
||||
{
|
||||
/// <summary>
|
||||
/// Package defining core framework modules (e.g. UIManager). It should be used for modules that
|
||||
/// require special integration with other framework parts (e.g. with the list of packages to load
|
||||
/// view managers from).
|
||||
///
|
||||
/// TODO
|
||||
/// 1.add DefaultHardwareBackBtnHandler functoinality
|
||||
/// 2.Add Core native modules
|
||||
/// 3.Implement UIManagerModule
|
||||
/// </summary>
|
||||
public class CoreModulesPackage : IReactPackage
|
||||
{
|
||||
private readonly ReactInstanceManager _ReactInstanceManager;
|
||||
private readonly UIImplementationProvider _UIImplementationProvider;
|
||||
|
||||
public CoreModulesPackage(ReactInstanceManager reactInstanceManager, UIImplementationProvider uiImplementationProvider)
|
||||
{
|
||||
_ReactInstanceManager = reactInstanceManager;
|
||||
_UIImplementationProvider = uiImplementationProvider;
|
||||
}
|
||||
|
||||
public NativeModuleRegistry createNativeModules(ReactApplicationContext reactContext)
|
||||
{
|
||||
var uiManagerModule = default(UIManagerModule);
|
||||
List<ViewManager<FrameworkElement, ReactShadowNode>> viewManagersList = _ReactInstanceManager.CreateAllViewManagers(reactContext);
|
||||
|
||||
uiManagerModule = new UIManagerModule(reactContext, viewManagersList, _UIImplementationProvider.createUIImplementation(reactContext, viewManagersList));
|
||||
|
||||
var builder = new NativeModuleRegistry.Builder();
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
public JavaScriptModulesConfig createJSModules()
|
||||
{
|
||||
var builder = new JavaScriptModulesConfig.Builder();
|
||||
builder.Add<RCTEventEmitter>();
|
||||
builder.Add<RCTNativeAppEventEmitter>();
|
||||
builder.Add<AppRegistry>();
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
public List<ViewManager<FrameworkElement, ReactShadowNode>> CreateViewManagers(ReactApplicationContext reactContext)
|
||||
{
|
||||
return new List<ViewManager<FrameworkElement, ReactShadowNode>>(0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
namespace ReactNative
|
||||
{
|
||||
public enum LifecycleState
|
||||
{
|
||||
BEFORE_RESUME,
|
||||
RESUMED,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
namespace ReactNative.Modules.Core
|
||||
{
|
||||
using Bridge;
|
||||
using UIManager;
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
public interface IReactPackage
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds all the native modules for a react package.
|
||||
/// </summary>
|
||||
/// <param name="reactContext">The React app context</param>
|
||||
/// <returns>The module registry</returns>
|
||||
NativeModuleRegistry createNativeModules(ReactApplicationContext reactContext);
|
||||
|
||||
/// <summary>
|
||||
/// Creates all the javascript modules
|
||||
/// </summary>
|
||||
/// <returns>the registered javascript modules</returns>
|
||||
JavaScriptModulesConfig createJSModules();
|
||||
|
||||
List<ViewManager<FrameworkElement, ReactShadowNode>> CreateViewManagers(ReactApplicationContext reactContext);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
using ReactNative.Bridge;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
using ReactNative.UIManager;
|
||||
using ReactNative.Modules.Core;
|
||||
|
||||
namespace ReactNative
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is managing instances of <see cref="CatalystInstance" />. It expose a way to configure
|
||||
/// catalyst instance using <see cref="ReactRootView"/> and keeps track of the lifecycle of that
|
||||
/// instance.It also sets up connection between the instance and developers support functionality
|
||||
/// of the framework.
|
||||
///
|
||||
/// An instance of this manager is required to start JS application in <see cref=" ReactRootView"/>
|
||||
/// #startReactApplication for more info).
|
||||
///
|
||||
/// The lifecycle of the instance of <see cref="ReactInstanceManager"/> should be bound to the activity
|
||||
/// that owns the <see cref="ReactRootView"/> that is used to render react application using this
|
||||
/// instance manager <see cref=" ReactRootView"/>. It's required to pass
|
||||
/// owning activity's lifecycle events to the instance manager #onPause, #onDestroy, #onResume
|
||||
/// TODO:
|
||||
/// 1.Add lifecycle event hooks
|
||||
/// </summary>
|
||||
public abstract class ReactInstanceManager
|
||||
{
|
||||
public abstract List<ViewManager<FrameworkElement, ReactShadowNode>> CreateAllViewManagers(ReactApplicationContext catalystApplicationContext);
|
||||
/// <summary>
|
||||
/// Trigger react context initialization asynchronously in a background async task.
|
||||
/// </summary>
|
||||
//public abstract void createReactContextInBackground();
|
||||
|
||||
/// <summary>
|
||||
/// return whether createReactContextInBackground has been called
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
//public abstract bool hasStartedCreatingInitialContext();
|
||||
|
||||
//public abstract void onBackPressed();
|
||||
//public abstract void onPause();
|
||||
|
||||
//public abstract void onResume(DefaultHardwareBackBtnHandler defaultBackButtonImpl);
|
||||
|
||||
//public abstract void onDestroy();
|
||||
|
||||
/// <summary>
|
||||
/// Attach given {@param rootView} to a catalyst instance manager and start JS application
|
||||
/// </summary>
|
||||
/// <param name="rootView">The root view of the ReactJS app</param>
|
||||
public abstract void AttachMeasuredRootView(ReactRootView rootView);
|
||||
|
||||
/// <summary>
|
||||
/// Detach given rootView from current catalyst instance.
|
||||
/// </summary>
|
||||
/// <param name="rootView">The root view of the ReactJS app</param>
|
||||
public abstract void DetachRootView(ReactRootView rootView);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the <see cref="ReactApplicationContext" /> based on the user configured bundle <see cref="ReactApplicationContext#_jsBundleFile" /
|
||||
/// </summary>
|
||||
public abstract void RecreateReactContextInBackgroundFromBundleFileAsync();
|
||||
|
||||
class Builder
|
||||
{
|
||||
protected readonly List<IReactPackage> _reactPackages = new List<IReactPackage>();
|
||||
private LifecycleState _LifecycleState;
|
||||
private UIImplementationProvider _UIImplementationProvider;
|
||||
private string _jsBundleFile;
|
||||
private string _jsMainModuleName;
|
||||
|
||||
/// <summary>
|
||||
/// Sets a provider of <see cref="UIImplementation" />.
|
||||
/// </summary>
|
||||
/// <param name="uiImplementationProvider">The UI Implementaiton provider</param>
|
||||
/// <returns></returns>
|
||||
public Builder SetUIImplementationProvider(UIImplementationProvider uiImplementationProvider)
|
||||
{
|
||||
_UIImplementationProvider = uiImplementationProvider;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path to the JS bundle file to be loaded from the file system.
|
||||
/// </summary>
|
||||
/// <param name="jsBundleFile">JS bundle file path</param>
|
||||
/// <returns>A builder instance</returns>
|
||||
public Builder SetJSBundleFile(string jsBundleFile)
|
||||
{
|
||||
_jsBundleFile = jsBundleFile;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Path to your app's main module on the packager server. This is used when
|
||||
/// reloading JS during development. All paths are relative to the root folder
|
||||
/// the packager is serving files from.
|
||||
/// </summary>
|
||||
/// <param name="jsMainModuleName"></param>
|
||||
/// <returns></returns>
|
||||
public Builder SetJSMainModuleName(string jsMainModuleName)
|
||||
{
|
||||
_jsMainModuleName = jsMainModuleName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder AddPackage(IReactPackage reactPackage)
|
||||
{
|
||||
_reactPackages.Add(reactPackage);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new {@link ReactInstanceManagerImpl}.
|
||||
/// </summary>
|
||||
/// <param name="initialLifecycleState"></param>
|
||||
/// <returns></returns>
|
||||
public Builder SetInitialLifecycleState(LifecycleState initialLifecycleState)
|
||||
{
|
||||
_LifecycleState = initialLifecycleState;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a new <see cref="ReactInstanceManagerImpl"/> .
|
||||
/// Before calling <see mref="build"/>, the following must be called: setApplication then setJSMainModuleName
|
||||
/// </summary>
|
||||
/// <returns>A ReactInstanceManager instance</returns>
|
||||
public ReactInstanceManager Build()
|
||||
{
|
||||
if (_jsBundleFile == null || _jsMainModuleName == null)
|
||||
{
|
||||
throw new ArgumentNullException("JS bundle information is not provided exception");
|
||||
}
|
||||
|
||||
if (_UIImplementationProvider == null)
|
||||
{
|
||||
// create default UIImplementationProvider if the provided one is null.
|
||||
_UIImplementationProvider = new UIImplementationProvider();
|
||||
}
|
||||
|
||||
return new ReactInstanceManagerImpl(_jsMainModuleName, _reactPackages,
|
||||
_LifecycleState, _UIImplementationProvider, _jsBundleFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
|
||||
using ReactNative.Bridge;
|
||||
using ReactNative.Modules.Core;
|
||||
using ReactNative.UIManager;
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI.Xaml;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using ReactNative.Bridge.Queue;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ReactNative
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is managing instances of <see cref="CatalystInstance" />. It expose a way to configure
|
||||
/// catalyst instance using <see cref="IReactPackage" /> and keeps track of the lifecycle of that
|
||||
/// instance. It also sets up connection between the instance and developers support functionality
|
||||
/// of the framework.
|
||||
///
|
||||
/// An instance of this manager is required to start JS application in <see cref="ReactRootView" /> (see
|
||||
/// <see cref="ReactRootView#startReactApplication" /> for more info).
|
||||
///
|
||||
/// TODO:
|
||||
/// 1.Implement background task functionality and ReactContextInitAsyncTask class hierarchy.
|
||||
/// 2.Lifecycle managment functoinality. i.e. resume, pause, etc
|
||||
/// 3.Implement Backbutton handler
|
||||
/// 4.Implement js bundler load progress checks to ensure thread safety
|
||||
/// 5.Implement the ViewGroupManager as well as the main ReactViewManager
|
||||
/// 6.Create DevManager functionality to manage things like exceptions.
|
||||
/// </summary>
|
||||
public class ReactInstanceManagerImpl : ReactInstanceManager
|
||||
{
|
||||
private readonly List<ReactRootView> _AttachedRootViews = new List<ReactRootView>();
|
||||
private LifecycleState _LifecycleState;
|
||||
private readonly string _jsBundleFile;
|
||||
private readonly List<IReactPackage> _reactPackages;
|
||||
private volatile ReactApplicationContext _CurrentReactContext;
|
||||
private readonly string _jsMainModuleName;
|
||||
private readonly UIImplementationProvider _UIImplementationProvider;
|
||||
|
||||
public ReactInstanceManagerImpl(string jsMainModuleName, List<IReactPackage> packages, LifecycleState initialLifecycleState,
|
||||
UIImplementationProvider uiImplementationProvider, string jsBundleFile)
|
||||
{
|
||||
_jsBundleFile = jsBundleFile;
|
||||
_jsMainModuleName = jsMainModuleName;
|
||||
_reactPackages = packages;
|
||||
_LifecycleState = initialLifecycleState;
|
||||
_UIImplementationProvider = uiImplementationProvider;
|
||||
}
|
||||
|
||||
public override List<ViewManager<FrameworkElement, ReactShadowNode>> CreateAllViewManagers(ReactApplicationContext catalystApplicationContext)
|
||||
{
|
||||
var allViewManagers = default(List<ViewManager<FrameworkElement, ReactShadowNode>>);
|
||||
foreach (var reactPackage in _reactPackages)
|
||||
{
|
||||
var viewManagers = reactPackage.CreateViewManagers(catalystApplicationContext);
|
||||
allViewManagers.Concat(viewManagers);
|
||||
}
|
||||
|
||||
return allViewManagers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the <see cref="ReactApplicationContext" /> based on the user configured bundle <see cref="ReactApplicationContext#_jsBundleFile" />
|
||||
/// </summary>
|
||||
public override async void RecreateReactContextInBackgroundFromBundleFileAsync()
|
||||
{
|
||||
var jsExecutor = default(IJavaScriptExecutor);
|
||||
var jsBundler = await JavaScriptBundleLoader.Builder.Build(new Uri[] { new Uri(_jsBundleFile) });
|
||||
//TODO: Instantiate chakraJSExecutor following Erics rebase.
|
||||
await CreateReactContextAsync(jsExecutor, jsBundler);
|
||||
}
|
||||
|
||||
private async Task<ReactContext> CreateReactContextAsync(IJavaScriptExecutor jsExecutor, JavaScriptBundleLoader jsBundleLoader)
|
||||
{
|
||||
_CurrentReactContext = new ReactApplicationContext();
|
||||
var coreModulesPackage = new CoreModulesPackage(this, _UIImplementationProvider);
|
||||
var queueConfig = CatalystQueueConfigurationSpec.Default;
|
||||
|
||||
var javascriptRuntime = new CatalystInstance.Builder
|
||||
{
|
||||
QueueConfigurationSpec = queueConfig,
|
||||
JavaScriptExecutor = jsExecutor,
|
||||
Registry = coreModulesPackage.createNativeModules(_CurrentReactContext),
|
||||
JavaScriptModulesConfig = coreModulesPackage.createJSModules(),
|
||||
BundleLoader = jsBundleLoader,
|
||||
NativeModuleCallExceptionHandler = ex => { } /* TODO */,
|
||||
}.Build();
|
||||
|
||||
_CurrentReactContext.InitializeWithInstance(javascriptRuntime);
|
||||
await javascriptRuntime.RunJSBundleAsync();
|
||||
|
||||
return _CurrentReactContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches the <see cref="ReactRootView" /> to the list of tracked root views
|
||||
/// </summary>
|
||||
/// <param name="rootView">The root view for the ReactJS app</param>
|
||||
public override void AttachMeasuredRootView(ReactRootView rootView)
|
||||
{
|
||||
_AttachedRootViews.Add(rootView);
|
||||
|
||||
if (_CurrentReactContext != null)
|
||||
{
|
||||
AttachMeasuredRootViewToInstance(rootView, _CurrentReactContext.CatalystInstance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detach given <see cref="rootView" /> from current catalyst instance.
|
||||
/// </summary>
|
||||
/// <param name="rootView">The root view for the ReactJS app</param>
|
||||
public override void DetachRootView(ReactRootView rootView)
|
||||
{
|
||||
if (_AttachedRootViews.Remove(rootView))
|
||||
{
|
||||
if (_CurrentReactContext != null)
|
||||
{
|
||||
DetachViewFromInstance(rootView, _CurrentReactContext.CatalystInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DetachViewFromInstance(ReactRootView rootView, ICatalystInstance catalystInstance)
|
||||
{
|
||||
try
|
||||
{
|
||||
catalystInstance.GetJavaScriptModule<AppRegistry>()?.unmountApplicationComponentAtRootTag(rootView.TagId);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load AppRegistry JS module. Error message: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachMeasuredRootViewToInstance(ReactRootView rootView, ICatalystInstance catalystInstance)
|
||||
{
|
||||
UIManagerModule uiManagerModule = catalystInstance.GetNativeModule<UIManagerModule>();
|
||||
int rootTag = uiManagerModule.AddMeasuredRootView(rootView);
|
||||
var initialProps = new Dictionary<string, object>();
|
||||
initialProps.Add("rootTag", rootTag);
|
||||
|
||||
try
|
||||
{
|
||||
catalystInstance.GetJavaScriptModule<AppRegistry>()?.runApplication(rootView.JSModuleName, initialProps);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to load AppRegistry JS module. Error message: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -126,14 +126,13 @@
|
|||
<Compile Include="Bridge\JavaScriptModuleRegistration.cs" />
|
||||
<Compile Include="Bridge\JavaScriptModuleRegistry.cs" />
|
||||
<Compile Include="Bridge\JavaScriptModulesConfig.cs" />
|
||||
<Compile Include="Bridge\LifecycleEventListener.cs" />
|
||||
<Compile Include="Bridge\JavaScriptBundleLoader.cs" />
|
||||
<Compile Include="Bridge\NativeArgumentsParseException.cs" />
|
||||
<Compile Include="Bridge\ICallback.cs" />
|
||||
<Compile Include="Bridge\ICatalystInstance.cs" />
|
||||
<Compile Include="Bridge\INativeMethod.cs" />
|
||||
<Compile Include="Bridge\IOnBatchCompleteListener.cs" />
|
||||
<Compile Include="Bridge\NativeModuleBase.cs" />
|
||||
<Compile Include="Bridge\NativeModuleCallExceptionHandler.cs" />
|
||||
<Compile Include="Bridge\NativeModuleRegistry.cs" />
|
||||
<Compile Include="Bridge\Queue\CatalystQueueConfiguration.cs" />
|
||||
<Compile Include="Bridge\Queue\CatalystQueueConfigurationSpec.cs" />
|
||||
|
@ -152,8 +151,13 @@
|
|||
<Compile Include="Bridge\ReactContext.cs" />
|
||||
<Compile Include="Bridge\ReactContextNativeModuleBase.cs" />
|
||||
<Compile Include="Common\ReactConstants.cs" />
|
||||
<Compile Include="Core\ReactInstanceManager.cs" />
|
||||
<Compile Include="Core\ReactRootView.cs" />
|
||||
<Compile Include="Bridge\ReactApplicationContext.cs" />
|
||||
<Compile Include="CoreModulesPackage.cs" />
|
||||
<Compile Include="LifecycleState.cs" />
|
||||
<Compile Include="ReactInstanceManager.cs" />
|
||||
<Compile Include="ReactInstanceManagerImpl.cs" />
|
||||
<Compile Include="ReactRootView.cs" />
|
||||
<Compile Include="csslayout\CSSNode.cs" />
|
||||
<Compile Include="Hosting\JavaScriptBackgroundWorkItemCallback.cs" />
|
||||
<Compile Include="Hosting\JavaScriptBeforeCollectCallback.cs" />
|
||||
<Compile Include="Hosting\JavaScriptContext.cs" />
|
||||
|
@ -183,6 +187,9 @@
|
|||
<Compile Include="Modules\Core\RCTNativeAppEventEmitter.cs" />
|
||||
<Compile Include="Bridge\ReactDelegateFactoryBase.cs" />
|
||||
<Compile Include="Reflection\ExpressionExtensions.cs" />
|
||||
<Compile Include="Modules\Core\IReactPackage.cs" />
|
||||
<Compile Include="touch\JSResponderHandler.cs" />
|
||||
<Compile Include="touch\OnInterceptTouchEventListener.cs" />
|
||||
<Compile Include="UIManager\Events\RCTEventEmitter.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Bridge\IReactBridge.cs" />
|
||||
|
@ -193,8 +200,20 @@
|
|||
<Compile Include="Tracing\TraceDisposable.cs" />
|
||||
<Compile Include="Tracing\Tracer.cs" />
|
||||
<Compile Include="UIManager\AppRegistry.cs" />
|
||||
<Compile Include="UIManager\RootView.cs" />
|
||||
<Compile Include="UIManager\SizeMonitoringFrameLayout.cs" />
|
||||
<Compile Include="UIManager\NativeViewHierarchyManager.cs" />
|
||||
<Compile Include="UIManager\ReactProp.cs" />
|
||||
<Compile Include="UIManager\ReactShadowNode.cs" />
|
||||
<Compile Include="UIManager\IRootView.cs" />
|
||||
<Compile Include="UIManager\ShadowNodeRegistry.cs" />
|
||||
<Compile Include="UIManager\ThemedReactContext.cs" />
|
||||
<Compile Include="UIManager\UIImplementation.cs" />
|
||||
<Compile Include="UIManager\UIImplementationProvider.cs" />
|
||||
<Compile Include="UIManager\UIManagerModule.cs" />
|
||||
<Compile Include="UIManager\UIViewOperationQueue.cs" />
|
||||
<Compile Include="UIManager\ViewManager.cs" />
|
||||
<Compile Include="UIManager\ViewManagerRegistry.cs" />
|
||||
<Compile Include="UIManager\ViewManagersPropertyCache.cs" />
|
||||
<EmbeddedResource Include="Properties\ReactNative.rd.xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
using ReactNative.UIManager;
|
||||
using System;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace ReactNative
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Default root view for catalyst apps. Provides the ability to listen
|
||||
/// for size changes so that a UI manager can re-layout its elements
|
||||
/// TODO:
|
||||
/// 1. Add support for events(i.e. define the sendEvents method)
|
||||
/// 2. Add lifecycle functions to ensure the bundle is only loaded once
|
||||
/// </summary>
|
||||
public class ReactRootView : IRootView
|
||||
{
|
||||
private ReactInstanceManager _ReactInstanceManager;
|
||||
private string _JSModuleName;
|
||||
private int _rootTageNode;
|
||||
private bool _IsAttachedToWindow;
|
||||
|
||||
|
||||
public void OnChildStartedNativeGesture(RoutedEventArgs ev)
|
||||
{
|
||||
throw new NotImplementedException("Native gesture event handling is not yet supported");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exposes the Javascript module name of the root view
|
||||
/// </summary>
|
||||
public string JSModuleName
|
||||
{
|
||||
get
|
||||
{
|
||||
return _JSModuleName;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exposes the react tag id of the view
|
||||
/// </summary>
|
||||
public int TagId
|
||||
{
|
||||
get { return _rootTageNode; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the react tag id to the view
|
||||
/// </summary>
|
||||
/// <param name="tagId"></param>
|
||||
public void BindTagToView(int tagId)
|
||||
{
|
||||
_rootTageNode = tagId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedule rendering of the react component rendered by the JS application from the given JS
|
||||
/// module <see cref="moduleName" /> using provided <see cref="ReactInstanceManager" />
|
||||
/// </summary>
|
||||
/// <param name="reactInstanceManager">The React Instance Manager</param>
|
||||
/// <param name="moduleName">module to load</param>
|
||||
public void StartReactApplication(ReactInstanceManager reactInstanceManager, string moduleName)
|
||||
{
|
||||
_ReactInstanceManager = reactInstanceManager;
|
||||
_JSModuleName = moduleName;
|
||||
|
||||
_ReactInstanceManager.RecreateReactContextInBackgroundFromBundleFileAsync();
|
||||
|
||||
// We need to wait for the initial onMeasure, if this view has not yet been measured, we set
|
||||
// mAttachScheduled flag, which will make this view startReactApplication itself to instance
|
||||
// manager once onMeasure is called.
|
||||
if (!_IsAttachedToWindow)
|
||||
{
|
||||
_ReactInstanceManager.AttachMeasuredRootView(this);
|
||||
_IsAttachedToWindow = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
using Newtonsoft.Json.Linq;
|
||||
using ReactNative.Bridge;
|
||||
using ReactNative.Bridge;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
|
@ -13,7 +13,7 @@ namespace ReactNative.UIManager
|
|||
/// </summary>
|
||||
/// <param name="appKey">The app key.</param>
|
||||
/// <param name="appParameters">The app parameters.</param>
|
||||
public void runApplication(string appKey, JObject appParameters)
|
||||
public void runApplication(string appKey, IDictionary<string, object> appParameters)
|
||||
{
|
||||
Invoke(appKey, appParameters);
|
||||
}
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for the root native view of a React native application.
|
||||
/// </summary>
|
||||
public interface RootView
|
||||
public interface IRootView
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when a child starts a native gesture (e.g. a scroll in a ScrollView).
|
||||
/// </summary>
|
||||
/// <param name="androidEvent"></param>
|
||||
//void onChildStartedNativeGesture(CalibrationEventArgs wpEvent);
|
||||
void OnChildStartedNativeGesture(RoutedEventArgs ev);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System.Collections.Generic;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate of <see cref="UIManagerModule"/> that owns the native view hierarchy and mapping between
|
||||
/// native view names used in JS and corresponding instances of <see cref="ViewManager"/>
|
||||
/// </summary>
|
||||
public class NativeViewHierarchyManager
|
||||
{
|
||||
private readonly Dictionary<int, ViewManager<FrameworkElement, ReactShadowNode>> _TagsToViewManagers;
|
||||
private readonly ViewManagerRegistry _ViewManagers;
|
||||
|
||||
public NativeViewHierarchyManager(ViewManagerRegistry viewManagers)
|
||||
{
|
||||
_ViewManagers = viewManagers;
|
||||
_TagsToViewManagers = new Dictionary<int, ViewManager<FrameworkElement, ReactShadowNode>>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
public abstract class ReactProp
|
||||
{
|
||||
private readonly string USE_DEFAULT_TYPE = "__default_type__";
|
||||
private readonly double DEFAULT_DOUBLE = 0.0;
|
||||
private readonly float DEFAULT_FLOAT = 0.0f;
|
||||
private readonly int DEFAULT_INT = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the property exposed to JS
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected abstract string name();
|
||||
|
||||
/// <summary>
|
||||
/// Type of property that will be send to JS.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected virtual string customType() {
|
||||
return USE_DEFAULT_TYPE;
|
||||
}
|
||||
|
||||
public double defaultDouble() {
|
||||
return DEFAULT_DOUBLE;
|
||||
}
|
||||
|
||||
public float defaultFloat()
|
||||
{
|
||||
return DEFAULT_FLOAT;
|
||||
}
|
||||
|
||||
public int defaultInt()
|
||||
{
|
||||
return DEFAULT_INT;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
using ReactNative.csslayout;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
public class ReactShadowNode : CSSNode
|
||||
{
|
||||
private int _ReactTag;
|
||||
private float _AbsoluteLeft;
|
||||
private float _AbsoluteTop;
|
||||
private float _AbsoluteRight;
|
||||
private float _AbsoluteBottom;
|
||||
private bool _ShouldNotifyOnLayout;
|
||||
private bool _NodeUpdated = true;
|
||||
|
||||
// layout-only nodes
|
||||
private ReactShadowNode mNativeParent;
|
||||
private List<ReactShadowNode> mNativeChildren;
|
||||
|
||||
public int getReactTag()
|
||||
{
|
||||
return _ReactTag;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple container class to keep track of <see cref="ReactShadowNode" />s associated with a particular
|
||||
/// <see cref="UIManagerModule" /> instance.
|
||||
/// </summary>
|
||||
class ShadowNodeRegistry
|
||||
{
|
||||
private readonly Dictionary<int, ReactShadowNode> _TagsToCSSNodes;
|
||||
private readonly Dictionary<int, bool> _RootTags;
|
||||
|
||||
public ShadowNodeRegistry()
|
||||
{
|
||||
_TagsToCSSNodes = new Dictionary<int, ReactShadowNode>();
|
||||
_RootTags = new Dictionary<int, bool>();
|
||||
}
|
||||
|
||||
public void addRootNode(ReactShadowNode node)
|
||||
{
|
||||
int tag = node.getReactTag();
|
||||
_TagsToCSSNodes.Add(tag, node);
|
||||
_RootTags.Add(tag, true);
|
||||
}
|
||||
|
||||
public void removeRootNode(int tag)
|
||||
{
|
||||
if (!_RootTags.ContainsKey(tag))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"View with tag " + tag + " is not registered as a root view");
|
||||
}
|
||||
|
||||
_TagsToCSSNodes.Remove(tag);
|
||||
_RootTags.Remove(tag);
|
||||
}
|
||||
|
||||
public void addNode(ReactShadowNode node)
|
||||
{
|
||||
_TagsToCSSNodes.Add(node.getReactTag(), node);
|
||||
}
|
||||
|
||||
public void removeNode(int tag)
|
||||
{
|
||||
if (_RootTags[tag])
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Trying to remove root node " + tag + " without using removeRootNode!");
|
||||
}
|
||||
_TagsToCSSNodes.Remove(tag);
|
||||
}
|
||||
|
||||
public ReactShadowNode getNode(int tag)
|
||||
{
|
||||
return _TagsToCSSNodes[tag];
|
||||
}
|
||||
|
||||
public bool isRootNode(int tag)
|
||||
{
|
||||
return _RootTags.ContainsKey(tag);
|
||||
}
|
||||
|
||||
public int getRootNodeCount()
|
||||
{
|
||||
return _RootTags.Count;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// allows registering for size change events. The main purpose for this class is to hide complexity of ReactRootView
|
||||
/// </summary>
|
||||
|
@ -13,21 +14,6 @@ namespace ReactNative.UIManager
|
|||
|
||||
private OnSizeChangedListener mOnSizeChangedListener;
|
||||
|
||||
/*public SizeMonitoringFrameLayout(Context context)
|
||||
{
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SizeMonitoringFrameLayout(Context context, AttributeSet attrs)
|
||||
{
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SizeMonitoringFrameLayout(Context context, AttributeSet attrs, int defStyle)
|
||||
{
|
||||
super(context, attrs, defStyle);
|
||||
}*/
|
||||
|
||||
public void setOnSizeChangedListener(OnSizeChangedListener onSizeChangedListener)
|
||||
{
|
||||
mOnSizeChangedListener = onSizeChangedListener;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
using ReactNative.Bridge;
|
||||
|
||||
public class ThemedReactContext : ReactContext
|
||||
{
|
||||
private readonly ReactApplicationContext mReactApplicationContext;
|
||||
|
||||
public ThemedReactContext(ReactApplicationContext reactApplicationContext) {
|
||||
InitializeWithInstance(reactApplicationContext.CatalystInstance);
|
||||
mReactApplicationContext = reactApplicationContext;
|
||||
}
|
||||
|
||||
public void addLifecycleEventListener(ILifecycleEventListener listener)
|
||||
{
|
||||
mReactApplicationContext.AddLifecycleEventListener(listener);
|
||||
}
|
||||
|
||||
public void removeLifecycleEventListener(ILifecycleEventListener listener)
|
||||
{
|
||||
mReactApplicationContext.RemoveLifecycleEventListener(listener);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
using ReactNative.Bridge;
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
/// <summary>
|
||||
/// An class that is used to receive React commands from JS and translate them into a
|
||||
/// shadow node hierarchy that is then mapped to a native view hierarchy.
|
||||
///
|
||||
/// TODOS
|
||||
/// 1. CSSLayoutContext
|
||||
/// 2. Implement _ViewManagers registry
|
||||
/// 3. Create ShadowNodeRegistry
|
||||
/// 4. View reigstration for root and children
|
||||
/// 5. Shadow dom item updates
|
||||
/// </summary>
|
||||
public class UIImplementation
|
||||
{
|
||||
private readonly ViewManagerRegistry _ViewManagers;
|
||||
private readonly UIViewOperationQueue _OperationsQueue;
|
||||
private readonly ShadowNodeRegistry _ShadowNodeRegistry = new ShadowNodeRegistry();
|
||||
|
||||
public UIImplementation(ReactApplicationContext reactContext,
|
||||
List<ViewManager<FrameworkElement, ReactShadowNode>> viewManagers)
|
||||
{
|
||||
_ViewManagers = new ViewManagerRegistry(viewManagers);
|
||||
_OperationsQueue = new UIViewOperationQueue(reactContext, new NativeViewHierarchyManager(_ViewManagers));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
using ReactNative.Bridge;
|
||||
using ReactNative.UIManager;
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides UIImplementation to use in <see cref="UIManagerModule" />
|
||||
/// </summary>
|
||||
public class UIImplementationProvider
|
||||
{
|
||||
public UIImplementation createUIImplementation(ReactApplicationContext reactContext, List<ViewManager<FrameworkElement, ReactShadowNode>> viewManagers)
|
||||
{
|
||||
return new UIImplementation(reactContext, viewManagers);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
|
||||
using ReactNative.Bridge;
|
||||
using ReactNative.UIManager;
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Native module to allow JS to create and update native Views.
|
||||
/// </summary>
|
||||
public class UIManagerModule : NativeModuleBase
|
||||
{
|
||||
private readonly UIImplementation _UIImplementation;
|
||||
private int nextRootTag = 1;
|
||||
|
||||
public UIManagerModule(ReactApplicationContext reactContext,
|
||||
List<ViewManager<FrameworkElement, ReactShadowNode>> viewManagerList,
|
||||
UIImplementation uiImplementation)
|
||||
{
|
||||
_UIImplementation = uiImplementation;
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return "RKUIManager";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new root view. JS can use the returned tag with manageChildren to add/remove
|
||||
/// children to this view.
|
||||
///
|
||||
/// TODO:
|
||||
/// 1.This needs to be more formally implemented so that it takes <see cref="ThemedReactContext" /> into consideration. This is a
|
||||
/// temporary implementation
|
||||
/// </summary>
|
||||
/// <param name="rootView"></param>
|
||||
/// <returns></returns>
|
||||
public int AddMeasuredRootView(ReactRootView rootView)
|
||||
{
|
||||
var tag = nextRootTag;
|
||||
nextRootTag += 10;
|
||||
rootView.BindTagToView(nextRootTag);
|
||||
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
using ReactNative.Bridge;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
/// <summary>
|
||||
/// This class acts as a buffer for command executed on <see cref="NativeViewHierarchyManager"/>. It expose similar
|
||||
/// methods as mentioned classes but instead of executing commands immediately it enqueues those operations
|
||||
/// in a queue that is then flushed from <see cref="UIManagerModule"/> once JS batch of ui operations is finished.
|
||||
/// </summary>
|
||||
public class UIViewOperationQueue
|
||||
{
|
||||
private readonly int[] _MeasureBuffer = new int[4];
|
||||
private readonly NativeViewHierarchyManager _NativeViewHierarchyManager;
|
||||
private readonly ReactApplicationContext _ReactApplicationContext;
|
||||
|
||||
/// <summary>
|
||||
/// A spec for an operation on the native View hierarchy.
|
||||
/// </summary>
|
||||
protected interface UIOperation
|
||||
{
|
||||
void execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A spec for an operation on the native View hierarchy.
|
||||
/// </summary>
|
||||
private abstract class ViewOperation : UIOperation
|
||||
{
|
||||
|
||||
public int mTag;
|
||||
|
||||
public ViewOperation(int tag)
|
||||
{
|
||||
mTag = tag;
|
||||
}
|
||||
|
||||
public abstract void execute();
|
||||
}
|
||||
|
||||
public UIViewOperationQueue(ReactApplicationContext reactContext, NativeViewHierarchyManager nativeViewHierarchyManager)
|
||||
{
|
||||
_NativeViewHierarchyManager = nativeViewHierarchyManager;
|
||||
_ReactApplicationContext = reactContext;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
public abstract class ViewManager<T, C> where T : FrameworkElement
|
||||
where C: ReactShadowNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a view and installs event emitters on it.
|
||||
/// </summary>
|
||||
/*public sealed T createView(ThemedReactContext reactContext, JSResponderHandler jsResponderHandler)
|
||||
{
|
||||
T view = createViewInstance(reactContext);
|
||||
addEventEmitters(reactContext, view);
|
||||
if (view instanceof CatalystInterceptingViewGroup) {
|
||||
((CatalystInterceptingViewGroup)view).setOnInterceptTouchEventListener(jsResponderHandler);
|
||||
}
|
||||
return view;
|
||||
}*/
|
||||
|
||||
/// <summary>
|
||||
/// Subclasses should return a new View instance of the proper type.
|
||||
/// </summary>
|
||||
/// <param name="reactContext"></param>
|
||||
/// <returns></returns>
|
||||
protected abstract T createViewInstance(ThemedReactContext reactContext);
|
||||
|
||||
/// <summary>
|
||||
/// the name of this view manager. This will be the name used to reference this view manager from JavaScript in createReactNativeComponentClass.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public abstract string getName();
|
||||
|
||||
/// <summary>
|
||||
/// Called when view is detached from view hierarchy and allows for some additional cleanup by the {@link ViewManager} subclass.
|
||||
/// </summary>
|
||||
/// <param name="reactContext"></param>
|
||||
/// <param name="view"></param>
|
||||
public void onDropViewInstance(ThemedReactContext reactContext, T view)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subclasses can override this method to install custom event emitters on the given View. You might want to override this method if your view needs to emit events besides basic touch events * to JS (e.g.scroll events).
|
||||
/// </summary>
|
||||
/// <param name="reactContext"></param>
|
||||
/// <param name="view"></param>
|
||||
protected void addEventEmitters(ThemedReactContext reactContext, T view)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a dictoinary of view-specific constants that are injected to JavaScript. These constants are made accessible via UIManager.<ViewName>.Constants.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/*public Dictionary<string, string> getNativeProps()
|
||||
{
|
||||
return ViewManagersPropertyCache.getNativePropsForView(getClass(), getShadowNodeClass());
|
||||
}*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
using UIManager;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
public class ViewManagerRegistry
|
||||
{
|
||||
private readonly Dictionary<string, ViewManager<FrameworkElement, ReactShadowNode>> mViewManagers = new Dictionary<string, ViewManager<FrameworkElement, ReactShadowNode>>();
|
||||
|
||||
public ViewManagerRegistry(List<ViewManager<FrameworkElement, ReactShadowNode>> viewManagerList)
|
||||
{
|
||||
foreach (var viewManager in viewManagerList)
|
||||
{
|
||||
mViewManagers.Add(viewManager.getName(), viewManager);
|
||||
}
|
||||
}
|
||||
|
||||
public ViewManager<FrameworkElement, ReactShadowNode> get(String className)
|
||||
{
|
||||
var viewManager = mViewManagers[className];
|
||||
if (viewManager != null)
|
||||
{
|
||||
return viewManager;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("No ViewManager defined for class " + className);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ReactNative.UIManager
|
||||
{
|
||||
public class ViewManagersPropertyCache
|
||||
{
|
||||
private static readonly Dictionary<Type, Dictionary<string, PropSetter>> CLASS_PROPS_CACHE = new Dictionary<Type, Dictionary<string, PropSetter>>();
|
||||
private static readonly Dictionary<string, PropSetter> EMPTY_PROPS_MAP = new Dictionary<string, PropSetter>();
|
||||
|
||||
abstract class PropSetter
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
namespace ReactNative.csslayout
|
||||
{
|
||||
public class CSSNode
|
||||
{
|
||||
private enum LayoutState
|
||||
{
|
||||
/**
|
||||
* Some property of this node or its children has changes and the current values in
|
||||
* {@link #layout} are not valid.
|
||||
*/
|
||||
DIRTY,
|
||||
|
||||
/**
|
||||
* This node has a new layout relative to the last time {@link #markLayoutSeen()} was called.
|
||||
*/
|
||||
HAS_NEW_LAYOUT,
|
||||
|
||||
/**
|
||||
* {@link #layout} is valid for the node's properties and this layout has been marked as
|
||||
* having been seen.
|
||||
*/
|
||||
UP_TO_DATE,
|
||||
}
|
||||
|
||||
public interface MeasureFunction
|
||||
{
|
||||
|
||||
/**
|
||||
* Should measure the given node and put the result in the given MeasureOutput.
|
||||
*
|
||||
* NB: measure is NOT guaranteed to be threadsafe/re-entrant safe!
|
||||
*/
|
||||
void measure(CSSNode node, float width);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
|
||||
namespace ReactNative.touch
|
||||
{
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
/// <summary>
|
||||
/// This class coordinates JSResponder commands for UIManagerModule
|
||||
/// </summary>
|
||||
public class JSResponderHandler
|
||||
{
|
||||
/*private static readonly int JS_RESPONDER_UNSET = -1;
|
||||
|
||||
private volatile int mCurrentJSResponder = JS_RESPONDER_UNSET;
|
||||
|
||||
public void setJSResponder(int tag) {
|
||||
mCurrentJSResponder = tag;
|
||||
// We need to unblock the native responder first, otherwise we can get in a bad state: a
|
||||
// ViewParent sets requestDisallowInterceptTouchEvent to true, which sets this setting to true
|
||||
// to all of its ancestors. Now, if one of its ancestors sets requestDisallowInterceptTouchEvent
|
||||
// to false, it unsets the setting for itself and all of its ancestors, which means that they
|
||||
// can intercept events again.
|
||||
maybeUnblockNativeResponder();
|
||||
if (viewParentBlockingNativeResponder != null)
|
||||
{
|
||||
viewParentBlockingNativeResponder.requestDisallowInterceptTouchEvent(true);
|
||||
mViewParentBlockingNativeResponder = viewParentBlockingNativeResponder;
|
||||
}
|
||||
}
|
||||
|
||||
public void clearJSResponder() {
|
||||
mCurrentJSResponder = JS_RESPONDER_UNSET;
|
||||
maybeUnblockNativeResponder();
|
||||
}
|
||||
|
||||
private void maybeUnblockNativeResponder() {
|
||||
if (mViewParentBlockingNativeResponder != null)
|
||||
{
|
||||
mViewParentBlockingNativeResponder.requestDisallowInterceptTouchEvent(false);
|
||||
mViewParentBlockingNativeResponder = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool onInterceptTouchEvent(RoutedEventArgs ev) {
|
||||
int currentJSResponder = mCurrentJSResponder;
|
||||
if (currentJSResponder != JS_RESPONDER_UNSET && ev.getAction() != MotionEvent.ACTION_UP) {
|
||||
// Don't intercept ACTION_UP events. If we return true here than UP event will not be
|
||||
// delivered. That is because intercepted touch events are converted into CANCEL events
|
||||
// and make all further events to be delivered to the view that intercepted the event.
|
||||
// Therefore since "UP" event is the last event in a gesture, we should just let it reach the
|
||||
// original target that is a child view of {@param v}.
|
||||
// http://developer.android.com/reference/android/view/ViewGroup.html#onInterceptTouchEvent(android.view.MotionEvent)
|
||||
return v.getId() == currentJSResponder;
|
||||
}
|
||||
return false;
|
||||
}*/
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
namespace ReactNative.touch
|
||||
{
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
public interface OnInterceptTouchEventListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when a onInterceptTouch is invoked on a view group
|
||||
/// </summary>
|
||||
/// <param name="event"> The motion event being dispatched down the hierarchy.</param>
|
||||
/// <returns>Return true to steal motion event from the children and have the dispatched to this view, or return false to allow motion event to be delivered to children view</returns>
|
||||
bool onInterceptTouchEvent(RoutedEventArgs ev);
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
Двоичный файл не отображается.
Загрузка…
Ссылка в новой задаче