2015-02-04 updates

- Unbreak ReactKit | Nick Lockwood
- Refactor | Nick Lockwood
- [ReactNative] fix touch cancel behavior | Spencer Ahrens
- [ReactNative iOS] Fix responder issue with textInput | Eric Vicenti
- [ReactNative] README updates - file watching troubleshooting, one-time code drop -> currently private repo | Spencer Ahrens
- [ReactKit] Re-add README linebreaks | Ben Alpert
This commit is contained in:
Christopher Chedeau
2015-02-06 15:43:59 -08:00
parent 6153fffb30
commit fd8b7dee77
55 changed files with 1515 additions and 1668 deletions

View File

@@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTNavItemManager : RCTUIViewManager
@interface RCTNavItemManager : RCTViewManager
@end

View File

@@ -2,9 +2,11 @@
#import <UIKit/UIKit.h>
#import "RCTInvalidating.h"
@class RCTEventDispatcher;
@interface RCTNavigator : UIView <UINavigationControllerDelegate>
@interface RCTNavigator : UIView <RCTInvalidating>
@property (nonatomic, strong) UIView *reactNavSuperviewLink;
@property (nonatomic, assign) NSInteger requestedTopOfStack;

View File

@@ -200,7 +200,7 @@ NSInteger kNeverProgressed = -10000;
@end
@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener>
@interface RCTNavigator() <RCTWrapperViewControllerNavigationListener, UINavigationControllerDelegate>
{
RCTEventDispatcher *_eventDispatcher;
NSInteger _numberOfViewControllerMovesToIgnore;
@@ -417,9 +417,14 @@ NSInteger kNeverProgressed = -10000;
return _currentViews;
}
- (void)reactWillDestroy
- (BOOL)isValid
{
// Removes run loop's references to `displayLink`.
return _displayLink != nil;
}
- (void)invalidate
{
// Prevent displayLink from retaining the navigator indefinitely
[_displayLink invalidate];
_displayLink = nil;
_runTimer = nil;

View File

@@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTNavigatorManager : RCTUIViewManager
@interface RCTNavigatorManager : RCTViewManager
@end

View File

@@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTNetworkImageViewManager : RCTUIViewManager
@interface RCTNetworkImageViewManager : RCTViewManager
@end

View File

@@ -1,7 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTRawTextManager : RCTUIViewManager
@interface RCTRawTextManager : RCTViewManager
@end

View File

@@ -242,11 +242,6 @@ CGFloat const ZINDEX_STICKY_HEADER = 50;
return frame;
}
- (void)insertReactSubview:(NSObject<RCTViewNodeProtocol> *)subview atIndex:(NSInteger)atIndex
{
[super insertReactSubview:subview atIndex:atIndex];
}
@end
@implementation RCTScrollView

View File

@@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTScrollViewManager : RCTUIViewManager
@interface RCTScrollViewManager : RCTViewManager
@end

View File

@@ -1,7 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTStaticImageManager : RCTUIViewManager
@interface RCTStaticImageManager : RCTViewManager
@end

View File

