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