• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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