@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.
@@ -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 (this.oBinding?.getModel() as any) as ODataModel;
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((this.aMockContexts as any) as Context[]);
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 (this.aMockContexts as any) as Context[];
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 (this.mockModel as any) as ODataModel;
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 (this.oMockContext as any) as Context;
206
+ return this.oMockContext as any as Context;
143
207
  });
144
208
  public attachEventOnce = jest.fn();
145
209
  public getModel = jest.fn(() => {
146
- return (this.mockModel as any) as ODataModel;
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 (new MockContext({ $path: sPath }) as any) as Context;
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 (new MockContext({ $path: sPath }) as any) as Context;
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 (this.mockListBinding as any) as ODataListBinding;
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 (this.mockContextBinding as any) as ODataContextBinding;
278
+ return this.mockContextBinding as any as ODataContextBinding;
215
279
  });
216
280
  public getMetaModel = jest.fn(() => {
217
- return (this.oMetaModel as any) as ODataMetaModel;
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;