• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15/// <reference path="../../state_mgmt/src/lib/common/ifelse_native.d.ts" />
16/// <reference path="../../state_mgmt/src/lib/partial_update/pu_viewstack_processor.d.ts" />
17
18class BuilderNode {
19  private _JSBuilderNode: JSBuilderNode;
20  private nodePtr_: number | null;
21  constructor(uiContext: UIContext, options: RenderOptions) {
22    let jsBuilderNode = new JSBuilderNode(uiContext, options);
23    this._JSBuilderNode = jsBuilderNode;
24    let id = Symbol('BuilderNode');
25    BuilderNodeFinalizationRegisterProxy.ElementIdToOwningBuilderNode_.set(id, jsBuilderNode);
26    BuilderNodeFinalizationRegisterProxy.register(this, { name: 'BuilderNode', idOfNode: id });
27  }
28  public update(params: Object) {
29    this._JSBuilderNode.update(params);
30  }
31  public build(builder: WrappedBuilder<Object[]>, params: Object) {
32    this._JSBuilderNode.build(builder, params);
33    this.nodePtr_ = this._JSBuilderNode.getNodePtr();
34  }
35  public getFrameNode(): FrameNode {
36    return this._JSBuilderNode.getFrameNode();
37  }
38  public postTouchEvent(touchEvent: TouchEvent): boolean {
39    return this._JSBuilderNode.postTouchEvent(touchEvent);
40  }
41  public dispose(): void {
42    this._JSBuilderNode.dispose();
43  }
44}
45
46class JSBuilderNode extends BaseNode {
47  private updateFuncByElmtId?: Map<number, UpdateFunc | UpdateFuncRecord>;
48  private params_: Object;
49  private uiContext_: UIContext;
50  private frameNode_: FrameNode;
51  private childrenWeakrefMap_ = new Map<number, WeakRef<ViewPU>>();
52
53  constructor(uiContext: UIContext, options?: RenderOptions) {
54    super(uiContext, options);
55    this.uiContext_ = uiContext;
56    this.updateFuncByElmtId = new Map();
57  }
58
59  public getCardId(): number {
60    return -1;
61  }
62
63  public addChild(child: ViewPU): boolean {
64    if (this.childrenWeakrefMap_.has(child.id__())) {
65      return false;
66    }
67    this.childrenWeakrefMap_.set(child.id__(), new WeakRef(child));
68    return true;
69  }
70  public getChildById(id: number) {
71    const childWeakRef = this.childrenWeakrefMap_.get(id);
72    return childWeakRef ? childWeakRef.deref() : undefined;
73  }
74  public updateStateVarsOfChildByElmtId(elmtId, params: Object): void {
75    if (elmtId < 0) {
76      return;
77    }
78    let child: ViewPU = this.getChildById(elmtId);
79    if (!child) {
80      return;
81    }
82    child.updateStateVars(params);
83  }
84  public createOrGetNode(elmtId: number, builder: () => object): object {
85    const entry = this.updateFuncByElmtId.get(elmtId);
86    if (entry === undefined) {
87      throw new Error(`fail to create node, elmtId is illegal`);
88    }
89    let updateFuncRecord : UpdateFuncRecord = (typeof entry === 'object') ? entry : undefined;
90    if(updateFuncRecord === undefined)
91    {
92      throw new Error(`fail to create node, the api level of app does not supported`);
93    }
94    let nodeInfo = updateFuncRecord.node;
95    if (nodeInfo === undefined) {
96      nodeInfo = builder();
97      updateFuncRecord.node = nodeInfo;
98    }
99    return nodeInfo;
100  }
101  public build(builder: WrappedBuilder<Object[]>, params: Object) {
102    __JSScopeUtil__.syncInstanceId(this.instanceId_);
103    this.params_ = params;
104    this.updateFuncByElmtId.clear();
105    this.nodePtr_ = super.create(builder.builder, this.params_);
106    if (this.frameNode_ === undefined || this.frameNode_ === null) {
107      this.frameNode_ = new FrameNode(this.uiContext_, 'BuilderNode');
108    }
109    this.frameNode_.setNodePtr(this.nodePtr_);
110    this.frameNode_.setBaseNode(this);
111    __JSScopeUtil__.restoreInstanceId();
112  }
113  public update(param: Object) {
114    __JSScopeUtil__.syncInstanceId(this.instanceId_);
115    this.purgeDeletedElmtIds();
116    this.params_ = param;
117    Array.from(this.updateFuncByElmtId.keys()).sort((a: number, b: number): number => {
118      return (a < b) ? -1 : (a > b) ? 1 : 0;
119    }).forEach(elmtId => this.UpdateElement(elmtId));
120    __JSScopeUtil__.restoreInstanceId();
121  }
122  private UpdateElement(elmtId: number): void {
123    // do not process an Element that has been marked to be deleted
124    const obj: UpdateFunc | UpdateFuncRecord | undefined = this.updateFuncByElmtId.get(elmtId);
125    const updateFunc = (typeof obj === 'object') ? obj.updateFunc : null;
126    if (typeof updateFunc === 'function') {
127      updateFunc(elmtId, /* isFirstRender */ false);
128      this.finishUpdateFunc();
129    }
130  }
131
132  protected purgeDeletedElmtIds(): void {
133    UINodeRegisterProxy.obtainDeletedElmtIds();
134    UINodeRegisterProxy.unregisterElmtIdsFromViewPUs();
135  }
136  public purgeDeleteElmtId(rmElmtId: number): boolean {
137    const result = this.updateFuncByElmtId.delete(rmElmtId);
138    if (result) {
139      UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(rmElmtId);
140    }
141    return result;
142  }
143
144  public getFrameNode(): FrameNode | null {
145    if (
146      this.frameNode_ !== undefined &&
147      this.frameNode_ !== null &&
148      this.frameNode_.getNodePtr() !== null
149    ) {
150      return this.frameNode_;
151    }
152    return null;
153  }
154
155  public observeComponentCreation(func: (arg0: number, arg1: boolean) => void) {
156    let elmId: number = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
157    UINodeRegisterProxy.ElementIdToOwningViewPU_.set(elmId, new WeakRef(this));
158    try {
159      func(elmId, true);
160    } catch (error) {
161      // avoid the incompatible change that move set function before updateFunc.
162      UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmId);
163      throw error;
164    }
165  }
166
167  public observeComponentCreation2(compilerAssignedUpdateFunc: UpdateFunc, classObject: { prototype: Object; pop?: () => void }): void {
168    const _componentName: string = classObject && 'name' in classObject ? (Reflect.get(classObject, 'name') as string) : 'unspecified UINode';
169    const _popFunc: () => void =
170      classObject && "pop" in classObject ? classObject.pop! : () => { };
171    const updateFunc = (elmtId: number, isFirstRender: boolean) => {
172      __JSScopeUtil__.syncInstanceId(this.instanceId_);
173      ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
174      compilerAssignedUpdateFunc(elmtId, isFirstRender, this.params_);
175      if (!isFirstRender) {
176        _popFunc();
177      }
178      ViewStackProcessor.StopGetAccessRecording();
179      __JSScopeUtil__.restoreInstanceId();
180    };
181
182    const elmtId = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
183    // needs to move set before updateFunc.
184    // make sure the key and object value exist since it will add node in attributeModifier during updateFunc.
185    this.updateFuncByElmtId.set(elmtId, {
186      updateFunc: updateFunc,
187      componentName: _componentName,
188    });
189    UINodeRegisterProxy.ElementIdToOwningViewPU_.set(elmtId, new WeakRef(this));
190    try {
191      updateFunc(elmtId, /* is first render */ true);
192    } catch (error) {
193      // avoid the incompatible change that move set function before updateFunc.
194      this.updateFuncByElmtId.delete(elmtId);
195      UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmtId);
196      throw error;
197    }
198  }
199
200  /**
201   Partial updates for ForEach.
202   * @param elmtId ID of element.
203   * @param itemArray Array of items for use of itemGenFunc.
204   * @param itemGenFunc Item generation function to generate new elements. If index parameter is
205   *                    given set itemGenFuncUsesIndex to true.
206   * @param idGenFunc   ID generation function to generate unique ID for each element. If index parameter is
207   *                    given set idGenFuncUsesIndex to true.
208   * @param itemGenFuncUsesIndex itemGenFunc optional index parameter is given or not.
209   * @param idGenFuncUsesIndex idGenFunc optional index parameter is given or not.
210   */
211  public forEachUpdateFunction(
212    elmtId: number,
213    itemArray: Array<any>,
214    itemGenFunc: (item: any, index?: number) => void,
215    idGenFunc?: (item: any, index?: number) => string,
216    itemGenFuncUsesIndex: boolean = false,
217    idGenFuncUsesIndex: boolean = false
218  ): void {
219    if (itemArray === null || itemArray === undefined) {
220      return;
221    }
222
223    if (itemGenFunc === null || itemGenFunc === undefined) {
224      return;
225    }
226
227    if (idGenFunc === undefined) {
228      idGenFuncUsesIndex = true;
229      // catch possible error caused by Stringify and re-throw an Error with a meaningful (!) error message
230      idGenFunc = (item: any, index: number) => {
231        try {
232          return `${index}__${JSON.stringify(item)}`;
233        } catch (e) {
234          throw new Error(
235            ` ForEach id ${elmtId}: use of default id generator function not possible on provided data structure. Need to specify id generator function (ForEach 3rd parameter). Application Error!`
236          );
237        }
238      };
239    }
240
241    let diffIndexArray = []; // New indexes compared to old one.
242    let newIdArray = [];
243    let idDuplicates = [];
244    const arr = itemArray; // just to trigger a 'get' onto the array
245
246    // ID gen is with index.
247    if (idGenFuncUsesIndex) {
248      // Create array of new ids.
249      arr.forEach((item, indx) => {
250        newIdArray.push(idGenFunc(item, indx));
251      });
252    } else {
253      // Create array of new ids.
254      arr.forEach((item, index) => {
255        newIdArray.push(
256          `${itemGenFuncUsesIndex ? index + "_" : ""}` + idGenFunc(item)
257        );
258      });
259    }
260
261    // Set new array on C++ side.
262    // C++ returns array of indexes of newly added array items.
263    // these are indexes in new child list.
264    ForEach.setIdArray(elmtId, newIdArray, diffIndexArray, idDuplicates);
265    // Item gen is with index.
266    diffIndexArray.forEach((indx) => {
267      ForEach.createNewChildStart(newIdArray[indx], this);
268      if (itemGenFuncUsesIndex) {
269        itemGenFunc(arr[indx], indx);
270      } else {
271        itemGenFunc(arr[indx]);
272      }
273      ForEach.createNewChildFinish(newIdArray[indx], this);
274    });
275  }
276
277  public ifElseBranchUpdateFunction(branchId: number, branchfunc: () => void) {
278    const oldBranchid = If.getBranchId();
279    if (branchId === oldBranchid) {
280      return;
281    }
282    // branchId identifies uniquely the if .. <1> .. else if .<2>. else .<3>.branch
283    // ifElseNode stores the most recent branch, so we can compare
284    // removedChildElmtIds will be filled with the elmtIds of all children and their children will be deleted in response to if .. else change
285    let removedChildElmtIds = new Array();
286    If.branchId(branchId, removedChildElmtIds);
287    this.purgeDeletedElmtIds();
288
289    branchfunc();
290  }
291  public getNodePtr(): number | null {
292    return this.nodePtr_;
293  }
294  public dispose() {
295    this.nodePtr_ = null;
296    super.dispose();
297    if (this.frameNode_ !== undefined && this.frameNode_ !== null) {
298      this.frameNode_.setNodePtr(null);
299    }
300  }
301}
302