• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021 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
16// Nativeview
17// implemented in C++  for release
18// and in utest/view_native_mock.ts for testing
19class View extends NativeView {
20    constructor(compilerAssignedUniqueChildId, parent) {
21        super(compilerAssignedUniqueChildId, parent);
22        this.propsUsedForRender = new Set();
23        this.isRenderingInProgress = false;
24        this.watchedProps = new Map();
25        this.id_ = SubscriberManager.Get().MakeId();
26        this.providedVars_ = parent ? new Map(parent.providedVars_)
27            : new Map();
28        SubscriberManager.Get().add(this);
29        aceConsole.debug(`${this.constructor.name}: constructor done`);
30    }
31    // globally unique id, this is different from compilerAssignedUniqueChildId!
32    id() {
33        return this.id_;
34    }
35    propertyHasChanged(info) {
36        if (info) {
37            // need to sync container instanceId to switch instanceId in C++ side.
38            this.syncInstanceId();
39            if (this.propsUsedForRender.has(info)) {
40                aceConsole.debug(`${this.constructor.name}: propertyHasChanged ['${info || "unknowm"}']. View needs update`);
41                this.markNeedUpdate();
42            }
43            else {
44                aceConsole.debug(`${this.constructor.name}: propertyHasChanged ['${info || "unknowm"}']. View does NOT need update`);
45            }
46            let cb = this.watchedProps.get(info);
47            if (cb) {
48                aceConsole.debug(`${this.constructor.name}: propertyHasChanged ['${info || "unknowm"}']. calling @Watch function`);
49                cb.call(this, info);
50            }
51            this.restoreInstanceId();
52        } // if info avail.
53    }
54    propertyRead(info) {
55        aceConsole.debug(`${this.constructor.name}: propertyRead ['${info || "unknowm"}'].`);
56        if (info && (info != "unknown") && this.isRenderingInProgress) {
57            this.propsUsedForRender.add(info);
58        }
59    }
60    // for test purposes
61    propertiesNeededToRender() {
62        return this.propsUsedForRender;
63    }
64    aboutToRender() {
65        aceConsole.log(`${this.constructor.name}: aboutToRender`);
66        // reset
67        this.propsUsedForRender = new Set();
68        this.isRenderingInProgress = true;
69    }
70    aboutToContinueRender() {
71        // do not reset
72        //this.propsUsedForRender = new Set<string>();
73        this.isRenderingInProgress = true;
74    }
75    onRenderDone() {
76        this.isRenderingInProgress = false;
77        aceConsole.log(`${this.constructor.name}: onRenderDone: render performed get access to these properties: ${JSON.stringify(Array.from(this.propsUsedForRender))}.`);
78    }
79    /**
80     * Function to be called from the constructor of the sub component
81     * to register a @Watch varibale
82     * @param propStr name of the variable. Note from @Provide and @Consume this is
83     *      the variable name and not the alias!
84     * @param callback application defined member function of sub-class
85     */
86    declareWatch(propStr, callback) {
87        this.watchedProps.set(propStr, callback);
88    }
89    /**
90     * This View @Provide's a variable under given name
91     * Call this function from the constructor of the sub class
92     * @param providedPropName either the variable name or the alias defined as
93     *        decorator param
94     * @param store the backing store object for this variable (not the get/set variable!)
95     */
96    addProvidedVar(providedPropName, store) {
97        if (this.providedVars_.has(providedPropName)) {
98            throw new ReferenceError(`${this.constructor.name}: duplicate @Provide property with name ${providedPropName}.
99      Property with this name is provided by one of the ancestor Views already.`);
100        }
101        this.providedVars_.set(providedPropName, store);
102    }
103    /**
104     * Method for the sub-class to call from its constructor for resolving
105     *       a @Consume variable and initializing its backing store
106     *       with the yncedPropertyTwoWay<T> object created from the
107     *       @Provide variable's backing store.
108     * @param providedPropName the name of the @Provide'd variable.
109     *     This is either the @Consume decortor parameter, or variable name.
110     * @param consumeVarName the @Consume variable name (not the
111     *            @Consume decortor parameter)
112     * @returns initiaizing value of the @Consume backing store
113     */
114    initializeConsume(providedPropName, consumeVarName) {
115        let providedVarStore = this.providedVars_.get(providedPropName);
116        if (providedVarStore === undefined) {
117            throw new ReferenceError(`${this.constructor.name}: missing @Provide property with name ${providedPropName}.
118     Fail to resolve @Consume(${providedPropName}).`);
119        }
120        return providedVarStore.createLink(this, consumeVarName);
121    }
122}
123
124function getContentStorage(view) {
125    return view.getContentStorage();
126}
127
128function getContext(view) {
129    return view.getContext();
130}
131
132class PersistentStorage {
133    constructor() {
134        this.links_ = new Map();
135        this.id_ = SubscriberManager.Get().MakeId();
136        SubscriberManager.Get().add(this);
137    }
138    /**
139     *
140     * @param storage method to be used by the framework to set the backend
141     * this is to be done during startup
142     */
143    static ConfigureBackend(storage) {
144        PersistentStorage.Storage_ = storage;
145    }
146    static GetOrCreate() {
147        if (PersistentStorage.Instance_) {
148            // already initialized
149            return PersistentStorage.Instance_;
150        }
151        PersistentStorage.Instance_ = new PersistentStorage();
152        return PersistentStorage.Instance_;
153    }
154    static AboutToBeDeleted() {
155        if (!PersistentStorage.Instance_) {
156            return;
157        }
158        PersistentStorage.GetOrCreate().aboutToBeDeleted();
159        PersistentStorage.Instance_ = undefined;
160    }
161    static PersistProp(key, defaultValue) {
162        PersistentStorage.GetOrCreate().persistProp(key, defaultValue);
163    }
164    static DeleteProp(key) {
165        PersistentStorage.GetOrCreate().deleteProp(key);
166    }
167    static PersistProps(properties) {
168        PersistentStorage.GetOrCreate().persistProps(properties);
169    }
170    static Keys() {
171        let result = [];
172        const it = PersistentStorage.GetOrCreate().keys();
173        let val = it.next();
174        while (!val.done) {
175            result.push(val.value);
176            val = it.next();
177        }
178        return result;
179    }
180    keys() {
181        return this.links_.keys();
182    }
183    persistProp(propName, defaultValue) {
184        if (this.persistProp1(propName, defaultValue)) {
185            // persist new prop
186            aceConsole.debug(`PersistentStorage: writing '${propName}' - '${this.links_.get(propName)}' to storage`);
187            PersistentStorage.Storage_.set(propName, JSON.stringify(this.links_.get(propName).get()));
188        }
189    }
190    // helper function to persist a property
191    // does everything except writing prop to disk
192    persistProp1(propName, defaultValue) {
193        if (defaultValue == null || defaultValue == undefined) {
194            aceConsole.error(`PersistentStorage: persistProp for ${propName} called with 'null' or 'undefined' default value!`);
195            return false;
196        }
197        if (this.links_.get(propName)) {
198            aceConsole.warn(`PersistentStorage: persistProp: ${propName} is already persisted`);
199            return false;
200        }
201        let link = AppStorage.GetOrCreate().link(propName, this);
202        if (link) {
203            aceConsole.debug(`PersistentStorage: persistProp ${propName} in AppStorage, using that`);
204            this.links_.set(propName, link);
205        }
206        else {
207            let newValue = PersistentStorage.Storage_.get(propName);
208            let returnValue;
209            if (!newValue || newValue == "") {
210                aceConsole.debug(`PersistentStorage: no entry for ${propName}, will initialize with default value`);
211                returnValue = defaultValue;
212            } else {
213                try {
214                    returnValue = JSON.parse(newValue);
215                }
216                catch (error) {
217                    aceConsole.error(`PersistentStorage: convert for ${propName} has error: ` + error.toString());
218                }
219            }
220            link = AppStorage.GetOrCreate().setAndLink(propName, returnValue, this);
221            this.links_.set(propName, link);
222            aceConsole.debug(`PersistentStorage: created new persistent prop for ${propName}`);
223        }
224        return true;
225    }
226    persistProps(properties) {
227        properties.forEach(property => this.persistProp1(property.key, property.defaultValue));
228        this.write();
229    }
230    deleteProp(propName) {
231        let link = this.links_.get(propName);
232        if (link) {
233            link.aboutToBeDeleted();
234            this.links_.delete(propName);
235            PersistentStorage.Storage_.delete(propName);
236            aceConsole.debug(`PersistentStorage: deleteProp: no longer persisting '${propName}'.`);
237        }
238        else {
239            aceConsole.warn(`PersistentStorage: '${propName}' is not a persisted property warning.`);
240        }
241    }
242    write() {
243        this.links_.forEach((link, propName, map) => {
244            aceConsole.debug(`PersistentStorage: writing ${propName} to storage`);
245            PersistentStorage.Storage_.set(propName, JSON.stringify(link.get()));
246        });
247    }
248    propertyHasChanged(info, isCrossWindow) {
249        aceConsole.debug("PersistentStorage: property changed");
250        if (isCrossWindow) {
251            aceConsole.debug(`PersistentStorage propertyHasChanged isCrossWindow is ${isCrossWindow}`);
252        } else {
253            this.write();
254        }
255    }
256    // public required by the interface, use the static method instead!
257    aboutToBeDeleted() {
258        aceConsole.debug("PersistentStorage: about to be deleted");
259        this.links_.forEach((val, key, map) => {
260            aceConsole.debug(`PersistentStorage: removing ${key}`);
261            val.aboutToBeDeleted();
262        });
263        this.links_.clear();
264        SubscriberManager.Get().delete(this.id());
265        PersistentStorage.Storage_.clear();
266    }
267    id() {
268        return this.id_;
269    }
270    /**
271    * This methid offers a way to force writing the property value with given
272    * key to persistent storage.
273    * In the general case this is unnecessary as the framework observed changes
274    * and triggers writing to disk by itself. For nested objects (e.g. array of
275    * objects) however changes of a property of a property as not observed. This
276    * is the case where the application needs to signal to the framework.
277    * @param key property that has changed
278    */
279    static NotifyHasChanged(propName) {
280        aceConsole.debug(`PersistentStorage: force writing '${propName}' - '${PersistentStorage.GetOrCreate().links_.get(propName)}' to storage`);
281        PersistentStorage.Storage_.set(propName, JSON.stringify(PersistentStorage.GetOrCreate().links_.get(propName).get()));
282    }
283}
284PersistentStorage.Instance_ = undefined;
285
286class Environment {
287    constructor() {
288        this.props_ = new Map();
289        Environment.EnvBackend_.onValueChanged(this.onValueChanged.bind(this));
290    }
291    static GetOrCreate() {
292        if (Environment.Instance_) {
293            // already initialized
294            return Environment.Instance_;
295        }
296        Environment.Instance_ = new Environment();
297        return Environment.Instance_;
298    }
299    static ConfigureBackend(envBackend) {
300        Environment.EnvBackend_ = envBackend;
301    }
302    static AboutToBeDeleted() {
303        if (!Environment.Instance_) {
304            return;
305        }
306        Environment.GetOrCreate().aboutToBeDeleted();
307        Environment.Instance_ = undefined;
308    }
309    static EnvProp(key, value) {
310        return Environment.GetOrCreate().envProp(key, value);
311    }
312    static EnvProps(props) {
313        Environment.GetOrCreate().envProps(props);
314    }
315    static Keys() {
316        return Environment.GetOrCreate().keys();
317    }
318    envProp(key, value) {
319        let prop = AppStorage.Prop(key);
320        if (prop) {
321            aceConsole.warn(`Environment: envProp '${key}': Property already exists in AppStorage. Not using environment property.`);
322            return false;
323        }
324        let tmp;
325        switch (key) {
326            case "accessibilityEnabled":
327                tmp = Environment.EnvBackend_.getAccessibilityEnabled();
328                break;
329            case "colorMode":
330                tmp = Environment.EnvBackend_.getColorMode();
331                break;
332            case "fontScale":
333                tmp = Environment.EnvBackend_.getFontScale().toFixed(2);
334                break;
335            case "fontWeightScale":
336                tmp = Environment.EnvBackend_.getFontWeightScale().toFixed(2);
337                break;
338            case "layoutDirection":
339                tmp = Environment.EnvBackend_.getLayoutDirection();
340                break;
341            case "languageCode":
342                tmp = Environment.EnvBackend_.getLanguageCode();
343                break;
344            default:
345                tmp = value;
346        }
347        prop = AppStorage.SetAndProp(key, tmp);
348        this.props_.set(key, prop);
349        aceConsole.debug(`Environment: envProp for '${key}' done.`);
350    }
351    envProps(properties) {
352        properties.forEach(property => {
353            this.envProp(property.key, property.defaultValue);
354            aceConsole.debug(`Environment: envProps for '${property.key}' done.`);
355        });
356    }
357    keys() {
358        let result = [];
359        const it = this.props_.keys();
360        let val = it.next();
361        while (!val.done) {
362            result.push(val.value);
363            val = it.next();
364        }
365        return result;
366    }
367    onValueChanged(key, value) {
368        let ok = AppStorage.Set(key, value);
369        if (ok) {
370            aceConsole.debug(`Environment: onValueChanged: ${key} changed to ${value}`);
371        }
372        else {
373            aceConsole.warn(`Environment: onValueChanged: error changing ${key}! See results above.`);
374        }
375    }
376    aboutToBeDeleted() {
377        this.props_.forEach((val, key, map) => {
378            val.aboutToBeDeleted();
379            AppStorage.Delete(key);
380        });
381    }
382}
383Environment.Instance_ = undefined;
384var global = globalThis;
385aceConsole.debug("ACE State Mgmt init ...");
386PersistentStorage.ConfigureBackend(new Storage());
387Environment.ConfigureBackend(new EnvironmentSetting());
388
389function notifyAppStorageChange(key, value) {
390    aceConsole.debug(`notifyAppStorageChange(${key}, ${value})`);
391    if (value === "undefined") {
392        return;
393    }
394    AppStorage.GetOrCreate().crossWindowNotify(key, value);
395}
396
397class Clipboard {
398    static set(type, value) {
399        JSClipboard.set(value);
400    }
401
402    static get(type) {
403        return new Promise((resolve, reject) => {
404            const callback = () => {
405                resolve();
406            };
407            JSClipboard.get(callback.bind(this));
408        })
409    }
410
411    static clear() {
412        JSClipboard.clear();
413    }
414}