Generic solution for live editing
Summary: Preparation for making components live editable Reviewed By: kevin0571 Differential Revision: D16379961 fbshipit-source-id: d0ea3d753eb588fe7b55f2345124427b4a5a58b5
This commit is contained in:
committed by
Facebook Github Bot
parent
cb374ffccd
commit
f2bc5d3fb2
@@ -9,10 +9,14 @@
|
|||||||
#import <FlipperKitLayoutPlugin/SKNamed.h>
|
#import <FlipperKitLayoutPlugin/SKNamed.h>
|
||||||
#import <FlipperKitLayoutPlugin/SKNodeDescriptor.h>
|
#import <FlipperKitLayoutPlugin/SKNodeDescriptor.h>
|
||||||
|
|
||||||
|
typedef id (^SKNodeDataChanged)(id value);
|
||||||
|
|
||||||
FB_LINK_REQUIRE_CATEGORY(CKComponent_Sonar)
|
FB_LINK_REQUIRE_CATEGORY(CKComponent_Sonar)
|
||||||
@interface CKComponent (Sonar)
|
@interface CKComponent (Sonar)
|
||||||
@property (assign, nonatomic) NSUInteger flipper_canBeReusedCounter;
|
@property (assign, nonatomic) NSUInteger flipper_canBeReusedCounter;
|
||||||
|
|
||||||
|
- (void)setMutableData:(id)data;
|
||||||
|
- (NSDictionary<NSString *, SKNodeDataChanged> *)sonar_getDataMutationsChanged;
|
||||||
- (NSArray<SKNamed<NSDictionary<NSString *, NSObject *> *> *> *)sonar_getData;
|
- (NSArray<SKNamed<NSDictionary<NSString *, NSObject *> *> *> *)sonar_getData;
|
||||||
- (NSDictionary<NSString *, SKNodeUpdateData> *)sonar_getDataMutations;
|
- (NSDictionary<NSString *, SKNodeUpdateData> *)sonar_getDataMutations;
|
||||||
- (NSString *)sonar_getName;
|
- (NSString *)sonar_getName;
|
||||||
|
|||||||
@@ -14,12 +14,17 @@
|
|||||||
#import <ComponentKit/CKComponentInternal.h>
|
#import <ComponentKit/CKComponentInternal.h>
|
||||||
#import <ComponentKit/CKComponentSubclass.h>
|
#import <ComponentKit/CKComponentSubclass.h>
|
||||||
#import <ComponentKit/CKComponentViewConfiguration.h>
|
#import <ComponentKit/CKComponentViewConfiguration.h>
|
||||||
|
#import <ComponentKit/CKComponentDebugController.h>
|
||||||
|
#import <ComponentKit/CKMutex.h>
|
||||||
#import <FlipperKitLayoutPlugin/SKNamed.h>
|
#import <FlipperKitLayoutPlugin/SKNamed.h>
|
||||||
#import <FlipperKitLayoutPlugin/SKObject.h>
|
#import <FlipperKitLayoutPlugin/SKObject.h>
|
||||||
|
#import <objc/runtime.h>
|
||||||
|
#import <objc/message.h>
|
||||||
|
|
||||||
#import "CKFlexboxComponent+Sonar.h"
|
#import "CKFlexboxComponent+Sonar.h"
|
||||||
#import "CKInsetComponent+Sonar.h"
|
#import "CKInsetComponent+Sonar.h"
|
||||||
#import "CKStatelessComponent+Sonar.h"
|
#import "CKStatelessComponent+Sonar.h"
|
||||||
|
#import "FKDataStorageForLiveEditing.h"
|
||||||
|
|
||||||
/** This protocol isn't actually adopted anywhere, it just lets us use the SEL below */
|
/** This protocol isn't actually adopted anywhere, it just lets us use the SEL below */
|
||||||
@protocol SonarKitLayoutComponentKitOverrideInformalProtocol
|
@protocol SonarKitLayoutComponentKitOverrideInformalProtocol
|
||||||
@@ -58,6 +63,30 @@ static NSDictionary<NSString *, NSObject *> *AccessibilityContextDict(CKComponen
|
|||||||
FB_LINKABLE(CKComponent_Sonar)
|
FB_LINKABLE(CKComponent_Sonar)
|
||||||
@implementation CKComponent (Sonar)
|
@implementation CKComponent (Sonar)
|
||||||
|
|
||||||
|
static FKDataStorageForLiveEditing *_dataStorage;
|
||||||
|
static NSMutableSet<NSString *> *_swizzledClasses;
|
||||||
|
static CK::StaticMutex _mutex = CK_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
+ (void)swizzleOriginalSEL:(SEL)originalSEL to:(SEL)replacementSEL
|
||||||
|
{
|
||||||
|
Class targetClass = self;
|
||||||
|
Method original = class_getInstanceMethod(targetClass, originalSEL);
|
||||||
|
Method replacement = class_getInstanceMethod(targetClass, replacementSEL);
|
||||||
|
BOOL didAddMethod =
|
||||||
|
class_addMethod(targetClass,
|
||||||
|
originalSEL,
|
||||||
|
method_getImplementation(replacement),
|
||||||
|
method_getTypeEncoding(replacement));
|
||||||
|
if (didAddMethod) {
|
||||||
|
class_replaceMethod(targetClass,
|
||||||
|
replacementSEL,
|
||||||
|
method_getImplementation(original),
|
||||||
|
method_getTypeEncoding(original));
|
||||||
|
} else {
|
||||||
|
method_exchangeImplementations(original, replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (NSString *)sonar_getName
|
- (NSString *)sonar_getName
|
||||||
{
|
{
|
||||||
if ([self respondsToSelector:@selector(sonar_componentNameOverride)]) {
|
if ([self respondsToSelector:@selector(sonar_componentNameOverride)]) {
|
||||||
@@ -171,7 +200,6 @@ FB_LINKABLE(CKComponent_Sonar)
|
|||||||
@"accessibilityEnabled": SKMutableObject(@(CK::Component::Accessibility::IsAccessibilityEnabled())),
|
@"accessibilityEnabled": SKMutableObject(@(CK::Component::Accessibility::IsAccessibilityEnabled())),
|
||||||
}]];
|
}]];
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([self respondsToSelector:@selector(sonar_additionalDataOverride)]) {
|
if ([self respondsToSelector:@selector(sonar_additionalDataOverride)]) {
|
||||||
[data addObjectsFromArray:[(id)self sonar_additionalDataOverride]];
|
[data addObjectsFromArray:[(id)self sonar_additionalDataOverride]];
|
||||||
}
|
}
|
||||||
@@ -179,12 +207,89 @@ FB_LINKABLE(CKComponent_Sonar)
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setMutableData:(id)value {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) setMutableDataFromStorage {
|
||||||
|
const auto globalID = self.treeNode.nodeIdentifier;
|
||||||
|
id data = [_dataStorage dataForTreeNodeIdentifier:globalID];
|
||||||
|
if (data) {
|
||||||
|
[self setMutableData:data];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (NSString *)swizzledMethodNameForRender {
|
||||||
|
return [NSString stringWithFormat:@"sonar_render_%@", NSStringFromClass(self)];
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (SEL)registerNewImplementation:(SEL)selector {
|
||||||
|
SEL resultSelector = sel_registerName([[self swizzledMethodNameForRender] UTF8String]);
|
||||||
|
Method method = class_getInstanceMethod(self, selector);
|
||||||
|
class_addMethod(self,
|
||||||
|
resultSelector,
|
||||||
|
method_getImplementation(method),
|
||||||
|
method_getTypeEncoding(method)
|
||||||
|
);
|
||||||
|
return resultSelector;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (CKComponent *)sonar_render:(id)state {
|
||||||
|
[self setMutableDataFromStorage];
|
||||||
|
SEL resultSelector = NSSelectorFromString([[self class] swizzledMethodNameForRender]);
|
||||||
|
return ((CKComponent *(*)(CKComponent *, SEL, id))objc_msgSend)(self, resultSelector, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (std::vector<CKComponent *>)sonar_renderChildren:(id)state {
|
||||||
|
[self setMutableDataFromStorage];
|
||||||
|
SEL resultSelector = NSSelectorFromString([[self class] swizzledMethodNameForRender]);
|
||||||
|
return ((std::vector<CKComponent *>(*)(CKComponent *, SEL, id))objc_msgSend_stret)(self, resultSelector, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDictionary<NSString *, SKNodeDataChanged> *)sonar_getDataMutationsChanged {
|
||||||
|
return @{};
|
||||||
|
}
|
||||||
|
|
||||||
- (NSDictionary<NSString *, SKNodeUpdateData> *)sonar_getDataMutations {
|
- (NSDictionary<NSString *, SKNodeUpdateData> *)sonar_getDataMutations {
|
||||||
return @{
|
static dispatch_once_t onceToken;
|
||||||
@"Accessibility.accessibilityEnabled": ^(NSNumber *value) {
|
dispatch_once(&onceToken, ^{
|
||||||
CK::Component::Accessibility::SetForceAccessibilityEnabled([value boolValue]);
|
_dataStorage = [[FKDataStorageForLiveEditing alloc] init];
|
||||||
}
|
_swizzledClasses = [[NSMutableSet alloc] init];
|
||||||
};
|
});
|
||||||
|
{
|
||||||
|
CK::StaticMutexLocker l(_mutex);
|
||||||
|
if (![_swizzledClasses containsObject:NSStringFromClass([self class])]) {
|
||||||
|
[_swizzledClasses addObject:NSStringFromClass([self class])];
|
||||||
|
if ([self respondsToSelector:@selector(render:)]) {
|
||||||
|
SEL replacement = [[self class] registerNewImplementation:@selector(sonar_render:)];
|
||||||
|
[[self class] swizzleOriginalSEL:@selector(render:) to:replacement];
|
||||||
|
} else if ([self respondsToSelector:@selector(renderChildren:)]) {
|
||||||
|
SEL replacement = [[self class] registerNewImplementation:@selector(sonar_renderChildren:)];
|
||||||
|
[[self class] swizzleOriginalSEL:@selector(renderChildren:) to:replacement];
|
||||||
|
} else {
|
||||||
|
CKAssert(NO, @"Only CKRenderLayoutComponent and CKRenderLayoutWithChildrenComponent children are now able to be live editable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NSDictionary<NSString *, SKNodeDataChanged> *dataChanged = [self sonar_getDataMutationsChanged];
|
||||||
|
NSMutableDictionary *dataMutation = [[NSMutableDictionary alloc] init];
|
||||||
|
[dataMutation addEntriesFromDictionary:@{
|
||||||
|
@"Accessibility.accessibilityEnabled": ^(NSNumber *value) {
|
||||||
|
CK::Component::Accessibility::SetForceAccessibilityEnabled([value boolValue]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const auto globalID = self.treeNode.nodeIdentifier;
|
||||||
|
for (NSString *key in dataChanged) {
|
||||||
|
const auto block = dataChanged[key];
|
||||||
|
[dataMutation setObject:^(id value) {
|
||||||
|
id data = block(value);
|
||||||
|
[_dataStorage setData:data forTreeNodeIdentifier:globalID];
|
||||||
|
[CKComponentDebugController reflowComponentsWithTreeNodeIdentifier:globalID];
|
||||||
|
}
|
||||||
|
forKey:key];
|
||||||
|
}
|
||||||
|
return dataMutation;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char const kCanBeReusedKey = ' ';
|
static char const kCanBeReusedKey = ' ';
|
||||||
|
|||||||
@@ -69,6 +69,11 @@
|
|||||||
return node.identifier;
|
return node.identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSString *)identifierForInvalidation:(SKComponentLayoutWrapper *)node
|
||||||
|
{
|
||||||
|
return [NSString stringWithFormat:@"%p", node.rootNode];
|
||||||
|
}
|
||||||
|
|
||||||
- (NSString *)nameForNode:(SKComponentLayoutWrapper *)node {
|
- (NSString *)nameForNode:(SKComponentLayoutWrapper *)node {
|
||||||
return [node.component sonar_getName];
|
return [node.component sonar_getName];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ static CKFlexboxComponentChild findFlexboxLayoutParams(CKComponent *parent, CKCo
|
|||||||
SKComponentLayoutWrapper *const wrapper =
|
SKComponentLayoutWrapper *const wrapper =
|
||||||
[[SKComponentLayoutWrapper alloc] initWithLayout:layout
|
[[SKComponentLayoutWrapper alloc] initWithLayout:layout
|
||||||
position:CGPointMake(0, 0)
|
position:CGPointMake(0, 0)
|
||||||
parentKey:[NSString stringWithFormat: @"%p.", layout.component]
|
parentKey:[NSString stringWithFormat: @"%d.", layout.component.treeNode.nodeIdentifier]
|
||||||
reuseWrapper:reuseWrapper
|
reuseWrapper:reuseWrapper
|
||||||
rootNode: root];
|
rootNode: root];
|
||||||
// Cache the result.
|
// Cache the result.
|
||||||
@@ -97,7 +97,7 @@ static CKFlexboxComponentChild findFlexboxLayoutParams(CKComponent *parent, CKCo
|
|||||||
position:child.position
|
position:child.position
|
||||||
parentKey:[_identifier stringByAppendingFormat:@"[%d].", index++]
|
parentKey:[_identifier stringByAppendingFormat:@"[%d].", index++]
|
||||||
reuseWrapper:reuseWrapper
|
reuseWrapper:reuseWrapper
|
||||||
rootNode: nil
|
rootNode:node
|
||||||
];
|
];
|
||||||
childWrapper->_isFlexboxChild = [_component isKindOfClass:[CKFlexboxComponent class]];
|
childWrapper->_isFlexboxChild = [_component isKindOfClass:[CKFlexboxComponent class]];
|
||||||
childWrapper->_flexboxChild = findFlexboxLayoutParams(_component, child.layout.component);
|
childWrapper->_flexboxChild = findFlexboxLayoutParams(_component, child.layout.component);
|
||||||
|
|||||||
@@ -213,11 +213,13 @@
|
|||||||
NSString *dotJoinedPath = [path componentsJoinedByString: @"."];
|
NSString *dotJoinedPath = [path componentsJoinedByString: @"."];
|
||||||
SKNodeUpdateData updateDataForPath = [[descriptor dataMutationsForNode: node] objectForKey: dotJoinedPath];
|
SKNodeUpdateData updateDataForPath = [[descriptor dataMutationsForNode: node] objectForKey: dotJoinedPath];
|
||||||
if (updateDataForPath != nil) {
|
if (updateDataForPath != nil) {
|
||||||
|
const auto identifierForInvalidation = [descriptor identifierForInvalidation:node];
|
||||||
|
id nodeForInvalidation = [_trackedObjects objectForKey:identifierForInvalidation];
|
||||||
|
SKNodeDescriptor *descriptorForInvalidation = [_descriptorMapper descriptorForClass:[nodeForInvalidation class]];
|
||||||
|
NSMutableArray *children = [self getChildrenForNode:nodeForInvalidation withDescriptor:descriptorForInvalidation];
|
||||||
updateDataForPath(value);
|
updateDataForPath(value);
|
||||||
|
|
||||||
NSMutableArray *children = [self getChildrenForNode:node withDescriptor:descriptor];
|
|
||||||
[connection send: @"invalidate" withParams: @{
|
[connection send: @"invalidate" withParams: @{
|
||||||
@"nodes": @[@{@"id": [descriptor identifierForNode: node], @"children": children}]
|
@"nodes": @[@{@"id": [descriptorForInvalidation identifierForNode: nodeForInvalidation], @"children": children}]
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -446,9 +448,7 @@
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! [_trackedObjects objectForKey: objectIdentifier]) {
|
[_trackedObjects setObject:object forKey:objectIdentifier];
|
||||||
[_trackedObjects setObject:object forKey:objectIdentifier];
|
|
||||||
}
|
|
||||||
|
|
||||||
return objectIdentifier;
|
return objectIdentifier;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ typedef void (^SKNodeUpdateData)(id value);
|
|||||||
*/
|
*/
|
||||||
- (NSString *)identifierForNode:(T)node;
|
- (NSString *)identifierForNode:(T)node;
|
||||||
|
|
||||||
|
/**
|
||||||
|
An ID which is equal between reflowing components is needed to get the identifier of root
|
||||||
|
node of a tree which need to be invalidated on FlipperKitLayoutPlugin side.
|
||||||
|
*/
|
||||||
|
- (NSString *)identifierForInvalidation:(T)node;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The name used to identify this node in the Sonar desktop application. This is what
|
The name used to identify this node in the Sonar desktop application. This is what
|
||||||
will be visible in the hierarchy.
|
will be visible in the hierarchy.
|
||||||
|
|||||||
@@ -33,6 +33,11 @@
|
|||||||
@throw [NSString stringWithFormat:@"need to implement %@", NSStringFromSelector(_cmd)];
|
@throw [NSString stringWithFormat:@"need to implement %@", NSStringFromSelector(_cmd)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSString *)identifierForInvalidation:(id)node
|
||||||
|
{
|
||||||
|
return [self identifierForNode:node];
|
||||||
|
}
|
||||||
|
|
||||||
- (NSString *)nameForNode:(id)node {
|
- (NSString *)nameForNode:(id)node {
|
||||||
return NSStringFromClass([node class]);
|
return NSStringFromClass([node class]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user