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