1/* 2 * Copyright (c) 2023-2024 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 * all definitions in this file are framework internal 16*/ 17 18class __RepeatImpl<T> { 19 private arr_: Array<T>; 20 private itemGenFuncs_: { [type: string]: RepeatItemGenFunc<T> }; 21 private keyGenFunction_?: RepeatKeyGenFunc<T>; 22 private typeGenFunc_: RepeatTypeGenFunc<T>; 23 // 24 private mkRepeatItem_: (item: T, index?: number) =>__RepeatItemFactoryReturn<T>; 25 private onMoveHandler_?: OnMoveHandler; 26 27 private key2Item_ = new Map<string, __RepeatItemInfo<T>>(); 28 29 /**/ 30 constructor() { 31 } 32 33 /**/ 34 public render(config: __RepeatConfig<T>, isInitialRender: boolean): void { 35 this.arr_ = config.arr; 36 this.itemGenFuncs_ = config.itemGenFuncs; 37 this.typeGenFunc_ = config.typeGenFunc; 38 this.keyGenFunction_ = config.keyGenFunc; 39 this.mkRepeatItem_ = config.mkRepeatItem; 40 this.onMoveHandler_ = config.onMoveHandler; 41 42 isInitialRender ? this.initialRender() : this.reRender(); 43 } 44 45 private genKeys(): Map<string, __RepeatItemInfo<T>> { 46 const key2Item = new Map<string, __RepeatItemInfo<T>>(); 47 this.arr_.forEach((item, index) => { 48 const key = this.keyGenFunction_(item, index); 49 key2Item.set(key, { key, index }); 50 }); 51 if (key2Item.size < this.arr_.length) { 52 stateMgmtConsole.warn('__RepeatImpl: Duplicates detected, fallback to index-based keyGen.'); 53 // Causes all items to be re-rendered 54 this.keyGenFunction_ = __RepeatDefaultKeyGen.funcWithIndex; 55 return this.genKeys(); 56 } 57 return key2Item; 58 } 59 60 private initialRender(): void { 61 //console.log('__RepeatImpl initialRender() 0') 62 this.key2Item_ = this.genKeys(); 63 64 RepeatNative.startRender(); 65 66 let index = 0; 67 this.key2Item_.forEach((itemInfo, key) => { 68 itemInfo.repeatItem = this.mkRepeatItem_(this.arr_[index], index); 69 this.initialRenderItem(key, itemInfo.repeatItem); 70 index++; 71 }); 72 let removedChildElmtIds = new Array<number>(); 73 // Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy 74 RepeatNative.onMove(this.onMoveHandler_); 75 RepeatNative.finishRender(removedChildElmtIds); 76 UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds); 77 stateMgmtConsole.debug(`__RepeatImpl: initialRender elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`); 78 } 79 80 private reRender(): void { 81 const oldKey2Item: Map<string, __RepeatItemInfo<T>> = this.key2Item_; 82 this.key2Item_ = this.genKeys(); 83 84 // identify array items that have been deleted 85 // these are candidates for re-use 86 const deletedKeysAndIndex = new Array<__RepeatItemInfo<T>>(); 87 for (const [key, feInfo] of oldKey2Item) { 88 if (!this.key2Item_.has(key)) { 89 deletedKeysAndIndex.push(feInfo); 90 } 91 } 92 93 // C++: mv children_ aside to tempchildren_ 94 RepeatNative.startRender(); 95 96 let index = 0; 97 this.key2Item_.forEach((itemInfo, key) => { 98 const item = this.arr_[index]; 99 let oldItemInfo = oldKey2Item.get(key); 100 101 if (oldItemInfo) { 102 // case #1 retained array item 103 // moved from oldIndex to index 104 const oldIndex = oldItemInfo.index; 105 itemInfo.repeatItem = oldItemInfo!.repeatItem!; 106 stateMgmtConsole.debug(`__RepeatImpl: retained: key ${key} ${oldIndex}->${index}`); 107 itemInfo.repeatItem.updateIndex(index); 108 // C++ mv from tempChildren[oldIndex] to end of children_ 109 RepeatNative.moveChild(oldIndex); 110 111 // TBD moveChild() only when item types are same 112 } else if (deletedKeysAndIndex.length) { 113 // case #2: 114 // new array item, there is an deleted array items whose 115 // UINode children cab re-used 116 const oldItemInfo = deletedKeysAndIndex.pop(); 117 const reuseKey = oldItemInfo!.key; 118 const oldKeyIndex = oldItemInfo!.index; 119 const oldRepeatItem = oldItemInfo!.repeatItem!; 120 itemInfo.repeatItem = oldRepeatItem; 121 stateMgmtConsole.debug(`__RepeatImpl: new: key ${key} reuse key ${reuseKey} ${oldKeyIndex}->${index}`); 122 123 itemInfo.repeatItem.updateItem(item); 124 itemInfo.repeatItem.updateIndex(index); 125 126 // update key2item_ Map 127 this.key2Item_.set(key, itemInfo); 128 129 // TBD moveChild() only when item types are same 130 // C++ mv from tempChildren[oldIndex] to end of children_ 131 RepeatNative.moveChild(oldKeyIndex); 132 } else { 133 // case #3: 134 // new array item, there are no deleted array items 135 // render new UINode children 136 itemInfo.repeatItem = this.mkRepeatItem_(item, index); 137 this.initialRenderItem(key, itemInfo.repeatItem); 138 } 139 140 index++; 141 }); 142 143 // keep this.id2item_. by removing all entries for remaining 144 // deleted items 145 deletedKeysAndIndex.forEach(delItem => { 146 this.key2Item_.delete(delItem!.key); 147 }); 148 149 // Finish up for.each update 150 // C++ tempChildren.clear() , trigger re-layout 151 let removedChildElmtIds = new Array<number>(); 152 // Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy 153 RepeatNative.onMove(this.onMoveHandler_); 154 RepeatNative.finishRender(removedChildElmtIds); 155 UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds); 156 stateMgmtConsole.debug(`__RepeatImpl: reRender elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`); 157 } 158 159 private initialRenderItem(key: string, repeatItem: __RepeatItemFactoryReturn<T>): void { 160 //console.log('__RepeatImpl initialRenderItem()') 161 // render new UINode children 162 stateMgmtConsole.debug(`__RepeatImpl: new: key ${key} n/a->${repeatItem.index}`); 163 164 // C++: initial render will render to the end of children_ 165 RepeatNative.createNewChildStart(key); 166 167 // execute the itemGen function 168 const itemType = this.typeGenFunc_(repeatItem.item, repeatItem.index) ?? ''; 169 const itemFunc = this.itemGenFuncs_[itemType] ?? this.itemGenFuncs_['']; 170 itemFunc(repeatItem); 171 172 RepeatNative.createNewChildFinish(key); 173 } 174 175}; 176