@@ -14,12 +14,13 @@
return [[RCTStaticImage alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(capInsets)
RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode)
- (void)set_src:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView
{
if (json) {
if ([json isKindOfClass:[NSString class]] && [[json pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
if ([[[json description] pathExtension] caseInsensitiveCompare:@"gif"] == NSOrderedSame) {
[view.layer addAnimation:[RCTConvert GIF:json] forKey:@"contents"];
} else {
view.image = [RCTConvert UIImage:json];
@@ -29,11 +30,6 @@ RCT_REMAP_VIEW_PROPERTY(resizeMode, contentMode)
}
}
- (void)set_capInsets:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView
{
view.capInsets = json ? [RCTConvert UIEdgeInsets:json] : defaultView.capInsets;
}
- (void)set_tintColor:(id)json forView:(RCTStaticImage *)view withDefaultView:(RCTStaticImage *)defaultView
{
if (json) {

View File

@@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTTextFieldManager : RCTUIViewManager
@interface RCTTextFieldManager : RCTViewManager
@end

View File

@@ -1,8 +1,8 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTTextManager : RCTUIViewManager
@interface RCTTextManager : RCTViewManager
@end

View File

@@ -67,6 +67,13 @@ RCT_REMAP_VIEW_PROPERTY(textAlign, textAlignment);
shadowView.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
}
// TODO: the purpose of this block is effectively just to copy properties from the shadow views
// to their equivalent UIViews. In this case, the property being copied is the attributed text,
// but the same principle could be used to copy any property. The implementation is really ugly tho
// because the RCTViewManager doesn't retain a reference to the views that it manages, so it basically
// has to search the entire view hierarchy for relevant views. Not awesome. This seems like something
// where we could introduce a generic solution - perhaps a method on RCTShadowView that is called after
// layout to copy its properties across?
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
NSMutableArray *shadowBlocks = [NSMutableArray new];

View File

@@ -1,7 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTViewManager.h"
@interface RCTUIActivityIndicatorViewManager : RCTUIViewManager
@interface RCTUIActivityIndicatorViewManager : RCTViewManager
@end

View File

@@ -1,15 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
#import "RCTExport.h"
@class RCTEventDispatcher;
@interface RCTUIViewManager : NSObject <RCTNativeViewModule>
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly) RCTEventDispatcher *eventDispatcher;
@end

View File

@@ -1,143 +0,0 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTShadowView.h"
#import "RCTView.h"
@implementation RCTUIViewManager
{
__weak RCTEventDispatcher *_eventDispatcher;
}
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super init])) {
_eventDispatcher = eventDispatcher;
}
return self;
}
+ (NSString *)moduleName
{
// Default implementation, works in most cases
NSString *name = NSStringFromClass(self);
if ([name hasPrefix:@"RCTUI"]) {
name = [name substringFromIndex:@"RCT".length];
}
if ([name hasSuffix:@"Manager"]) {
name = [name substringToIndex:name.length - @"Manager".length];
}
return name;
}
- (UIView *)view
{
return [[UIView alloc] init];
}
// View properties
RCT_EXPORT_VIEW_PROPERTY(accessibilityLabel)
RCT_EXPORT_VIEW_PROPERTY(hidden)
RCT_EXPORT_VIEW_PROPERTY(backgroundColor)
RCT_REMAP_VIEW_PROPERTY(accessible, isAccessibilityElement)
RCT_REMAP_VIEW_PROPERTY(testID, accessibilityIdentifier)
RCT_REMAP_VIEW_PROPERTY(opacity, alpha)
RCT_REMAP_VIEW_PROPERTY(shadowColor, layer.shadowColor);
RCT_REMAP_VIEW_PROPERTY(shadowOffset, layer.shadowOffset);
RCT_REMAP_VIEW_PROPERTY(shadowOpacity, layer.shadowOpacity)
RCT_REMAP_VIEW_PROPERTY(shadowRadius, layer.shadowRadius)
RCT_REMAP_VIEW_PROPERTY(borderColor, layer.borderColor);
RCT_REMAP_VIEW_PROPERTY(borderRadius, layer.cornerRadius)
RCT_REMAP_VIEW_PROPERTY(borderWidth, layer.borderWidth)
RCT_REMAP_VIEW_PROPERTY(transformMatrix, layer.transform)
- (void)set_overflow:(id)json
forView:(UIView *)view
withDefaultView:(UIView *)defaultView
{
view.clipsToBounds = json ? ![RCTConvert css_overflow:json] : defaultView.clipsToBounds;
}
- (void)set_pointerEvents:(id)json
forView:(UIView *)view
withDefaultView:(UIView *)defaultView
{
if (!json) {
view.userInteractionEnabled = defaultView.userInteractionEnabled;
return;
}
switch ([RCTConvert NSInteger:json]) {
case RCTPointerEventsUnspecified:
// Pointer events "unspecified" acts as if a stylesheet had not specified,
// which is different than "auto" in CSS (which cannot and will not be
// supported in `ReactKit`. "auto" may override a parent's "none".
// Unspecified values do not.
// This wouldn't override a container view's `userInteractionEnabled = NO`
view.userInteractionEnabled = YES;
case RCTPointerEventsNone:
view.userInteractionEnabled = NO;
break;
default:
RCTLogError(@"UIView base class does not support pointerEvent value: %@", json);
}
}
// ShadowView properties
- (void)set_backgroundColor:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
shadowView.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
}
- (void)set_flexDirection:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.flexDirection = json? [RCTConvert css_flex_direction_t:json] : defaultView.flexDirection;
}
- (void)set_flexWrap:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.flexWrap = json ? [RCTConvert css_wrap_type_t:json] : defaultView.flexWrap;
}
- (void)set_justifyContent:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.justifyContent = json ? [RCTConvert css_justify_t:json] : defaultView.justifyContent;
}
- (void)set_alignItems:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.alignItems = json ? [RCTConvert css_align_t:json] : defaultView.alignItems;
}
- (void)set_alignSelf:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.alignSelf = json ? [RCTConvert css_align_t:json] : defaultView.alignSelf;
}
- (void)set_position:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.positionType = json ? [RCTConvert css_position_type_t:json] : defaultView.positionType;
}
@end

