@sapui5/sap.fe.test 1.101.1 → 1.102.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/sap/fe/test/.library +1 -1
- package/src/sap/fe/test/CollaborationClient.js +77 -0
- package/src/sap/fe/test/CollaborationClient.ts +105 -0
- package/src/sap/fe/test/FeMocks.js +1 -1
- package/src/sap/fe/test/FeMocks.ts +6 -2
- package/src/sap/fe/test/JestTemplatingHelper.js +19 -1
- package/src/sap/fe/test/JestTemplatingHelper.ts +13 -0
- package/src/sap/fe/test/ListReport.js +7 -15
- package/src/sap/fe/test/LocationUtil.js +12 -16
- package/src/sap/fe/test/ObjectPage.js +38 -6
- package/src/sap/fe/test/Shell.js +94 -34
- package/src/sap/fe/test/TemplatePage.js +6 -0
- package/src/sap/fe/test/UI5MockHelper.js +98 -18
- package/src/sap/fe/test/UI5MockHelper.ts +79 -14
- package/src/sap/fe/test/api/CollaborationAPI.js +215 -0
- package/src/sap/fe/test/api/CollaborationAPI.ts +240 -0
- package/src/sap/fe/test/api/HeaderAssertions.js +76 -41
- package/src/sap/fe/test/api/TableAPI.js +30 -34
- package/src/sap/fe/test/api/TableActions.js +1 -1
- package/src/sap/fe/test/api/TableAssertions.js +51 -46
- package/src/sap/fe/test/builder/MacroFieldBuilder.js +161 -156
- package/src/sap/fe/test/builder/MdcTableBuilder.js +89 -12
- package/src/sap/fe/test/library.js +1 -1
|
@@ -6,13 +6,17 @@ import Control from "sap/ui/core/Control";
|
|
|
6
6
|
import UI5Element from "sap/ui/core/Element";
|
|
7
7
|
import Controller from "sap/ui/core/mvc/Controller";
|
|
8
8
|
import View from "sap/ui/core/mvc/View";
|
|
9
|
+
import CompositeBinding from "sap/ui/model/CompositeBinding";
|
|
10
|
+
import JSONModel from "sap/ui/model/json/JSONModel";
|
|
9
11
|
import Context from "sap/ui/model/odata/v4/Context";
|
|
10
|
-
import ODataListBinding from "sap/ui/model/odata/v4/ODataListBinding";
|
|
11
12
|
import ODataContextBinding from "sap/ui/model/odata/v4/ODataContextBinding";
|
|
13
|
+
import ODataListBinding from "sap/ui/model/odata/v4/ODataListBinding";
|
|
12
14
|
import ODataMetaModel from "sap/ui/model/odata/v4/ODataMetaModel";
|
|
13
15
|
import ODataModel from "sap/ui/model/odata/v4/ODataModel";
|
|
16
|
+
import ODataPropertyBinding from "sap/ui/model/odata/v4/ODataPropertyBinding";
|
|
14
17
|
|
|
15
18
|
export class MockContext implements Partial<Context> {
|
|
19
|
+
private isKeptAlive = false;
|
|
16
20
|
public constructor(private oValues?: any, private oBinding?: any) {}
|
|
17
21
|
|
|
18
22
|
public isA(sClassName: string): boolean {
|
|
@@ -52,9 +56,14 @@ export class MockContext implements Partial<Context> {
|
|
|
52
56
|
return this.oBinding;
|
|
53
57
|
});
|
|
54
58
|
public getModel = jest.fn(() => {
|
|
55
|
-
return
|
|
59
|
+
return this.oBinding?.getModel() as any as ODataModel;
|
|
56
60
|
});
|
|
57
61
|
public isTransient = jest.fn();
|
|
62
|
+
public setKeepAlive = jest.fn((bool: boolean) => {
|
|
63
|
+
this.isKeptAlive = bool;
|
|
64
|
+
});
|
|
65
|
+
public isKeepAlive = jest.fn(() => this.isKeptAlive);
|
|
66
|
+
public requestSideEffects = jest.fn();
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
export class MockUI5Element implements Partial<UI5Element> {
|
|
@@ -70,12 +79,13 @@ export class MockUI5Element implements Partial<UI5Element> {
|
|
|
70
79
|
|
|
71
80
|
export class MockControl extends MockUI5Element implements Partial<Control> {
|
|
72
81
|
public getBindingContext: jest.Mock<any, any> = jest.fn();
|
|
82
|
+
public getModel: jest.Mock<any, any> = jest.fn();
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
export class MockEvent implements Partial<Event> {
|
|
76
86
|
public constructor(public params: { [key: string]: any } = {}, public source: MockUI5Element = new MockUI5Element()) {}
|
|
77
87
|
|
|
78
|
-
public getParameter = jest.fn(name => this.params[name]);
|
|
88
|
+
public getParameter = jest.fn((name) => this.params[name]);
|
|
79
89
|
|
|
80
90
|
public getSource = jest.fn(() => this.source as any);
|
|
81
91
|
}
|
|
@@ -89,7 +99,7 @@ export class MockListBinding implements Partial<ODataListBinding> {
|
|
|
89
99
|
public constructor(aContexts?: any[], private mockModel?: MockModel) {
|
|
90
100
|
aContexts = aContexts || [];
|
|
91
101
|
|
|
92
|
-
this.aMockContexts = aContexts.map(context => {
|
|
102
|
+
this.aMockContexts = aContexts.map((context) => {
|
|
93
103
|
return new MockContext(context, this);
|
|
94
104
|
});
|
|
95
105
|
}
|
|
@@ -109,19 +119,73 @@ export class MockListBinding implements Partial<ODataListBinding> {
|
|
|
109
119
|
|
|
110
120
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
111
121
|
public requestContexts = jest.fn((...args) => {
|
|
112
|
-
return Promise.resolve(
|
|
122
|
+
return Promise.resolve(this.aMockContexts as any as Context[]);
|
|
113
123
|
});
|
|
114
124
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
115
125
|
public getCurrentContexts = jest.fn((...args) => {
|
|
116
|
-
return
|
|
126
|
+
return this.aMockContexts as any as Context[];
|
|
127
|
+
});
|
|
128
|
+
public getModel = jest.fn(() => {
|
|
129
|
+
return this.mockModel as any as ODataModel;
|
|
117
130
|
});
|
|
131
|
+
public getUpdateGroupId = jest.fn(() => "auto");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export class MockPropertyBinding implements Partial<ODataPropertyBinding> {
|
|
135
|
+
private mockModel!: JSONModel;
|
|
136
|
+
private mockPath!: string;
|
|
137
|
+
|
|
138
|
+
public constructor(mockModel: JSONModel, mockPath: string) {
|
|
139
|
+
this.setModel(mockModel);
|
|
140
|
+
this.setPath(mockPath);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public isA(sClassName: string): boolean {
|
|
144
|
+
return sClassName === "sap/ui/model/odata/v4/ODataPropertyBinding";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public setModel(oModel: JSONModel) {
|
|
148
|
+
this.mockModel = oModel;
|
|
149
|
+
}
|
|
118
150
|
public getModel = jest.fn(() => {
|
|
119
|
-
return
|
|
151
|
+
return this.mockModel as any as ODataModel;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
public setPath(path: string | undefined) {
|
|
155
|
+
this.mockPath = path ? path : "";
|
|
156
|
+
}
|
|
157
|
+
public getPath = jest.fn(() => {
|
|
158
|
+
return this.mockPath;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
public getValue = jest.fn(() => {
|
|
162
|
+
return this?.mockModel?.getProperty("/" + this.mockPath);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export class MockCompositeBinding implements Partial<CompositeBinding> {
|
|
167
|
+
private aBindings: MockPropertyBinding[];
|
|
168
|
+
|
|
169
|
+
public constructor(aBindings: MockPropertyBinding[]) {
|
|
170
|
+
this.aBindings = aBindings;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public isA(sClassName: string): boolean {
|
|
174
|
+
return sClassName === "sap.ui.model.CompositeBinding";
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public getBindings = jest.fn(() => {
|
|
178
|
+
return this.aBindings;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
public getValue = jest.fn(() => {
|
|
182
|
+
return this.aBindings.map((binding) => binding.getValue());
|
|
120
183
|
});
|
|
121
184
|
}
|
|
122
185
|
|
|
123
186
|
export class MockContextBinding implements Partial<ODataContextBinding> {
|
|
124
187
|
private oMockContext: MockContext;
|
|
188
|
+
private isKeptAlive: boolean = false;
|
|
125
189
|
|
|
126
190
|
public constructor(oContext?: any, private mockModel?: MockModel) {
|
|
127
191
|
this.oMockContext = new MockContext(oContext || {}, this);
|
|
@@ -139,11 +203,11 @@ export class MockContextBinding implements Partial<ODataContextBinding> {
|
|
|
139
203
|
|
|
140
204
|
// Mocked API
|
|
141
205
|
public getBoundContext = jest.fn(() => {
|
|
142
|
-
return
|
|
206
|
+
return this.oMockContext as any as Context;
|
|
143
207
|
});
|
|
144
208
|
public attachEventOnce = jest.fn();
|
|
145
209
|
public getModel = jest.fn(() => {
|
|
146
|
-
return
|
|
210
|
+
return this.mockModel as any as ODataModel;
|
|
147
211
|
});
|
|
148
212
|
}
|
|
149
213
|
|
|
@@ -161,14 +225,14 @@ export class MockMetaModel implements Partial<ODataMetaModel> {
|
|
|
161
225
|
// Mocked API
|
|
162
226
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
163
227
|
public getMetaContext = jest.fn((sPath: string) => {
|
|
164
|
-
return
|
|
228
|
+
return new MockContext({ $path: sPath }) as any as Context;
|
|
165
229
|
});
|
|
166
230
|
public getObject = jest.fn((sPath: string) => {
|
|
167
231
|
return this.oMetaContext.getProperty(sPath);
|
|
168
232
|
});
|
|
169
233
|
public requestObject = jest.fn();
|
|
170
234
|
public createBindingContext = jest.fn((sPath: string) => {
|
|
171
|
-
return
|
|
235
|
+
return new MockContext({ $path: sPath }) as any as Context;
|
|
172
236
|
});
|
|
173
237
|
public getMetaPath = jest.fn((sPath: string) => {
|
|
174
238
|
return sPath;
|
|
@@ -207,19 +271,20 @@ export class MockModel implements Partial<ODataModel> {
|
|
|
207
271
|
// Mocked API
|
|
208
272
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
209
273
|
public bindList = jest.fn((...args) => {
|
|
210
|
-
return
|
|
274
|
+
return this.mockListBinding as any as ODataListBinding;
|
|
211
275
|
});
|
|
212
276
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
213
277
|
public bindContext = jest.fn((...args) => {
|
|
214
|
-
return
|
|
278
|
+
return this.mockContextBinding as any as ODataContextBinding;
|
|
215
279
|
});
|
|
216
280
|
public getMetaModel = jest.fn(() => {
|
|
217
|
-
return
|
|
281
|
+
return this.oMetaModel as any as ODataMetaModel;
|
|
218
282
|
});
|
|
219
283
|
|
|
220
284
|
public getProperty = jest.fn();
|
|
221
285
|
public setProperty = jest.fn();
|
|
222
286
|
public requestObject = jest.fn();
|
|
287
|
+
public getKeepAliveContext = jest.fn();
|
|
223
288
|
}
|
|
224
289
|
|
|
225
290
|
export class MockView implements Partial<View> {
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* SAP UI development toolkit for HTML5 (SAPUI5)
|
|
3
|
+
* (c) Copyright 2009-2021 SAP SE. All rights reserved
|
|
4
|
+
*/
|
|
5
|
+
sap.ui.define(["sap/base/Log", "sap/fe/core/controllerextensions/collaboration/ActivityBase", "sap/fe/core/controllerextensions/collaboration/CollaborationCommon", "sap/fe/core/controllerextensions/editFlow/draft", "sap/ui/model/json/JSONModel"], function (Log, ActivityBase, CollaborationCommon, draft, JSONModel) {
|
|
6
|
+
"use strict";
|
|
7
|
+
|
|
8
|
+
var Activity = CollaborationCommon.Activity;
|
|
9
|
+
var initializeCollaboration = ActivityBase.initializeCollaboration;
|
|
10
|
+
var endCollaboration = ActivityBase.endCollaboration;
|
|
11
|
+
var broadcastCollaborationMessage = ActivityBase.broadcastCollaborationMessage;
|
|
12
|
+
|
|
13
|
+
var CollaborationAPI = {
|
|
14
|
+
_lastReceivedMessage: undefined,
|
|
15
|
+
_rootPath: "",
|
|
16
|
+
_oModel: undefined,
|
|
17
|
+
_lockedPropertyPath: "",
|
|
18
|
+
_internalModel: undefined,
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Open an existing collaborative draft with a new user, and creates a 'ghost client' for this user.
|
|
22
|
+
*
|
|
23
|
+
* @param oContext The context of the collaborative draft
|
|
24
|
+
* @param userID The ID of the user
|
|
25
|
+
* @param userName The name of the user
|
|
26
|
+
*/
|
|
27
|
+
enterDraft: function (oContext, userID, userName) {
|
|
28
|
+
var webSocketBaseURL = oContext.getModel().getMetaModel().getObject("/@com.sap.vocabularies.Common.v1.WebSocketBaseURL");
|
|
29
|
+
|
|
30
|
+
if (!webSocketBaseURL) {
|
|
31
|
+
Log.error("Cannot find WebSocketBaseURL annotation");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var sDraftUUID = oContext.getProperty("DraftAdministrativeData/DraftUUID");
|
|
36
|
+
this._internalModel = new JSONModel({});
|
|
37
|
+
initializeCollaboration({
|
|
38
|
+
id: userID,
|
|
39
|
+
name: userName,
|
|
40
|
+
initialName: userName
|
|
41
|
+
}, webSocketBaseURL, sDraftUUID, this._internalModel, this._onMessageReceived.bind(this), true);
|
|
42
|
+
this._rootPath = oContext.getPath();
|
|
43
|
+
this._oModel = oContext.getModel();
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Checks if the ghost client has revieved a given message.
|
|
48
|
+
*
|
|
49
|
+
* @param message The message content to be looked for
|
|
50
|
+
* @returns True if the last recieved message matches the content
|
|
51
|
+
*/
|
|
52
|
+
checkReceived: function (message) {
|
|
53
|
+
if (!this._lastReceivedMessage) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var found = (!message.userID || message.userID === this._lastReceivedMessage.userID) && (!message.userAction || message.userAction === this._lastReceivedMessage.userAction) && (!message.clientContent || message.clientContent === this._lastReceivedMessage.clientContent);
|
|
58
|
+
this._lastReceivedMessage = undefined; // reset history to avoid finding the same message twice
|
|
59
|
+
|
|
60
|
+
return found;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Closes the ghost client and removes the user from the collaborative draft.
|
|
65
|
+
*/
|
|
66
|
+
leaveDraft: function () {
|
|
67
|
+
if (this._internalModel) {
|
|
68
|
+
endCollaboration(this._internalModel);
|
|
69
|
+
|
|
70
|
+
this._internalModel.destroy();
|
|
71
|
+
|
|
72
|
+
this._internalModel = undefined;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Simulates that the user starts typing in an input (live change).
|
|
78
|
+
*
|
|
79
|
+
* @param sPropertyPath The path of the property being modified
|
|
80
|
+
*/
|
|
81
|
+
startLiveChange: function (sPropertyPath) {
|
|
82
|
+
if (this._internalModel) {
|
|
83
|
+
if (this._lockedPropertyPath) {
|
|
84
|
+
// Unlock previous property path
|
|
85
|
+
this.undoChange();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this._lockedPropertyPath = sPropertyPath;
|
|
89
|
+
broadcastCollaborationMessage(Activity.LiveChange, this._rootPath + "/" + sPropertyPath, this._internalModel);
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Simulates that the user has modified a property.
|
|
95
|
+
*
|
|
96
|
+
* @param sPropertyPath The path of the property being modified
|
|
97
|
+
* @param value The new value of the property being modified
|
|
98
|
+
*/
|
|
99
|
+
updatePropertyValue: function (sPropertyPath, value) {
|
|
100
|
+
var _this = this;
|
|
101
|
+
|
|
102
|
+
if (this._internalModel) {
|
|
103
|
+
if (this._lockedPropertyPath !== sPropertyPath) {
|
|
104
|
+
this.startLiveChange(sPropertyPath);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
var oContextBinding = this._oModel.bindContext(this._rootPath, undefined, {
|
|
108
|
+
$$patchWithoutSideEffects: true,
|
|
109
|
+
$$groupId: "$auto",
|
|
110
|
+
$$updateGroupId: "$auto"
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
var oPropertyBinding = this._oModel.bindProperty(sPropertyPath, oContextBinding.getBoundContext());
|
|
114
|
+
|
|
115
|
+
oPropertyBinding.requestValue().then(function () {
|
|
116
|
+
oPropertyBinding.setValue(value);
|
|
117
|
+
oContextBinding.attachEventOnce("patchCompleted", function () {
|
|
118
|
+
broadcastCollaborationMessage(Activity.Change, _this._rootPath + "/" + sPropertyPath, _this._internalModel);
|
|
119
|
+
_this._lockedPropertyPath = "";
|
|
120
|
+
});
|
|
121
|
+
}).catch(function (err) {
|
|
122
|
+
Log.error(err);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Simulates that the user did an 'undo' (to be called after startLiveChange).
|
|
129
|
+
*/
|
|
130
|
+
undoChange: function () {
|
|
131
|
+
if (this._lockedPropertyPath) {
|
|
132
|
+
broadcastCollaborationMessage(Activity.Undo, this._rootPath + "/" + this._lockedPropertyPath, this._internalModel);
|
|
133
|
+
this._lockedPropertyPath = "";
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Simulates that the user has discarded the draft.
|
|
139
|
+
*/
|
|
140
|
+
discardDraft: function () {
|
|
141
|
+
var _this2 = this;
|
|
142
|
+
|
|
143
|
+
if (this._internalModel) {
|
|
144
|
+
var draftContext = this._getDraftContext();
|
|
145
|
+
|
|
146
|
+
draftContext.requestProperty("IsActiveEntity").then(function () {
|
|
147
|
+
draft.deleteDraft(draftContext);
|
|
148
|
+
}).then(function () {
|
|
149
|
+
broadcastCollaborationMessage(Activity.Discard, _this2._rootPath.replace("IsActiveEntity=false", "IsActiveEntity=true"), _this2._internalModel);
|
|
150
|
+
|
|
151
|
+
_this2._internalModel.destroy();
|
|
152
|
+
|
|
153
|
+
_this2._internalModel = undefined;
|
|
154
|
+
}).catch(function (err) {
|
|
155
|
+
Log.error(err);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Simulates that the user has deleted the draft.
|
|
162
|
+
*/
|
|
163
|
+
deleteDraft: function () {
|
|
164
|
+
var _this3 = this;
|
|
165
|
+
|
|
166
|
+
if (this._internalModel) {
|
|
167
|
+
var draftContext = this._getDraftContext();
|
|
168
|
+
|
|
169
|
+
var activeContext;
|
|
170
|
+
draftContext.requestProperty("IsActiveEntity").then(function () {
|
|
171
|
+
return draftContext.getModel().bindContext(_this3._rootPath + "/SiblingEntity").getBoundContext();
|
|
172
|
+
}).then(function (context) {
|
|
173
|
+
activeContext = context;
|
|
174
|
+
return context.requestCanonicalPath();
|
|
175
|
+
}).then(function () {
|
|
176
|
+
return draft.deleteDraft(draftContext);
|
|
177
|
+
}).then(function () {
|
|
178
|
+
activeContext.delete();
|
|
179
|
+
broadcastCollaborationMessage(Activity.Delete, _this3._rootPath, _this3._internalModel);
|
|
180
|
+
|
|
181
|
+
_this3._internalModel.destroy();
|
|
182
|
+
|
|
183
|
+
_this3._internalModel = undefined;
|
|
184
|
+
}).catch(function (err) {
|
|
185
|
+
Log.error(err);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
// /////////////////////////////
|
|
190
|
+
// Private methods
|
|
191
|
+
_getDraftContext: function () {
|
|
192
|
+
return this._oModel.bindContext(this._rootPath, undefined, {
|
|
193
|
+
$$patchWithoutSideEffects: true,
|
|
194
|
+
$$groupId: "$auto",
|
|
195
|
+
$$updateGroupId: "$auto"
|
|
196
|
+
}).getBoundContext();
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Callback of the ghost client when receiving a message on the web socket.
|
|
201
|
+
*
|
|
202
|
+
* @param oMessage The message
|
|
203
|
+
*/
|
|
204
|
+
_onMessageReceived: function (oMessage) {
|
|
205
|
+
oMessage.userAction = oMessage.userAction || oMessage.clientAction;
|
|
206
|
+
this._lastReceivedMessage = oMessage;
|
|
207
|
+
|
|
208
|
+
if (oMessage.userAction === Activity.Join) {
|
|
209
|
+
broadcastCollaborationMessage(Activity.JoinEcho, this._lockedPropertyPath ? this._rootPath + "/" + this._lockedPropertyPath : undefined, this._internalModel);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
return CollaborationAPI;
|
|
214
|
+
}, false);
|
|
215
|
+
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIkNvbGxhYm9yYXRpb25BUEkudHMiXSwibmFtZXMiOlsiQ29sbGFib3JhdGlvbkFQSSIsIl9sYXN0UmVjZWl2ZWRNZXNzYWdlIiwidW5kZWZpbmVkIiwiX3Jvb3RQYXRoIiwiX29Nb2RlbCIsIl9sb2NrZWRQcm9wZXJ0eVBhdGgiLCJfaW50ZXJuYWxNb2RlbCIsImVudGVyRHJhZnQiLCJvQ29udGV4dCIsInVzZXJJRCIsInVzZXJOYW1lIiwid2ViU29ja2V0QmFzZVVSTCIsImdldE1vZGVsIiwiZ2V0TWV0YU1vZGVsIiwiZ2V0T2JqZWN0IiwiTG9nIiwiZXJyb3IiLCJzRHJhZnRVVUlEIiwiZ2V0UHJvcGVydHkiLCJKU09OTW9kZWwiLCJpbml0aWFsaXplQ29sbGFib3JhdGlvbiIsImlkIiwibmFtZSIsImluaXRpYWxOYW1lIiwiX29uTWVzc2FnZVJlY2VpdmVkIiwiYmluZCIsImdldFBhdGgiLCJjaGVja1JlY2VpdmVkIiwibWVzc2FnZSIsImZvdW5kIiwidXNlckFjdGlvbiIsImNsaWVudENvbnRlbnQiLCJsZWF2ZURyYWZ0IiwiZW5kQ29sbGFib3JhdGlvbiIsImRlc3Ryb3kiLCJzdGFydExpdmVDaGFuZ2UiLCJzUHJvcGVydHlQYXRoIiwidW5kb0NoYW5nZSIsImJyb2FkY2FzdENvbGxhYm9yYXRpb25NZXNzYWdlIiwiQWN0aXZpdHkiLCJMaXZlQ2hhbmdlIiwidXBkYXRlUHJvcGVydHlWYWx1ZSIsInZhbHVlIiwib0NvbnRleHRCaW5kaW5nIiwiYmluZENvbnRleHQiLCIkJHBhdGNoV2l0aG91dFNpZGVFZmZlY3RzIiwiJCRncm91cElkIiwiJCR1cGRhdGVHcm91cElkIiwib1Byb3BlcnR5QmluZGluZyIsImJpbmRQcm9wZXJ0eSIsImdldEJvdW5kQ29udGV4dCIsInJlcXVlc3RWYWx1ZSIsInRoZW4iLCJzZXRWYWx1ZSIsImF0dGFjaEV2ZW50T25jZSIsIkNoYW5nZSIsImNhdGNoIiwiZXJyIiwiVW5kbyIsImRpc2NhcmREcmFmdCIsImRyYWZ0Q29udGV4dCIsIl9nZXREcmFmdENvbnRleHQiLCJyZXF1ZXN0UHJvcGVydHkiLCJkcmFmdCIsImRlbGV0ZURyYWZ0IiwiRGlzY2FyZCIsInJlcGxhY2UiLCJhY3RpdmVDb250ZXh0IiwiY29udGV4dCIsInJlcXVlc3RDYW5vbmljYWxQYXRoIiwiZGVsZXRlIiwiRGVsZXRlIiwib01lc3NhZ2UiLCJjbGllbnRBY3Rpb24iLCJKb2luIiwiSm9pbkVjaG8iXSwibWFwcGluZ3MiOiI7QUFBQTtBQUNBO0FBQ0E7Ozs7Ozs7OztBQVVBLE1BQU1BLGdCQUFnQixHQUFHO0FBQ3hCQyxJQUFBQSxvQkFBb0IsRUFBRUMsU0FERTtBQUV4QkMsSUFBQUEsU0FBUyxFQUFFLEVBRmE7QUFHeEJDLElBQUFBLE9BQU8sRUFBRUYsU0FIZTtBQUl4QkcsSUFBQUEsbUJBQW1CLEVBQUUsRUFKRztBQUt4QkMsSUFBQUEsY0FBYyxFQUFFSixTQUxROztBQU94QjtBQUNEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNDSyxJQUFBQSxVQUFVLEVBQUUsVUFBVUMsUUFBVixFQUE2QkMsTUFBN0IsRUFBNkNDLFFBQTdDLEVBQStEO0FBQzFFLFVBQU1DLGdCQUF3QixHQUFHSCxRQUFRLENBQUNJLFFBQVQsR0FBb0JDLFlBQXBCLEdBQW1DQyxTQUFuQyxDQUE2QyxtREFBN0MsQ0FBakM7O0FBRUEsVUFBSSxDQUFDSCxnQkFBTCxFQUF1QjtBQUN0QkksUUFBQUEsR0FBRyxDQUFDQyxLQUFKLENBQVUseUNBQVY7QUFDQTtBQUNBOztBQUVELFVBQU1DLFVBQWtCLEdBQUdULFFBQVEsQ0FBQ1UsV0FBVCxDQUFxQixtQ0FBckIsQ0FBM0I7QUFDQSxXQUFLWixjQUFMLEdBQXNCLElBQUlhLFNBQUosQ0FBYyxFQUFkLENBQXRCO0FBRUFDLE1BQUFBLHVCQUF1QixDQUN0QjtBQUNDQyxRQUFBQSxFQUFFLEVBQUVaLE1BREw7QUFFQ2EsUUFBQUEsSUFBSSxFQUFFWixRQUZQO0FBR0NhLFFBQUFBLFdBQVcsRUFBRWI7QUFIZCxPQURzQixFQU10QkMs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b2cgZnJvbSBcInNhcC9iYXNlL0xvZ1wiO1xuaW1wb3J0IHtcblx0YnJvYWRjYXN0Q29sbGFib3JhdGlvbk1lc3NhZ2UsXG5cdGVuZENvbGxhYm9yYXRpb24sXG5cdGluaXRpYWxpemVDb2xsYWJvcmF0aW9uXG59IGZyb20gXCJzYXAvZmUvY29yZS9jb250cm9sbGVyZXh0ZW5zaW9ucy9jb2xsYWJvcmF0aW9uL0FjdGl2aXR5QmFzZVwiO1xuaW1wb3J0IHsgQWN0aXZpdHksIE1lc3NhZ2UgfSBmcm9tIFwic2FwL2ZlL2NvcmUvY29udHJvbGxlcmV4dGVuc2lvbnMvY29sbGFib3JhdGlvbi9Db2xsYWJvcmF0aW9uQ29tbW9uXCI7XG5pbXBvcnQgZHJhZnQgZnJvbSBcInNhcC9mZS9jb3JlL2NvbnRyb2xsZXJleHRlbnNpb25zL2VkaXRGbG93L2RyYWZ0XCI7XG5pbXBvcnQgQ29udGV4dCBmcm9tIFwic2FwL3VpL21vZGVsL0NvbnRleHRcIjtcbmltcG9ydCBKU09OTW9kZWwgZnJvbSBcInNhcC91aS9tb2RlbC9qc29uL0pTT05Nb2RlbFwiO1xuaW1wb3J0IE9EYXRhTW9kZWwgZnJvbSBcInNhcC91aS9tb2RlbC9vZGF0YS92NC9PRGF0YU1vZGVsXCI7XG5cbmNvbnN0IENvbGxhYm9yYXRpb25BUEkgPSB7XG5cdF9sYXN0UmVjZWl2ZWRNZXNzYWdlOiB1bmRlZmluZWQgYXMgTWVzc2FnZSB8IHVuZGVmaW5lZCxcblx0X3Jvb3RQYXRoOiBcIlwiLFxuXHRfb01vZGVsOiB1bmRlZmluZWQgYXMgT0RhdGFNb2RlbCB8IHVuZGVmaW5lZCxcblx0X2xvY2tlZFByb3BlcnR5UGF0aDogXCJcIixcblx0X2ludGVybmFsTW9kZWw6IHVuZGVmaW5lZCBhcyBKU09OTW9kZWwgfCB1bmRlZmluZWQsXG5cblx0LyoqXG5cdCAqIE9wZW4gYW4gZXhpc3RpbmcgY29sbGFib3JhdGl2ZSBkcmFmdCB3aXRoIGEgbmV3IHVzZXIsIGFuZCBjcmVhdGVzIGEgJ2dob3N0IGNsaWVudCcgZm9yIHRoaXMgdXNlci5cblx0ICpcblx0ICogQHBhcmFtIG9Db250ZXh0IFRoZSBjb250ZXh0IG9mIHRoZSBjb2xsYWJvcmF0aXZlIGRyYWZ0XG5cdCAqIEBwYXJhbSB1c2VySUQgVGhlIElEIG9mIHRoZSB1c2VyXG5cdCAqIEBwYXJhbSB1c2VyTmFtZSBUaGUgbmFtZSBvZiB0aGUgdXNlclxuXHQgKi9cblx0ZW50ZXJEcmFmdDogZnVuY3Rpb24gKG9Db250ZXh0OiBDb250ZXh0LCB1c2VySUQ6IHN0cmluZywgdXNlck5hbWU6IHN0cmluZykge1xuXHRcdGNvbnN0IHdlYlNvY2tldEJhc2VVUkw6IHN0cmluZyA9IG9Db250ZXh0LmdldE1vZGVsKCkuZ2V0TWV0YU1vZGVsKCkuZ2V0T2JqZWN0KFwiL0Bjb20uc2FwLnZvY2FidWxhcmllcy5Db21tb24udjEuV2ViU29ja2V0QmFzZVVSTFwiKTtcblxuXHRcdGlmICghd2ViU29ja2V0QmFzZVVSTCkge1xuXHRcdFx0TG9nLmVycm9yKFwiQ2Fubm90IGZpbmQgV2ViU29ja2V0QmFzZVVSTCBhbm5vdGF0aW9uXCIpO1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblxuXHRcdGNvbnN0IHNEcmFmdFVVSUQ6IHN0cmluZyA9IG9Db250ZXh0LmdldFByb3BlcnR5KFwiRHJhZnRBZG1pbmlzdHJhdGl2ZURhdGEvRHJhZnRVVUlEXCIpO1xuXHRcdHRoaXMuX2ludGVybmFsTW9kZWwgPSBuZXcgSlNPTk1vZGVsKHt9KTtcblxuXHRcdGluaXRpYWxpemVDb2xsYWJvcmF0aW9uKFxuXHRcdFx0e1xuXHRcdFx0XHRpZDogdXNlcklELFxuXHRcdFx0XHRuYW1lOiB1c2VyTmFtZSxcblx0XHRcdFx0aW5pdGlhbE5hbWU6IHVzZXJOYW1lXG5cdFx0XHR9LFxuXHRcdFx0d2ViU29ja2V0QmFzZVVSTCxcblx0XHRcdHNEcmFmdFVVSUQsXG5cdFx0XHR0aGlzLl9pbnRlcm5hbE1vZGVsLFxuXHRcdFx0dGhpcy5fb25NZXNzYWdlUmVjZWl2ZWQuYmluZCh0aGlzKSxcblx0XHRcdHRydWVcblx0XHQpO1xuXG5cdFx0dGhpcy5fcm9vdFBhdGggPSBvQ29udGV4dC5nZXRQYXRoKCk7XG5cdFx0dGhpcy5fb01vZGVsID0gb0NvbnRleHQuZ2V0TW9kZWwoKSBhcyBPRGF0YU1vZGVsO1xuXHR9LFxuXG5cdC8qKlxuXHQgKiBDaGVja3MgaWYgdGhlIGdob3N0IGNsaWVudCBoYXMgcmV2aWV2ZWQgYSBnaXZlbiBtZXNzYWdlLlxuXHQgKlxuXHQgKiBAcGFyYW0gbWVzc2FnZSBUaGUgbWVzc2FnZSBjb250ZW50IHRvIGJlIGxvb2tlZCBmb3Jcblx0ICogQHJldHVybnMgVHJ1ZSBpZiB0aGUgbGFzdCByZWNpZXZlZCBtZXNzYWdlIG1hdGNoZXMgdGhlIGNvbnRlbnRcblx0ICovXG5cdGNoZWNrUmVjZWl2ZWQ6IGZ1bmN0aW9uIChtZXNzYWdlOiBQYXJ0aWFsPE1lc3NhZ2U+KTogYm9vbGVhbiB7XG5cdFx0aWYgKCF0aGlzLl9sYXN0UmVjZWl2ZWRNZXNzYWdlKSB7XG5cdFx0XHRyZXR1cm4gZmFsc2U7XG5cdFx0fVxuXG5cdFx0Y29uc3QgZm91bmQgPVxuXHRcdFx0KCFtZXNzYWdlLnVzZXJJRCB8fCBtZXNzYWdlLnVzZXJJRCA9PT0gdGhpcy5fbGFzdFJlY2VpdmVkTWVzc2FnZS51c2VySUQpICYmXG5cdFx0XHQoIW1lc3NhZ2UudXNlckFjdGlvbiB8fCBtZXNzYWdlLnVzZXJBY3Rpb24gPT09IHRoaXMuX2xhc3RSZWNlaXZlZE1lc3NhZ2UudXNlckFjdGlvbikgJiZcblx0XHRcdCghbWVzc2FnZS5jbGllbnRDb250ZW50IHx8IG1lc3NhZ2UuY2xpZW50Q29udGVudCA9PT0gdGhpcy5fbGFzdFJlY2VpdmVkTWVzc2FnZS5jbGllbnRDb250ZW50KTtcblxuXHRcdHRoaXMuX2xhc3RSZWNlaXZlZE1lc3NhZ2UgPSB1bmRlZmluZWQ7IC8vIHJlc2V0IGhpc3RvcnkgdG8gYXZvaWQgZmluZGluZyB0aGUgc2FtZSBtZXNzYWdlIHR3aWNlXG5cblx0XHRyZXR1cm4gZm91bmQ7XG5cdH0sXG5cblx0LyoqXG5cdCAqIENsb3NlcyB0aGUgZ2hvc3QgY2xpZW50IGFuZCByZW1vdmVzIHRoZSB1c2VyIGZyb20gdGhlIGNvbGxhYm9yYXRpdmUgZHJhZnQuXG5cdCAqL1xuXHRsZWF2ZURyYWZ0OiBmdW5jdGlvbiAoKSB7XG5cdFx0aWYgKHRoaXMuX2ludGVybmFsTW9kZWwpIHtcblx0XHRcdGVuZENvbGxhYm9yYXRpb24odGhpcy5faW50ZXJuYWxNb2RlbCk7XG5cdFx0XHR0aGlzLl9pbnRlcm5hbE1vZGVsLmRlc3Ryb3koKTtcblx0XHRcdHRoaXMuX2ludGVybmFsTW9kZWwgPSB1bmRlZmluZWQ7XG5cdFx0fVxuXHR9LFxuXG5cdC8qKlxuXHQgKiBTaW11bGF0ZXMgdGhhdCB0aGUgdXNlciBzdGFydHMgdHlwaW5nIGluIGFuIGlucHV0IChsaXZlIGNoYW5nZSkuXG5cdCAqXG5cdCAqIEBwYXJhbSBzUHJvcGVydHlQYXRoIFRoZSBwYXRoIG9mIHRoZSBwcm9wZXJ0eSBiZWluZyBtb2RpZmllZFxuXHQgKi9cblx0c3RhcnRMaXZlQ2hhbmdlOiBmdW5jdGlvbiAoc1Byb3BlcnR5UGF0aDogc3RyaW5nKSB7XG5cdFx0aWYgKHRoaXMuX2ludGVybmFsTW9kZWwpIHtcblx0XHRcdGlmICh0aGlzLl9sb2NrZWRQcm9wZXJ0eVBhdGgpIHtcblx0XHRcdFx0Ly8gVW5sb2NrIHByZXZpb3VzIHByb3BlcnR5IHBhdGhcblx0XHRcdFx0dGhpcy51bmRvQ2hhbmdlKCk7XG5cdFx0XHR9XG5cdFx0XHR0aGlzLl9sb2NrZWRQcm9wZXJ0eVBhdGggPSBzUHJvcGVydHlQYXRoO1xuXHRcdFx0YnJvYWRjYXN0Q29sbGFib3JhdGlvbk1lc3NhZ2UoQWN0aXZpdHkuTGl2ZUNoYW5nZSwgdGhpcy5fcm9vdFBhdGggKyBcIi9cIiArIHNQcm9wZXJ0eVBhdGgsIHRoaXMuX2ludGVybmFsTW9kZWwpO1xuXHRcdH1cblx0fSxcblxuXHQvKipcblx0ICogU2ltdWxhdGVzIHRoYXQgdGhlIHVzZXIgaGFzIG1vZGlmaWVkIGEgcHJvcGVydHkuXG5cdCAqXG5cdCAqIEBwYXJhbSBzUHJvcGVydHlQYXRoIFRoZSBwYXRoIG9mIHRoZSBwcm9wZXJ0eSBiZWluZyBtb2RpZmllZFxuXHQgKiBAcGFyYW0gdmFsdWUgVGhlIG5ldyB2YWx1ZSBvZiB0aGUgcHJvcGVydHkgYmVpbmcgbW9kaWZpZWRcblx0ICovXG5cdHVwZGF0ZVByb3BlcnR5VmFsdWU6IGZ1bmN0aW9uIChzUHJvcGVydHlQYXRoOiBzdHJpbmcsIHZhbHVlOiBhbnkpIHtcblx0XHRpZiAodGhpcy5faW50ZXJuYWxNb2RlbCkge1xuXHRcdFx0aWYgKHRoaXMuX2xvY2tlZFByb3BlcnR5UGF0aCAhPT0gc1Byb3BlcnR5UGF0aCkge1xuXHRcdFx0XHR0aGlzLnN0YXJ0TGl2ZUNoYW5nZShzUHJvcGVydHlQYXRoKTtcblx0XHRcdH1cblxuXHRcdFx0Y29uc3Qgb0NvbnRleHRCaW5kaW5nID0gdGhpcy5fb01vZGVsIS5iaW5kQ29udGV4dCh0aGlzLl9yb290UGF0aCwgdW5kZWZpbmVkLCB7XG5cdFx0XHRcdCQkcGF0Y2hXaXRob3V0U2lkZUVmZmVjdHM6IHRydWUsXG5cdFx0XHRcdCQkZ3JvdXBJZDogXCIkYXV0b1wiLFxuXHRcdFx0XHQkJHVwZGF0ZUdyb3VwSWQ6IFwiJGF1dG9cIlxuXHRcdFx0fSk7XG5cblx0XHRcdGNvbnN0IG9Qcm9wZXJ0eUJpbmRpbmcgPSB0aGlzLl9vTW9kZWwhLmJpbmRQcm9wZXJ0eShzUHJvcGVydHlQYXRoLCBvQ29udGV4dEJpbmRpbmcuZ2V0Qm91bmRDb250ZXh0KCkpO1xuXG5cdFx0XHRvUHJvcGVydHlCaW5kaW5nXG5cdFx0XHRcdC5yZXF1ZXN0VmFsdWUoKVxuXHRcdFx0XHQudGhlbigoKSA9PiB7XG5cdFx0XHRcdFx0b1Byb3BlcnR5QmluZGluZy5zZXRWYWx1ZSh2YWx1ZSk7XG5cdFx0XHRcdFx0b0NvbnRleHRCaW5kaW5nLmF0dGFjaEV2ZW50T25jZShcInBhdGNoQ29tcGxldGVkXCIsICgpID0+IHtcblx0XHRcdFx0XHRcdGJyb2FkY2FzdENvbGxhYm9yYXRpb25NZXNzYWdlKEFjdGl2aXR5LkNoYW5nZSwgdGhpcy5fcm9vdFBhdGggKyBcIi9cIiArIHNQcm9wZXJ0eVBhdGgsIHRoaXMuX2ludGVybmFsTW9kZWwhKTtcblx0XHRcdFx0XHRcdHRoaXMuX2xvY2tlZFByb3BlcnR5UGF0aCA9IFwiXCI7XG5cdFx0XHRcdFx0fSk7XG5cdFx0XHRcdH0pXG5cdFx0XHRcdC5jYXRjaChmdW5jdGlvbiAoZXJyKSB7XG5cdFx0XHRcdFx0TG9nLmVycm9yKGVycik7XG5cdFx0XHRcdH0pO1xuXHRcdH1cblx0fSxcblxuXHQvKipcblx0ICogU2ltdWxhdGVzIHRoYXQgdGhlIHVzZXIgZGlkIGFuICd1bmRvJyAodG8gYmUgY2FsbGVkIGFmdGVyIHN0YXJ0TGl2ZUNoYW5nZSkuXG5cdCAqL1xuXHR1bmRvQ2hhbmdlOiBmdW5jdGlvbiAoKSB7XG5cdFx0aWYgKHRoaXMuX2xvY2tlZFByb3BlcnR5UGF0aCkge1xuXHRcdFx0YnJvYWRjYXN0Q29sbGFib3JhdGlvbk1lc3NhZ2UoQWN0aXZpdHkuVW5kbywgdGhpcy5fcm9vdFBhdGggKyBcIi9cIiArIHRoaXMuX2xvY2tlZFByb3BlcnR5UGF0aCwgdGhpcy5faW50ZXJuYWxNb2RlbCEpO1xuXHRcdFx0dGhpcy5fbG9ja2VkUHJvcGVydHlQYXRoID0gXCJcIjtcblx0XHR9XG5cdH0sXG5cblx0LyoqXG5cdCAqIFNpbXVsYXRlcyB0aGF0IHRoZSB1c2VyIGhhcyBkaXNjYXJkZWQgdGhlIGRyYWZ0LlxuXHQgKi9cblx0ZGlzY2FyZERyYWZ0OiBmdW5jdGlvbiAoKSB7XG5cdFx0aWYgKHRoaXMuX2ludGVybmFsTW9kZWwpIHtcblx0XHRcdGNvbnN0IGRyYWZ0Q29udGV4dCA9IHRoaXMuX2dldERyYWZ0Q29udGV4dCgpO1xuXG5cdFx0XHRkcmFmdENvbnRleHRcblx0XHRcdFx0LnJlcXVlc3RQcm9wZXJ0eShcIklzQWN0aXZlRW50aXR5XCIpXG5cdFx0XHRcdC50aGVuKCgpID0+IHtcblx0XHRcdFx0XHRkcmFmdC5kZWxldGVEcmFmdChkcmFmdENvbnRleHQpO1xuXHRcdFx0XHR9KVxuXHRcdFx0XHQudGhlbigoKSA9PiB7XG5cdFx0XHRcdFx0YnJvYWRjYXN0Q29sbGFib3JhdGlvbk1lc3NhZ2UoXG5cdFx0XHRcdFx0XHRBY3Rpdml0eS5EaXNjYXJkLFxuXHRcdFx0XHRcdFx0dGhpcy5fcm9vdFBhdGgucmVwbGFjZShcIklzQWN0aXZlRW50aXR5PWZhbHNlXCIsIFwiSXNBY3RpdmVFbnRpdHk9dHJ1ZVwiKSxcblx0XHRcdFx0XHRcdHRoaXMuX2ludGVybmFsTW9kZWwhXG5cdFx0XHRcdFx0KTtcblx0XHRcdFx0XHR0aGlzLl9pbnRlcm5hbE1vZGVsIS5kZXN0cm95KCk7XG5cdFx0XHRcdFx0dGhpcy5faW50ZXJuYWxNb2RlbCA9IHVuZGVmaW5lZDtcblx0XHRcdFx0fSlcblx0XHRcdFx0LmNhdGNoKGZ1bmN0aW9uIChlcnI6IGFueSkge1xuXHRcdFx0XHRcdExvZy5lcnJvcihlcnIpO1xuXHRcdFx0XHR9KTtcblx0XHR9XG5cdH0sXG5cblx0LyoqXG5cdCAqIFNpbXVsYXRlcyB0aGF0IHRoZSB1c2VyIGhhcyBkZWxldGVkIHRoZSBkcmFmdC5cblx0ICovXG5cdGRlbGV0ZURyYWZ0OiBmdW5jdGlvbiAoKSB7XG5cdFx0aWYgKHRoaXMuX2ludGVybmFsTW9kZWwpIHtcblx0XHRcdGNvbnN0IGRyYWZ0Q29udGV4dCA9IHRoaXMuX2dldERyYWZ0Q29udGV4dCgpO1xuXHRcdFx0bGV0IGFjdGl2ZUNvbnRleHQ6IENvbnRleHQ7XG5cblx0XHRcdGRyYWZ0Q29udGV4dFxuXHRcdFx0XHQucmVxdWVzdFByb3BlcnR5KFwiSXNBY3RpdmVFbnRpdHlcIilcblx0XHRcdFx0LnRoZW4oKCkgPT4ge1xuXHRcdFx0XHRcdHJldHVybiBkcmFmdENvbnRleHRcblx0XHRcdFx0XHRcdC5nZXRNb2RlbCgpXG5cdFx0XHRcdFx0XHQuYmluZENvbnRleHQodGhpcy5fcm9vdFBhdGggKyBcIi9TaWJsaW5nRW50aXR5XCIpXG5cdFx0XHRcdFx0XHQuZ2V0Qm91bmRDb250ZXh0KCk7XG5cdFx0XHRcdH0pXG5cdFx0XHRcdC50aGVuKChjb250ZXh0OiBhbnkpID0+IHtcblx0XHRcdFx0XHRhY3RpdmVDb250ZXh0ID0gY29udGV4dDtcblx0XHRcdFx0XHRyZXR1cm4gY29udGV4dC5yZXF1ZXN0Q2Fub25pY2FsUGF0aCgpO1xuXHRcdFx0XHR9KVxuXHRcdFx0XHQudGhlbigoKSA9PiB7XG5cdFx0XHRcdFx0cmV0dXJuIGRyYWZ0LmRlbGV0ZURyYWZ0KGRyYWZ0Q29udGV4dCk7XG5cdFx0XHRcdH0pXG5cdFx0XHRcdC50aGVuKCgpID0+IHtcblx0XHRcdFx0XHQoYWN0aXZlQ29udGV4dCBhcyBhbnkpLmRlbGV0ZSgpO1xuXHRcdFx0XHRcdGJyb2FkY2FzdENvbGxhYm9yYXRpb25NZXNzYWdlKEFjdGl2aXR5LkRlbGV0ZSwgdGhpcy5fcm9vdFBhdGgsIHRoaXMuX2ludGVybmFsTW9kZWwhKTtcblx0XHRcdFx0XHR0aGlzLl9pbnRlcm5hbE1vZGVsIS5kZXN0cm95KCk7XG5cdFx0XHRcdFx0dGhpcy5faW50ZXJuYWxNb2RlbCA9IHVuZGVmaW5lZDtcblx0XHRcdFx0fSlcblx0XHRcdFx0LmNhdGNoKGZ1bmN0aW9uIChlcnI6IGFueSkge1xuXHRcdFx0XHRcdExvZy5lcnJvcihlcnIpO1xuXHRcdFx0XHR9KTtcblx0XHR9XG5cdH0sXG5cblx0Ly8gLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy8vLy9cblx0Ly8gUHJpdmF0ZSBtZXRob2RzXG5cblx0X2dldERyYWZ0Q29udGV4dDogZnVuY3Rpb24gKCk6IGFueSB7XG5cdFx0cmV0dXJuIHRoaXMuX29Nb2RlbCEuYmluZENvbnRleHQodGhpcy5fcm9vdFBhdGgsIHVuZGVmaW5lZCwge1xuXHRcdFx0JCRwYXRjaFdpdGhvdXRTaWRlRWZmZWN0czogdHJ1ZSxcblx0XHRcdCQkZ3JvdXBJZDogXCIkYXV0b1wiLFxuXHRcdFx0JCR1cGRhdGVHcm91cElkOiBcIiRhdXRvXCJcblx0XHR9KS5nZXRCb3VuZENvbnRleHQoKTtcblx0fSxcblxuXHQvKipcblx0ICogQ2FsbGJhY2sgb2YgdGhlIGdob3N0IGNsaWVudCB3aGVuIHJlY2VpdmluZyBhIG1lc3NhZ2Ugb24gdGhlIHdlYiBzb2NrZXQuXG5cdCAqXG5cdCAqIEBwYXJhbSBvTWVzc2FnZSBUaGUgbWVzc2FnZVxuXHQgKi9cblx0X29uTWVzc2FnZVJlY2VpdmVkOiBmdW5jdGlvbiAob01lc3NhZ2U6IE1lc3NhZ2UpIHtcblx0XHRvTWVzc2FnZS51c2VyQWN0aW9uID0gb01lc3NhZ2UudXNlckFjdGlvbiB8fCBvTWVzc2FnZS5jbGllbnRBY3Rpb247XG5cdFx0dGhpcy5fbGFzdFJlY2VpdmVkTWVzc2FnZSA9IG9NZXNzYWdlO1xuXG5cdFx0aWYgKG9NZXNzYWdlLnVzZXJBY3Rpb24gPT09IEFjdGl2aXR5LkpvaW4pIHtcblx0XHRcdGJyb2FkY2FzdENvbGxhYm9yYXRpb25NZXNzYWdlKFxuXHRcdFx0XHRBY3Rpdml0eS5Kb2luRWNobyxcblx0XHRcdFx0dGhpcy5fbG9ja2VkUHJvcGVydHlQYXRoID8gdGhpcy5fcm9vdFBhdGggKyBcIi9cIiArIHRoaXMuX2xvY2tlZFByb3BlcnR5UGF0aCA6IHVuZGVmaW5lZCxcblx0XHRcdFx0dGhpcy5faW50ZXJuYWxNb2RlbCFcblx0XHRcdCk7XG5cdFx0fVxuXHR9XG59O1xuXG5leHBvcnQgZGVmYXVsdCBDb2xsYWJvcmF0aW9uQVBJO1xuIl19
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import Log from "sap/base/Log";
|
|
2
|
+
import {
|
|
3
|
+
broadcastCollaborationMessage,
|
|
4
|
+
endCollaboration,
|
|
5
|
+
initializeCollaboration
|
|
6
|
+
} from "sap/fe/core/controllerextensions/collaboration/ActivityBase";
|
|
7
|
+
import { Activity, Message } from "sap/fe/core/controllerextensions/collaboration/CollaborationCommon";
|
|
8
|
+
import draft from "sap/fe/core/controllerextensions/editFlow/draft";
|
|
9
|
+
import Context from "sap/ui/model/Context";
|
|
10
|
+
import JSONModel from "sap/ui/model/json/JSONModel";
|
|
11
|
+
import ODataModel from "sap/ui/model/odata/v4/ODataModel";
|
|
12
|
+
|
|
13
|
+
const CollaborationAPI = {
|
|
14
|
+
_lastReceivedMessage: undefined as Message | undefined,
|
|
15
|
+
_rootPath: "",
|
|
16
|
+
_oModel: undefined as ODataModel | undefined,
|
|
17
|
+
_lockedPropertyPath: "",
|
|
18
|
+
_internalModel: undefined as JSONModel | undefined,
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Open an existing collaborative draft with a new user, and creates a 'ghost client' for this user.
|
|
22
|
+
*
|
|
23
|
+
* @param oContext The context of the collaborative draft
|
|
24
|
+
* @param userID The ID of the user
|
|
25
|
+
* @param userName The name of the user
|
|
26
|
+
*/
|
|
27
|
+
enterDraft: function (oContext: Context, userID: string, userName: string) {
|
|
28
|
+
const webSocketBaseURL: string = oContext.getModel().getMetaModel().getObject("/@com.sap.vocabularies.Common.v1.WebSocketBaseURL");
|
|
29
|
+
|
|
30
|
+
if (!webSocketBaseURL) {
|
|
31
|
+
Log.error("Cannot find WebSocketBaseURL annotation");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const sDraftUUID: string = oContext.getProperty("DraftAdministrativeData/DraftUUID");
|
|
36
|
+
this._internalModel = new JSONModel({});
|
|
37
|
+
|
|
38
|
+
initializeCollaboration(
|
|
39
|
+
{
|
|
40
|
+
id: userID,
|
|
41
|
+
name: userName,
|
|
42
|
+
initialName: userName
|
|
43
|
+
},
|
|
44
|
+
webSocketBaseURL,
|
|
45
|
+
sDraftUUID,
|
|
46
|
+
this._internalModel,
|
|
47
|
+
this._onMessageReceived.bind(this),
|
|
48
|
+
true
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
this._rootPath = oContext.getPath();
|
|
52
|
+
this._oModel = oContext.getModel() as ODataModel;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Checks if the ghost client has revieved a given message.
|
|
57
|
+
*
|
|
58
|
+
* @param message The message content to be looked for
|
|
59
|
+
* @returns True if the last recieved message matches the content
|
|
60
|
+
*/
|
|
61
|
+
checkReceived: function (message: Partial<Message>): boolean {
|
|
62
|
+
if (!this._lastReceivedMessage) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const found =
|
|
67
|
+
(!message.userID || message.userID === this._lastReceivedMessage.userID) &&
|
|
68
|
+
(!message.userAction || message.userAction === this._lastReceivedMessage.userAction) &&
|
|
69
|
+
(!message.clientContent || message.clientContent === this._lastReceivedMessage.clientContent);
|
|
70
|
+
|
|
71
|
+
this._lastReceivedMessage = undefined; // reset history to avoid finding the same message twice
|
|
72
|
+
|
|
73
|
+
return found;
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Closes the ghost client and removes the user from the collaborative draft.
|
|
78
|
+
*/
|
|
79
|
+
leaveDraft: function () {
|
|
80
|
+
if (this._internalModel) {
|
|
81
|
+
endCollaboration(this._internalModel);
|
|
82
|
+
this._internalModel.destroy();
|
|
83
|
+
this._internalModel = undefined;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Simulates that the user starts typing in an input (live change).
|
|
89
|
+
*
|
|
90
|
+
* @param sPropertyPath The path of the property being modified
|
|
91
|
+
*/
|
|
92
|
+
startLiveChange: function (sPropertyPath: string) {
|
|
93
|
+
if (this._internalModel) {
|
|
94
|
+
if (this._lockedPropertyPath) {
|
|
95
|
+
// Unlock previous property path
|
|
96
|
+
this.undoChange();
|
|
97
|
+
}
|
|
98
|
+
this._lockedPropertyPath = sPropertyPath;
|
|
99
|
+
broadcastCollaborationMessage(Activity.LiveChange, this._rootPath + "/" + sPropertyPath, this._internalModel);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Simulates that the user has modified a property.
|
|
105
|
+
*
|
|
106
|
+
* @param sPropertyPath The path of the property being modified
|
|
107
|
+
* @param value The new value of the property being modified
|
|
108
|
+
*/
|
|
109
|
+
updatePropertyValue: function (sPropertyPath: string, value: any) {
|
|
110
|
+
if (this._internalModel) {
|
|
111
|
+
if (this._lockedPropertyPath !== sPropertyPath) {
|
|
112
|
+
this.startLiveChange(sPropertyPath);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const oContextBinding = this._oModel!.bindContext(this._rootPath, undefined, {
|
|
116
|
+
$$patchWithoutSideEffects: true,
|
|
117
|
+
$$groupId: "$auto",
|
|
118
|
+
$$updateGroupId: "$auto"
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const oPropertyBinding = this._oModel!.bindProperty(sPropertyPath, oContextBinding.getBoundContext());
|
|
122
|
+
|
|
123
|
+
oPropertyBinding
|
|
124
|
+
.requestValue()
|
|
125
|
+
.then(() => {
|
|
126
|
+
oPropertyBinding.setValue(value);
|
|
127
|
+
oContextBinding.attachEventOnce("patchCompleted", () => {
|
|
128
|
+
broadcastCollaborationMessage(Activity.Change, this._rootPath + "/" + sPropertyPath, this._internalModel!);
|
|
129
|
+
this._lockedPropertyPath = "";
|
|
130
|
+
});
|
|
131
|
+
})
|
|
132
|
+
.catch(function (err) {
|
|
133
|
+
Log.error(err);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Simulates that the user did an 'undo' (to be called after startLiveChange).
|
|
140
|
+
*/
|
|
141
|
+
undoChange: function () {
|
|
142
|
+
if (this._lockedPropertyPath) {
|
|
143
|
+
broadcastCollaborationMessage(Activity.Undo, this._rootPath + "/" + this._lockedPropertyPath, this._internalModel!);
|
|
144
|
+
this._lockedPropertyPath = "";
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Simulates that the user has discarded the draft.
|
|
150
|
+
*/
|
|
151
|
+
discardDraft: function () {
|
|
152
|
+
if (this._internalModel) {
|
|
153
|
+
const draftContext = this._getDraftContext();
|
|
154
|
+
|
|
155
|
+
draftContext
|
|
156
|
+
.requestProperty("IsActiveEntity")
|
|
157
|
+
.then(() => {
|
|
158
|
+
draft.deleteDraft(draftContext);
|
|
159
|
+
})
|
|
160
|
+
.then(() => {
|
|
161
|
+
broadcastCollaborationMessage(
|
|
162
|
+
Activity.Discard,
|
|
163
|
+
this._rootPath.replace("IsActiveEntity=false", "IsActiveEntity=true"),
|
|
164
|
+
this._internalModel!
|
|
165
|
+
);
|
|
166
|
+
this._internalModel!.destroy();
|
|
167
|
+
this._internalModel = undefined;
|
|
168
|
+
})
|
|
169
|
+
.catch(function (err: any) {
|
|
170
|
+
Log.error(err);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Simulates that the user has deleted the draft.
|
|
177
|
+
*/
|
|
178
|
+
deleteDraft: function () {
|
|
179
|
+
if (this._internalModel) {
|
|
180
|
+
const draftContext = this._getDraftContext();
|
|
181
|
+
let activeContext: Context;
|
|
182
|
+
|
|
183
|
+
draftContext
|
|
184
|
+
.requestProperty("IsActiveEntity")
|
|
185
|
+
.then(() => {
|
|
186
|
+
return draftContext
|
|
187
|
+
.getModel()
|
|
188
|
+
.bindContext(this._rootPath + "/SiblingEntity")
|
|
189
|
+
.getBoundContext();
|
|
190
|
+
})
|
|
191
|
+
.then((context: any) => {
|
|
192
|
+
activeContext = context;
|
|
193
|
+
return context.requestCanonicalPath();
|
|
194
|
+
})
|
|
195
|
+
.then(() => {
|
|
196
|
+
return draft.deleteDraft(draftContext);
|
|
197
|
+
})
|
|
198
|
+
.then(() => {
|
|
199
|
+
(activeContext as any).delete();
|
|
200
|
+
broadcastCollaborationMessage(Activity.Delete, this._rootPath, this._internalModel!);
|
|
201
|
+
this._internalModel!.destroy();
|
|
202
|
+
this._internalModel = undefined;
|
|
203
|
+
})
|
|
204
|
+
.catch(function (err: any) {
|
|
205
|
+
Log.error(err);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
// /////////////////////////////
|
|
211
|
+
// Private methods
|
|
212
|
+
|
|
213
|
+
_getDraftContext: function (): any {
|
|
214
|
+
return this._oModel!.bindContext(this._rootPath, undefined, {
|
|
215
|
+
$$patchWithoutSideEffects: true,
|
|
216
|
+
$$groupId: "$auto",
|
|
217
|
+
$$updateGroupId: "$auto"
|
|
218
|
+
}).getBoundContext();
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Callback of the ghost client when receiving a message on the web socket.
|
|
223
|
+
*
|
|
224
|
+
* @param oMessage The message
|
|
225
|
+
*/
|
|
226
|
+
_onMessageReceived: function (oMessage: Message) {
|
|
227
|
+
oMessage.userAction = oMessage.userAction || oMessage.clientAction;
|
|
228
|
+
this._lastReceivedMessage = oMessage;
|
|
229
|
+
|
|
230
|
+
if (oMessage.userAction === Activity.Join) {
|
|
231
|
+
broadcastCollaborationMessage(
|
|
232
|
+
Activity.JoinEcho,
|
|
233
|
+
this._lockedPropertyPath ? this._rootPath + "/" + this._lockedPropertyPath : undefined,
|
|
234
|
+
this._internalModel!
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export default CollaborationAPI;
|