View File

@@ -4,19 +4,13 @@
#import <UIKit/UIKit.h>
// TODO: rehome this
typedef NS_ENUM(NSInteger, RCTPointerEventsValue) {
RCTPointerEventsUnspecified = 0, // Default
RCTPointerEventsNone,
RCTPointerEventsBoxNone,
RCTPointerEventsBoxOnly,
};
#import "RCTPointerEvents.h"
@protocol RCTAutoInsetsProtocol;
@interface RCTView : UIView
@property (nonatomic, assign) RCTPointerEventsValue pointerEvents;
@property (nonatomic, assign) RCTPointerEvents pointerEvents;
@property (nonatomic, copy) NSString *overrideAccessibilityLabel;
+ (void)autoAdjustInsetsForView:(UIView<RCTAutoInsetsProtocol> *)parentView

View File

@@ -40,7 +40,7 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
return RCTRecursiveAccessibilityLabel(self);
}
- (void)setPointerEvents:(RCTPointerEventsValue)pointerEvents
- (void)setPointerEvents:(RCTPointerEvents)pointerEvents
{
_pointerEvents = pointerEvents;
self.userInteractionEnabled = (pointerEvents != RCTPointerEventsNone);

View File

@@ -1,7 +1,148 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTUIViewManager.h"
#import <UIKit/UIKit.h>
@interface RCTViewManager : RCTUIViewManager
#import "RCTConvert.h"
#import "RCTLog.h"
@class RCTEventDispatcher;
@class RCTShadowView;
@class RCTSparseArray;
@class RCTUIManager;
typedef void (^RCTViewManagerUIBlock)(RCTUIManager *uiManager, RCTSparseArray *viewRegistry);
@interface RCTViewManager : NSObject
/**
* Designated initializer for view modules. Override this when subclassing.
*/
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
/**
* The event dispatcher is used to send events back to the JavaScript application.
* It can either be used directly by the module, or passed on to instantiated
* view subclasses so that they can handle their own events.
*/
@property (nonatomic, readonly, weak) RCTEventDispatcher *eventDispatcher;
/**
* The module name exposed to React JS. If omitted, this will be inferred
* automatically by using the view module's class name. It is better to not
* override this, and just follow standard naming conventions for your view
* module subclasses.
*/
+ (NSString *)moduleName;
/**
* This method instantiates a native view to be managed by the module. Override
* this to return a custom view instance, which may be preconfigured with default
* properties, subviews, etc. This method will be called many times, and should
* return a fresh instance each time. The view module MUST NOT cache the returned
* view and return the same instance for subsequent calls.
*/
- (UIView *)view;
/**
* This method instantiates a shadow view to be managed by the module. If omitted,
* an ordinary RCTShadowView instance will be created, which is typically fine for
* most view types. As with the -view method, the -shadowView method should return
* a fresh instance each time it is called.
*/
- (RCTShadowView *)shadowView;
/**
* Returns a dictionary of config data passed to JS that defines eligible events
* that can be placed on native views. This should return bubbling
* directly-dispatched event types and specify what names should be used to
* subscribe to either form (bubbling/capturing).
*
* Returned dictionary should be of the form: @{
* @"onTwirl": {
* @"phasedRegistrationNames": @{
* @"bubbled": @"onTwirl",
* @"captured": @"onTwirlCaptured"
* }
* }
* }
*
* Note that this method is not inherited when you subclass a view module, and
* you should not call [super customBubblingEventTypes] when overriding it.
*/
+ (NSDictionary *)customBubblingEventTypes;
/**
* Returns a dictionary of config data passed to JS that defines eligible events
* that can be placed on native views. This should return non-bubbling
* directly-dispatched event types.
*
* Returned dictionary should be of the form: @{
* @"onTwirl": {
* @"registrationName": @"onTwirl"
* }
* }
*
* Note that this method is not inherited when you subclass a view module, and
* you should not call [super customDirectEventTypes] when overriding it.
*/
+ (NSDictionary *)customDirectEventTypes;
/**
* Injects constants into JS. These constants are made accessible via
* NativeModules.moduleName.X. Note that this method is not inherited when you
* subclass a view module, and you should not call [super constantsToExport]
* when overriding it.
*/
+ (NSDictionary *)constantsToExport;
/**
* To deprecate, hopefully
*/
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry;
/**
* Informal protocol for setting view and shadowView properties.
* Implement methods matching these patterns to set any properties that
* require special treatment (e.g. where the type or name cannot be inferred).
*
* - (void)set_<propertyName>:(id)property
* forView:(UIView *)view
* withDefaultView:(UIView *)defaultView;
*
* - (void)set_<propertyName>:(id)property
* forShadowView:(RCTShadowView *)view
* withDefaultView:(RCTShadowView *)defaultView;
*
* For simple cases, use the macros below:
*/
/**
* This handles the simple case, where JS and native property names match
* And the type can be automatically inferred.
*/
#define RCT_EXPORT_VIEW_PROPERTY(name) \
RCT_REMAP_VIEW_PROPERTY(name, name)
/**
* This macro maps a named property on the module to an arbitrary key path
* within the view.
*/
#define RCT_REMAP_VIEW_PROPERTY(name, keypath) \
- (void)set_##name:(id)json forView:(id)view withDefaultView:(id)defaultView { \
if ((json && !RCTSetProperty(view, @#keypath, json)) || \
(!json && !RCTCopyProperty(view, defaultView, @#keypath))) { \
RCTLogMustFix(@"%@ does not have setter for `%s` property", [view class], #name); \
} \
}
/**
* These are useful in cases where the module's superclass handles a
* property, but you wish to "unhandle" it, so it will be ignored.
*/
#define RCT_IGNORE_VIEW_PROPERTY(name) \
- (void)set_##name:(id)value forView:(id)view withDefaultView:(id)defaultView {}
#define RCT_IGNORE_SHADOW_PROPERTY(name) \
- (void)set_##name:(id)value forShadowView:(id)view withDefaultView:(id)defaultView {}
@end

View File

@@ -2,15 +2,169 @@
#import "RCTViewManager.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTShadowView.h"
#import "RCTView.h"
@implementation RCTViewManager
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super init])) {
_eventDispatcher = eventDispatcher;
}
return self;
}
+ (NSString *)moduleName
{
// Default implementation, works in most cases
NSString *name = NSStringFromClass(self);
if ([name hasPrefix:@"RCTUI"]) {
name = [name substringFromIndex:@"RCT".length];
}
if ([name hasSuffix:@"Manager"]) {
name = [name substringToIndex:name.length - @"Manager".length];
}
return name;
}
- (UIView *)view
{
return [[RCTView alloc] init];
}
RCT_EXPORT_VIEW_PROPERTY(pointerEvents)
- (RCTShadowView *)shadowView
{
return [[RCTShadowView alloc] init];
}
+ (NSDictionary *)customBubblingEventTypes
{
return nil;
}
+ (NSDictionary *)customDirectEventTypes
{
return nil;
}
+ (NSDictionary *)constantsToExport
{
return nil;
}
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(RCTSparseArray *)shadowViewRegistry
{
return nil;
}
// View properties
RCT_EXPORT_VIEW_PROPERTY(accessibilityLabel)
RCT_EXPORT_VIEW_PROPERTY(hidden)
RCT_EXPORT_VIEW_PROPERTY(backgroundColor)
RCT_REMAP_VIEW_PROPERTY(accessible, isAccessibilityElement)
RCT_REMAP_VIEW_PROPERTY(testID, accessibilityIdentifier)
RCT_REMAP_VIEW_PROPERTY(opacity, alpha)
RCT_REMAP_VIEW_PROPERTY(shadowColor, layer.shadowColor);
RCT_REMAP_VIEW_PROPERTY(shadowOffset, layer.shadowOffset);
RCT_REMAP_VIEW_PROPERTY(shadowOpacity, layer.shadowOpacity)
RCT_REMAP_VIEW_PROPERTY(shadowRadius, layer.shadowRadius)
RCT_REMAP_VIEW_PROPERTY(borderColor, layer.borderColor);
RCT_REMAP_VIEW_PROPERTY(borderRadius, layer.cornerRadius)
RCT_REMAP_VIEW_PROPERTY(borderWidth, layer.borderWidth)
RCT_REMAP_VIEW_PROPERTY(transformMatrix, view.layer.transform)
- (void)set_overflow:(id)json
forView:(UIView *)view
withDefaultView:(UIView *)defaultView
{
view.clipsToBounds = json ? ![RCTConvert css_overflow:json] : defaultView.clipsToBounds;
}
- (void)set_pointerEvents:(id)json
forView:(UIView *)view
withDefaultView:(UIView *)defaultView
{
if ([view respondsToSelector:@selector(setPointerEvents:)]) {
[(id)view setPointerEvents:json ? [RCTConvert RCTPointerEvents:json] : [(id)defaultView pointerEvents]];
return;
}
if (!json) {
view.userInteractionEnabled = defaultView.userInteractionEnabled;
return;
}
switch ([RCTConvert NSInteger:json]) {
case RCTPointerEventsUnspecified:
// Pointer events "unspecified" acts as if a stylesheet had not specified,
// which is different than "auto" in CSS (which cannot and will not be
// supported in `ReactKit`. "auto" may override a parent's "none".
// Unspecified values do not.
// This wouldn't override a container view's `userInteractionEnabled = NO`
view.userInteractionEnabled = YES;
case RCTPointerEventsNone:
view.userInteractionEnabled = NO;
break;
default:
RCTLogError(@"UIView base class does not support pointerEvent value: %@", json);
}
}
// ShadowView properties
- (void)set_backgroundColor:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.backgroundColor = json ? [RCTConvert UIColor:json] : defaultView.backgroundColor;
shadowView.isBGColorExplicitlySet = json ? YES : defaultView.isBGColorExplicitlySet;
}
- (void)set_flexDirection:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.flexDirection = json? [RCTConvert css_flex_direction_t:json] : defaultView.flexDirection;
}
- (void)set_flexWrap:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.flexWrap = json ? [RCTConvert css_wrap_type_t:json] : defaultView.flexWrap;
}
- (void)set_justifyContent:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.justifyContent = json ? [RCTConvert css_justify_t:json] : defaultView.justifyContent;
}
- (void)set_alignItems:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.alignItems = json ? [RCTConvert css_align_t:json] : defaultView.alignItems;
}
- (void)set_alignSelf:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.alignSelf = json ? [RCTConvert css_align_t:json] : defaultView.alignSelf;
}
- (void)set_position:(id)json
forShadowView:(RCTShadowView *)shadowView
withDefaultView:(RCTShadowView *)defaultView
{
shadowView.positionType = json ? [RCTConvert css_position_type_t:json] : defaultView.positionType;
}
@end