• 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
16class SubscriberManager {
17    constructor() {
18        this.subscriberById_ = new Map();
19        this.nextFreeId_ = 0;
20        aceConsole.debug("SubscriberManager has been created.");
21    }
22    static Get() { return SubscriberManager.INSTANCE_; }
23    has(id) {
24        return this.subscriberById_.has(id);
25    }
26    get(id) {
27        return this.subscriberById_.get(id);
28    }
29    delete(id) {
30        return this.subscriberById_.delete(id);
31    }
32    add(newSubsriber) {
33        if (this.has(newSubsriber.id())) {
34            return false;
35        }
36        this.subscriberById_.set(newSubsriber.id(), newSubsriber);
37        return true;
38    }
39    /**
40     * Method for testing purposes
41     * @returns number of subscribers
42     */
43    numberOfSubscrbers() {
44        return this.subscriberById_.size;
45    }
46    /**
47     * for debug purposes dump all known subscriber's info to comsole
48     */
49    dumpSubscriberInfo() {
50        aceConsole.debug("Dump of SubscriberManager +++ (sart)");
51        for (let [id, subscriber] of this.subscriberById_) {
52            aceConsole.debug(`Id: ${id} -> ${subscriber['info'] ? subscriber['info']() : 'unknown'}`);
53        }
54        aceConsole.debug("Dump of SubscriberManager +++ (end)");
55    }
56    MakeId() {
57        return this.nextFreeId_++;
58    }
59}
60SubscriberManager.INSTANCE_ = new SubscriberManager();
61/**
62 * Abstract class that manages subscribing properties
63 * that implement the interfaces ISinglePropertyChangeSubscriber
64 * and/or IMultiPropertiesChangeSubscriber. Each using @State, @Link, etc
65 * decorated varibale in a component will make its own subscription.
66 * When the component is created the subscription is added, and when the
67 * component is deleted it unsubscribes.
68 *
69 * About lifecycle: It is legal use for two components with two @State
70 * decorated variables to share the same instance to a SubscribaleAbstract
71 * object. Each such decorated variable implementation makes its own
72 * subscription to the SubscribaleAbstract object. Hence, when both variables
73 * have unsubscribed the SubscribaleAbstract may do its own de-initilization.,
74 * e.g. release held external resources.
75 *
76 * How to extend:
77 * A subclass manages the get and set to one or several properties on its own.
78 * The subclass needs to notify all relevant value changes to the framework for the
79 * UI to be updated. Notification should only be given for class properties that
80 * are used to generate the UI.
81 *
82 * A subclass must call super() in its constructor to let this base class
83 * initialize itself.
84 *
85 * A subclass must call 'notifyPropertyHasChanged' after the relevant property
86 * has changes. The framework will notify all dependent components to re-render.
87 *
88 * A sub-class may overwrite the 'addOwningProperty' function to add own
89 * functionality, but it must call super.addowningOwningProperty(..). E.g.
90 * the sub-class could connect to external resources upon the first subscriber.
91 *
92 * A sub-class may also overwrite the 'removeOwningProperty' function or
93 * 'removeOwningPropertyById' function to add own functionality,
94 * but it must call super.removeOwningProperty(..).
95 * E.g. the sub-class could release held external resources upon loosing the
96 * last subscriber.
97 *
98 */
99class SubscribaleAbstract {
100    /**
101     * make sure the call super from subclass constructor!
102     */
103    constructor() {
104        this.owningProperties_ = new Set();
105        aceConsole.debug(`SubscribaleAbstract: construcstor done`);
106    }
107    /**
108    * A subsclass must call this function whenever one of its properties has
109     * changed that is used to construct the UI.
110     * @param propName name of the change property
111     * @param newValue the property value after the change
112     */
113    notifyPropertyHasChanged(propName, newValue) {
114        aceConsole.debug(`SubscribaleAbstract: notifyPropertyHasChanged '${propName}'.`);
115        var registry = SubscriberManager.Get();
116        this.owningProperties_.forEach((subscribedId) => {
117            var owningProperty = registry.get(subscribedId);
118            if (owningProperty) {
119                if ('hasChanged' in owningProperty) {
120                    owningProperty.hasChanged(newValue);
121                }
122                if ('propertyHasChanged' in owningProperty) {
123                    owningProperty.propertyHasChanged(propName);
124                }
125            }
126            else {
127                aceConsole.error(`SubscribaleAbstract: notifyHasChanged: unknown subscriber.'${subscribedId}' error!.`);
128            }
129        });
130    }
131    /**
132     * Method used by the framework to add subscribing decorated variables
133     * Subclass may overwrite this function but must call the function of the base
134     * class from its own implementation.
135     * @param subscriber new subscriber that implements ISinglePropertyChangeSubscriber
136     * and/or IMultiPropertiesChangeSubscriber interfaces
137     */
138    addOwningProperty(subscriber) {
139        aceConsole.debug(`SubscribaleAbstract: addOwningProperty: subscriber '${subscriber.id()}'.`);
140        this.owningProperties_.add(subscriber.id());
141    }
142    /**
143     * Method used by the framework to ubsubscribing decorated variables
144     * Subclass may overwrite this function but must call the function of the base
145     * class from its own implementation.
146     * @param subscriber subscriber that implements ISinglePropertyChangeSubscriber
147     * and/or IMultiPropertiesChangeSubscriber interfaces
148     */
149    removeOwningProperty(property) {
150        return this.removeOwningPropertyById(property.id());
151    }
152    removeOwningPropertyById(subscriberId) {
153        aceConsole.debug(`SubscribaleAbstract: removeOwningProperty '${subscriberId}'.`);
154        this.owningProperties_.delete(subscriberId);
155    }
156}
157/**
158* @Observed Decorator function, use
159*    @Observed class ClassA { ... }
160* when defining ClassA
161*
162* Can also be used to create a new Object and wrap it in
163* ObservedObject by calling
164*   obsObj = Observed(ClassA)(params to ClassA constructor)
165*
166* Note this works only for classes, not for ClassA[]
167* Also does not work for classes with genetics it seems
168* In that case use factory function
169*   obsObj = ObservedObject.createNew<ClassA[]>([])
170*/
171function Observed(target) {
172    var original = target;
173    // the new constructor behaviour
174    var f = function (...args) {
175        aceConsole.log(`New ${original.name}, gets wrapped inside ObservableObject proxy.`);
176        return new ObservedObject(new original(...args), undefined);
177    };
178    Object.setPrototypeOf(f, Object.getPrototypeOf(original));
179    // return new constructor (will override original)
180    return f;
181}
182class SubscribableHandler {
183    constructor(owningProperty) {
184        this.owningProperties_ = new Set();
185        if (owningProperty) {
186            this.addOwningProperty(owningProperty);
187        }
188        aceConsole.debug(`SubscribableHandler: construcstor done`);
189    }
190    addOwningProperty(subscriber) {
191        aceConsole.debug(`SubscribableHandler: addOwningProperty: subscriber '${subscriber.id()}'.`);
192        this.owningProperties_.add(subscriber.id());
193    }
194    /*
195        the inverse function of createOneWaySync or createTwoWaySync
196      */
197    removeOwningProperty(property) {
198        return this.removeOwningPropertyById(property.id());
199    }
200    removeOwningPropertyById(subscriberId) {
201        aceConsole.debug(`SubscribableHandler: removeOwningProperty '${subscriberId}'.`);
202        this.owningProperties_.delete(subscriberId);
203    }
204    notifyPropertyHasChanged(propName, newValue) {
205        aceConsole.debug(`SubscribableHandler: notifyPropertyHasChanged '${propName}'.`);
206        var registry = SubscriberManager.Get();
207        this.owningProperties_.forEach((subscribedId) => {
208            var owningProperty = registry.get(subscribedId);
209            if (owningProperty) {
210                if ('hasChanged' in owningProperty) {
211                    owningProperty.hasChanged(newValue);
212                }
213                if ('propertyHasChanged' in owningProperty) {
214                    owningProperty.propertyHasChanged(propName);
215                }
216            }
217            else {
218                aceConsole.error(`SubscribableHandler: notifyHasChanged: unknown subscriber.'${subscribedId}' error!.`);
219            }
220        });
221    }
222    get(target, property) {
223        return (property === SubscribableHandler.IS_OBSERVED_OBJECT) ? true :
224            (property === SubscribableHandler.RAW_OBJECT) ? target : target[property];
225    }
226    set(target, property, newValue) {
227        switch (property) {
228            case SubscribableHandler.SUBSCRIBE:
229                // assignment obsObj[SubscribableHandler.SUBSCRCRIBE] = subscriber
230                this.addOwningProperty(newValue);
231                return true;
232                break;
233            case SubscribableHandler.UNSUBSCRIBE:
234                // assignment obsObj[SubscribableHandler.UN_SUBSCRCRIBE] = subscriber
235                this.removeOwningProperty(newValue);
236                return true;
237                break;
238            default:
239                if (target[property] == newValue) {
240                    return true;
241                }
242                aceConsole.log(`SubscribableHandler: set property '${property.toString()}' to new value'`);
243                target[property] = newValue;
244                this.notifyPropertyHasChanged(property.toString(), newValue); // FIXME PropertyKey.toString
245                return true;
246                break;
247        }
248        // unreachable
249        return false;
250    }
251}
252SubscribableHandler.IS_OBSERVED_OBJECT = Symbol("_____is_observed_object__");
253SubscribableHandler.RAW_OBJECT = Symbol("_____raw_object__");
254SubscribableHandler.SUBSCRIBE = Symbol("_____subscribe__");
255SubscribableHandler.UNSUBSCRIBE = Symbol("_____unsubscribe__");
256class ExtendableProxy {
257    constructor(obj, handler) {
258        return new Proxy(obj, handler);
259    }
260}
261class ObservedObject extends ExtendableProxy {
262    /**
263     * Factory function for ObservedObjects /
264     *  wrapping of objects for proxying
265     *
266     * @param rawObject unproxied Object or ObservedObject
267     * @param objOwner owner of this Object to sign uop for propertyChange
268     *          notifications
269     * @returns the rawObject if object is already an ObservedObject,
270     *          otherwise the newly created ObservedObject
271     */
272    static createNew(rawObject, owningProperty) {
273        if (ObservedObject.IsObservedObject(rawObject)) {
274            ObservedObject.addOwningProperty(rawObject, owningProperty);
275            return rawObject;
276        }
277        else {
278            return new ObservedObject(rawObject, owningProperty);
279        }
280    }
281    /*
282      Return the unproxied object 'inside' the ObservedObject / the ES6 Proxy
283      no set observation, no notification of changes!
284      Use with caution, do not store any references
285    */
286    static GetRawObject(obj) {
287        return !ObservedObject.IsObservedObject(obj) ? obj : obj[SubscribableHandler.RAW_OBJECT];
288    }
289    /**
290     *
291     * @param obj anything
292     * @returns true if the parameter is an Object wrpped with a ObservedObject
293     * Note: Since ES6 Proying is transparent, 'instance of' will not work. Use
294     * this static function instead.
295     */
296    static IsObservedObject(obj) {
297        return obj ? (obj[SubscribableHandler.IS_OBSERVED_OBJECT] == true) : false;
298    }
299    static addOwningProperty(obj, subscriber) {
300        if (!ObservedObject.IsObservedObject(obj)) {
301            return false;
302        }
303        obj[SubscribableHandler.SUBSCRIBE] = subscriber;
304        return true;
305    }
306    static removeOwningProperty(obj, subscriber) {
307        if (!ObservedObject.IsObservedObject(obj)) {
308            return false;
309        }
310        obj[SubscribableHandler.UNSUBSCRIBE] = subscriber;
311        return true;
312    }
313    /**
314     * Create a new ObservableObject and subscribe its owner to propertyHasChanged
315     * ntifications
316     * @param obj  raw Object, if obj is a ObservableOject throws an error
317     * @param objectOwner
318     */
319    constructor(obj, objectOwningProperty) {
320        if (ObservedObject.IsObservedObject(obj)) {
321            throw new Error("Invalid constructor argument error: ObservableObject contructor called with an ObservedObject as parameer");
322        }
323        let handler = new SubscribableHandler(objectOwningProperty);
324        super(obj, handler);
325        if (ObservedObject.IsObservedObject(obj)) {
326            aceConsole.error("ObservableOject constructor: INTERNAL ERROR: after jsObj is observedObject already");
327        }
328    } // end of constructor
329}
330/*
331
332  Overview of the Observed Property class hiararchy
333
334  ObservedPropertyAbstract
335     |-- ObservedSimplePropertyAbstract - boolean, number, string
336     |         |-- ObservedSimpleProperty - owns the property
337     |         |-- SynchedSimplePropertyOneWay - one way sync from ObservedSimpleProperty
338     |         |        |--SynchedPropertySimpleOneWaySubscribing - one way sync
339     |         |           from ObservedSimpleProperty, return value of AppStorage.prop(..)
340     |         |-- SynchedSimplePropertyTwoWay - two way sync with ObservedSimpleProperty
341     |
342     |-- ObservedObjectPropertyAbstract - Object proxied by ObservedObject
343               |-- ObservedObjectProperty - owns the property
344               |-- SynchedObjectPropertyTwoWay - two way sync with ObservedObjectProperty
345
346*/
347/*
348   manage subscriptions to a property
349   managing the property is left to sub
350   classes
351   Extended by ObservedProperty, SyncedPropertyOneWay
352   and SyncedPropertyTwoWay
353*/
354class ObservedPropertyAbstract {
355    constructor(subscribeMe, info) {
356        this.subscribers_ = new Set();
357        this.id_ = SubscriberManager.Get().MakeId();
358        SubscriberManager.Get().add(this);
359        if (subscribeMe) {
360            this.subscribers_.add(subscribeMe.id());
361        }
362        if (info) {
363            this.info_ = info;
364        }
365    }
366    aboutToBeDeleted() {
367        SubscriberManager.Get().delete(this.id());
368    }
369    id() {
370        return this.id_;
371    }
372    info() {
373        return this.info_;
374    }
375    subscribeMe(subscriber) {
376        aceConsole.debug(`ObservedPropertyAbstract[${this.id()}, '${this.info() || "unknown"}']: subscribeMe: Property new subscriber '${subscriber.id()}'`);
377        this.subscribers_.add(subscriber.id());
378    }
379    /*
380      the inverse function of createOneWaySync or createTwoWaySync
381    */
382    unlinkSuscriber(subscriberId) {
383        this.subscribers_.delete(subscriberId);
384    }
385    notifyHasChanged(newValue, isCrossWindow) {
386        aceConsole.debug(`ObservedPropertyAbstract[${this.id()}, '${this.info() || "unknown"}']: notifyHasChanged, notifying.`);
387        var registry = SubscriberManager.Get();
388        this.subscribers_.forEach((subscribedId) => {
389            var subscriber = registry.get(subscribedId);
390            if (subscriber) {
391                if ('hasChanged' in subscriber) {
392                    subscriber.hasChanged(newValue, isCrossWindow);
393                }
394                if ('propertyHasChanged' in subscriber) {
395                    subscriber.propertyHasChanged(this.info_, isCrossWindow);
396                }
397            }
398            else {
399                aceConsole.error(`ObservedPropertyAbstract[${this.id()}, '${this.info() || "unknown"}']: notifyHasChanged: unknown subscriber ID '${subscribedId}' error!`);
400            }
401        });
402    }
403    notifyPropertyRead() {
404        aceConsole.debug(`ObservedPropertyAbstract[${this.id()}, '${this.info() || "unknown"}']: propertyRead.`);
405        var registry = SubscriberManager.Get();
406        this.subscribers_.forEach((subscribedId) => {
407            var subscriber = registry.get(subscribedId);
408            if (subscriber) {
409                if ('propertyRead' in subscriber) {
410                    subscriber.propertyRead(this.info_);
411                }
412            }
413        });
414    }
415    /*
416    return numebr of subscribers to this property
417    mostly useful for unit testin
418    */
419    numberOfSubscrbers() {
420        return this.subscribers_.size;
421    }
422    /**
423     * factory function for concrete 'object' or 'simple' ObservedProperty object
424     * depending if value is Class object
425     * or simple type (boolean | number | string)
426     * @param value
427     * @param owningView
428     * @param thisPropertyName
429     * @returns either
430     */
431    static CreateObservedObject(value, owningView, thisPropertyName) {
432        return (typeof value === "object") ?
433            new ObservedPropertyObject(value, owningView, thisPropertyName)
434            : new ObservedPropertySimple(value, owningView, thisPropertyName);
435    }
436}
437/**
438 * common bbase class of ObservedPropertyObject and
439 * SyncedObjectPropertyTwoWay
440 * adds the createObjectLink to the ObservedPropertyAbstract base
441 */
442class ObservedPropertyObjectAbstract extends ObservedPropertyAbstract {
443    constructor(owningView, thisPropertyName) {
444        super(owningView, thisPropertyName);
445    }
446}
447/*
448  class that holds an actual property value of type T
449  uses its base class to manage subscribers to this
450  property.
451*/
452class ObservedPropertyObject extends ObservedPropertyObjectAbstract {
453    constructor(value, owningView, propertyName) {
454        super(owningView, propertyName);
455        this.setValueInternal(value);
456    }
457    aboutToBeDeleted(unsubscribeMe) {
458        this.unsubscribeFromOwningProperty();
459        if (unsubscribeMe) {
460            this.unlinkSuscriber(unsubscribeMe.id());
461        }
462        super.aboutToBeDeleted();
463    }
464    // FIXME
465    // notification from ObservedObject value one of its
466    // props has chnaged. Implies the ObservedProperty has changed
467    // Note: this function gets called when in this case:
468    //       thisProp.aObsObj.aProp = 47  a object prop gets changed
469    // It is NOT called when
470    //    thisProp.aObsObj = new ClassA
471    hasChanged(newValue, isCrossWindow) {
472        aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}']: hasChanged`);
473        this.notifyHasChanged(this.wrappedValue_, isCrossWindow);
474    }
475    unsubscribeFromOwningProperty() {
476        if (this.wrappedValue_) {
477            if (this.wrappedValue_ instanceof SubscribaleAbstract) {
478                this.wrappedValue_.removeOwningProperty(this);
479            }
480            else {
481                ObservedObject.removeOwningProperty(this.wrappedValue_, this);
482            }
483        }
484    }
485    /*
486      actually update this.wrappedValue_
487      called needs to do value change check
488      and also notify with this.aboutToChange();
489    */
490    setValueInternal(newValue) {
491        if (typeof newValue !== 'object') {
492            aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}'] new value is NOT an object. Application error. Ignoring set.`);
493            return false;
494        }
495        this.unsubscribeFromOwningProperty();
496        if (ObservedObject.IsObservedObject(newValue)) {
497            aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}'] new value is an ObservedObject already`);
498            ObservedObject.addOwningProperty(newValue, this);
499            this.wrappedValue_ = newValue;
500        }
501        else if (newValue instanceof SubscribaleAbstract) {
502            aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}'] new value is an SubscribaleAbstract, subscribiung to it.`);
503            this.wrappedValue_ = newValue;
504            this.wrappedValue_.addOwningProperty(this);
505        }
506        else {
507            aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}'] new value is an Object, needs to be wrapped in an ObservedObject.`);
508            this.wrappedValue_ = ObservedObject.createNew(newValue, this);
509        }
510        return true;
511    }
512    get() {
513        aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}']: get`);
514        this.notifyPropertyRead();
515        return this.wrappedValue_;
516    }
517    set(newValue, isCrossWindow) {
518        if (this.wrappedValue_ == newValue) {
519            aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}']: set with unchanged value - ignoring.`);
520            return;
521        }
522        aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}']: set, changed`);
523        this.setValueInternal(newValue);
524        this.notifyHasChanged(newValue, isCrossWindow);
525    }
526    /**
527   * These functions are meant for use in connection with the App Stoage and
528   * business logic implementation.
529   * the created Link and Prop will update when 'this' property value
530   * changes.
531   */
532    createLink(subscribeOwner, linkPropName, contentObserver) {
533        return new SynchedPropertyObjectTwoWay(this, subscribeOwner, linkPropName, contentObserver);
534    }
535    createProp(subscribeOwner, linkPropName, contentObserver) {
536        throw new Error("Creating a 'Prop' proerty is unsuppoeted for Object type prperty value.");
537    }
538}
539class ObservedPropertySimpleAbstract extends ObservedPropertyAbstract {
540    constructor(owningView, propertyName) {
541        super(owningView, propertyName);
542    }
543}
544/*
545  class that holds an actual property value of type T
546  uses its base class to manage subscribers to this
547  property.
548*/
549class ObservedPropertySimple extends ObservedPropertySimpleAbstract {
550    constructor(value, owningView, propertyName) {
551        super(owningView, propertyName);
552        if (typeof value === "object") {
553            throw new SyntaxError("ObservedPropertySimple value must not be an object");
554        }
555        this.setValueInternal(value);
556    }
557    aboutToBeDeleted(unsubscribeMe) {
558        if (unsubscribeMe) {
559            this.unlinkSuscriber(unsubscribeMe.id());
560        }
561        super.aboutToBeDeleted();
562    }
563    hasChanged(newValue, isCrossWindow) {
564        aceConsole.debug(`ObservedPropertySimple[${this.id()}, '${this.info() || "unknown"}']: hasChanged`);
565        this.notifyHasChanged(this.wrappedValue_, isCrossWindow);
566    }
567    /*
568      actually update this.wrappedValue_
569      called needs to do value change check
570      and also notify with this.aboutToChange();
571    */
572    setValueInternal(newValue) {
573        aceConsole.debug(`ObservedPropertySimple[${this.id()}, '${this.info() || "unknown"}'] new value is of simple type`);
574        this.wrappedValue_ = newValue;
575    }
576    get() {
577        aceConsole.debug(`ObservedPropertySimple[${this.id()}, '${this.info() || "unknown"}']: get returns '${JSON.stringify(this.wrappedValue_)}' .`);
578        this.notifyPropertyRead();
579        return this.wrappedValue_;
580    }
581    set(newValue, isCrossWindow) {
582        if (this.wrappedValue_ == newValue) {
583            aceConsole.debug(`ObservedPropertySimple[${this.id()}, '${this.info() || "unknown"}']: set with unchanged value - ignoring.`);
584            return;
585        }
586        aceConsole.debug(`ObservedPropertySimple[${this.id()}, '${this.info() || "unknown"}']: set, changed from '${JSON.stringify(this.wrappedValue_)}' to '${JSON.stringify(newValue)}.`);
587        this.setValueInternal(newValue);
588        this.notifyHasChanged(newValue, isCrossWindow);
589    }
590    /**
591   * These functions are meant for use in connection with the App Stoage and
592   * business logic implementation.
593   * the created Link and Prop will update when 'this' property value
594   * changes.
595   */
596    createLink(subscribeOwner, linkPropName, contentObserver) {
597        return new SynchedPropertySimpleTwoWay(this, subscribeOwner, linkPropName, contentObserver);
598    }
599    createProp(subscribeOwner, linkPropName, contentObserver) {
600        return new SynchedPropertySimpleOneWaySubscribing(this, subscribeOwner, linkPropName, contentObserver);
601    }
602}
603class SynchedPropertyObjectTwoWay extends ObservedPropertyObjectAbstract {
604    constructor(linkSource, owningChildView, thisPropertyName, contentStoragelinkedParentProperty) {
605        super(owningChildView, thisPropertyName);
606        this.linkedParentProperty_ = linkSource;
607        // register to the parent property
608        this.linkedParentProperty_.subscribeMe(this);
609        if (contentStoragelinkedParentProperty) {
610            this.contentStoragelinkedParentProperty_ = contentStoragelinkedParentProperty;
611        }
612        // register to the ObservedObject
613        ObservedObject.addOwningProperty(this.getObject(), this);
614    }
615    /*
616    like a destructor, need to call this before deleting
617    the property.
618    */
619    aboutToBeDeleted() {
620        // unregister from parent of this link
621        this.linkedParentProperty_.unlinkSuscriber(this.id());
622        // unregister from the ObservedObject
623        ObservedObject.removeOwningProperty(this.getObject(), this);
624        super.aboutToBeDeleted();
625    }
626    getObject() {
627        this.notifyPropertyRead();
628        return this.linkedParentProperty_.get();
629    }
630    setObject(newValue) {
631        this.linkedParentProperty_.set(newValue);
632    }
633    // this object is subscriber to ObservedObject
634    // will call this cb function when property has changed
635    hasChanged(newValue, isCrossWindow) {
636        aceConsole.debug(`SynchedPropertyObjectTwoWay[${this.id()}, '${this.info() || "unknown"}']: contained ObservedObject hasChanged'.`);
637        this.notifyHasChanged(this.getObject(), isCrossWindow);
638    }
639    // get 'read through` from the ObservedProperty
640    get() {
641        aceConsole.debug(`SynchedPropertyObjectTwoWay[${this.id()}, '${this.info() || "unknown"}']: get`);
642        if (this.contentStoragelinkedParentProperty_) {
643            return this.contentStoragelinkedParentProperty_.get();
644        }
645        return this.getObject();
646    }
647    // set 'writes through` to the ObservedProperty
648    set(newValue, isCrossWindow) {
649        if (this.contentStoragelinkedParentProperty_) {
650            this.contentStoragelinkedParentProperty_.set(newValue, isCrossWindow);
651            return;
652        }
653        if (this.getObject() == newValue) {
654            aceConsole.debug(`SynchedPropertyObjectTwoWay[${this.id()}IP, '${this.info() || "unknown"}']: set with unchanged value '${newValue}'- ignoring.`);
655            return;
656        }
657        aceConsole.debug(`SynchedPropertyObjectTwoWay[${this.id()}, '${this.info() || "unknown"}']: set to newValue: '${newValue}'.`);
658        ObservedObject.removeOwningProperty(this.getObject(), this);
659        this.setObject(newValue);
660        ObservedObject.addOwningProperty(this.getObject(), this);
661        this.notifyHasChanged(newValue, isCrossWindow);
662    }
663    /**
664   * These functions are meant for use in connection with the App Stoage and
665   * business logic implementation.
666   * the created Link and Prop will update when 'this' property value
667   * changes.
668   */
669    createLink(subscribeOwner, linkPropName, contentStoragelinkedParentProperty) {
670        return new SynchedPropertyObjectTwoWay(this, subscribeOwner, linkPropName, contentStoragelinkedParentProperty);
671    }
672    createProp(subscribeOwner, linkPropName, contentStoragelinkedParentProperty) {
673        throw new Error("Creating a 'Prop' proerty is unsuppoeted for Object type prperty value.");
674    }
675}
676class SynchedPropertyNesedObject extends ObservedPropertyObjectAbstract {
677    /**
678     * Construct a Property of a su component that links to a variable of parent view that holds an ObservedObject
679     * example
680     *   this.b.$a with b of type PC and a of type C, or
681     *   this.$b[5] with this.b of type PC and array item b[5] of type C;
682     *
683     * @param subscribeMe
684     * @param propName
685     */
686    constructor(obsObject, owningChildView, propertyName) {
687        super(owningChildView, propertyName);
688        this.obsObject_ = obsObject;
689        // register to the ObservedObject
690        ObservedObject.addOwningProperty(this.obsObject_, this);
691    }
692    /*
693    like a destructor, need to call this before deleting
694    the property.
695    */
696    aboutToBeDeleted() {
697        // unregister from the ObservedObject
698        ObservedObject.removeOwningProperty(this.obsObject_, this);
699        super.aboutToBeDeleted();
700    }
701    // this object is subscriber to ObservedObject
702    // will call this cb function when property has changed
703    hasChanged(newValue, isCrossWindow) {
704        aceConsole.debug(`SynchedPropertyNesedObject[${this.id()}, '${this.info() || "unknown"}']: contained ObservedObject hasChanged'.`);
705        this.notifyHasChanged(this.obsObject_, isCrossWindow);
706    }
707    // get 'read through` from the ObservedProperty
708    get() {
709        aceConsole.debug(`SynchedPropertyNesedObject[${this.id()}, '${this.info() || "unknown"}']: get`);
710        this.notifyPropertyRead();
711        return this.obsObject_;
712    }
713    // set 'writes through` to the ObservedProperty
714    set(newValue, isCrossWindow) {
715        if (this.obsObject_ == newValue) {
716            aceConsole.debug(`SynchedPropertyNesedObject[${this.id()}IP, '${this.info() || "unknown"}']: set with unchanged value '${newValue}'- ignoring.`);
717            return;
718        }
719        aceConsole.debug(`SynchedPropertyNesedObject[${this.id()}, '${this.info() || "unknown"}']: set to newValue: '${newValue}'.`);
720        // unsubscribe from the old value ObservedObject
721        ObservedObject.removeOwningProperty(this.obsObject_, this);
722        this.obsObject_ = newValue;
723        // subscribe to the new value ObservedObject
724        ObservedObject.addOwningProperty(this.obsObject_, this);
725        // notify value change to subscribing View
726        this.notifyHasChanged(this.obsObject_, isCrossWindow);
727    }
728    /**
729   * These functions are meant for use in connection with the App Stoage and
730   * business logic implementation.
731   * the created Link and Prop will update when 'this' property value
732   * changes.
733   */
734    createLink(subscribeOwner, linkPropName, contentObserver) {
735        throw new Error("Method not supported for property linking to a nested objects.");
736    }
737    createProp(subscribeOwner, linkPropName, contentObserver) {
738        throw new Error("Creating a 'Prop' proerty is unsuppoeted for Object type prperty value.");
739    }
740}
741class SynchedPropertySimpleOneWay extends ObservedPropertySimpleAbstract {
742    constructor(value, subscribeMe, info, contentObserver) {
743        super(subscribeMe, info);
744        // TODO prop is only supported for simple types
745        // add a test here that T is a simple type
746        this.wrappedValue_ = value;
747        if (contentObserver) {
748            this.contentStorageLinkedParentProperty_ = contentObserver;
749        }
750    }
751    /*
752      like a destructor, need to call this before deleting
753      the property.
754    */
755    aboutToBeDeleted() {
756        super.aboutToBeDeleted();
757    }
758    // get 'read through` from the ObservedProperty
759    get() {
760        aceConsole.debug(`SynchedPropertySimpleOneWay[${this.id()}, '${this.info() || "unknown"}']: get returns '${this.wrappedValue_}'`);
761        this.notifyPropertyRead();
762        if (this.contentStorageLinkedParentProperty_) {
763            return this.contentStorageLinkedParentProperty_.get();
764        }
765        return this.wrappedValue_;
766    }
767    set(newValue, isCrossWindow) {
768        if (this.contentStorageLinkedParentProperty_) {
769            this.contentStorageLinkedParentProperty_.set(newValue, isCrossWindow);
770        }
771        if (this.wrappedValue_ == newValue) {
772            aceConsole.debug(`SynchedPropertySimpleOneWay[${this.id()}, '${this.info() || "unknown"}']: set with unchanged value '${this.wrappedValue_}'- ignoring.`);
773            return;
774        }
775        aceConsole.debug(`SynchedPropertySimpleOneWay[${this.id()}, '${this.info() || "unknown"}']: set from '${this.wrappedValue_} to '${newValue}'.`);
776        this.wrappedValue_ = newValue;
777        this.notifyHasChanged(newValue, isCrossWindow);
778    }
779    /**
780     * These functions are meant for use in connection with the App Stoage and
781     * business logic implementation.
782     * the created Link and Prop will update when 'this' property value
783     * changes.
784     */
785    createLink(subscribeOwner, linkPropName, contentObserver) {
786        throw new Error("Can not create a 'Link' from a 'Prop' property. ");
787    }
788    createProp(subscribeOwner, linkPropName, contentObserver) {
789        throw new Error("Method not supported, create a SynchedPropertySimpleOneWaySubscribing from, where to create a Prop.");
790    }
791}
792/*
793  This exrension of SynchedPropertySimpleOneWay needs to be used for AppStorage
794  because it needs to be notified about the source property changing
795  ( there is no re-render process as in Views to update the wrappedValue )
796*/
797class SynchedPropertySimpleOneWaySubscribing extends SynchedPropertySimpleOneWay {
798    constructor(linkedProperty, subscribeMe, info, contentObserver) {
799        super(linkedProperty.get(), subscribeMe, info, contentObserver);
800        this.linkedParentProperty_ = linkedProperty;
801        this.linkedParentProperty_.subscribeMe(this);
802    }
803    aboutToBeDeleted() {
804        // unregister from parent of this prop
805        this.linkedParentProperty_.unlinkSuscriber(this.id());
806        super.aboutToBeDeleted();
807    }
808    hasChanged(newValue, isCrossWindow) {
809        aceConsole.debug(`SynchedPropertySimpleOneWaySubscribing[${this.id()}, '${this.info() || "unknown"}']: source property hasChanged'.`);
810        this.set(newValue, isCrossWindow);
811    }
812    /**
813     * These functions are meant for use in connection with the App Stoage and
814     * business logic implementation.
815     * the created Link and Prop will update when 'this' property value
816     * changes.
817     */
818    createLink(subscribeOwner, linkPropName) {
819        throw new Error("Can not create a 'Link' from a 'Prop' property. ");
820    }
821    createProp(subscribeOwner, propPropName, contentObserver) {
822        return new SynchedPropertySimpleOneWaySubscribing(this, subscribeOwner, propPropName, contentObserver);
823    }
824}
825class SynchedPropertySimpleTwoWay extends ObservedPropertySimpleAbstract {
826    constructor(source, owningView, owningViewPropNme, contentObserver) {
827        super(owningView, owningViewPropNme);
828        this.source_ = source;
829        this.source_.subscribeMe(this);
830        if (contentObserver) {
831            this.contentObserver_ = contentObserver;
832        }
833    }
834    /*
835    like a destructor, need to call this before deleting
836    the property.
837  */
838    aboutToBeDeleted() {
839        this.source_.unlinkSuscriber(this.id());
840        this.source_ = undefined;
841        super.aboutToBeDeleted();
842    }
843    // this object is subscriber to  SynchedPropertySimpleTwoWay
844    // will call this cb function when property has changed
845    // a set (newValue) is not done because get reads through for the source_
846    hasChanged(newValue, isCrossWindow) {
847        aceConsole.debug(`SynchedPropertySimpleTwoWay[${this.id()}, '${this.info() || "unknown"}']: hasChanged to '${newValue}'.`);
848        this.notifyHasChanged(newValue, isCrossWindow);
849    }
850    // get 'read through` from the ObservedProperty
851    get() {
852        aceConsole.debug(`SynchedPropertySimpleTwoWay[${this.id()}IP, '${this.info() || "unknown"}']: get`);
853        this.notifyPropertyRead();
854        if (this.contentObserver_) {
855            return this.contentObserver_.get();
856        }
857        return this.source_.get();
858    }
859    // set 'writes through` to the ObservedProperty
860    set(newValue, isCrossWindow) {
861        if (this.contentObserver_) {
862            this.contentObserver_.set(newValue, isCrossWindow);
863            return;
864        }
865        if (this.source_.get() == newValue) {
866            aceConsole.debug(`SynchedPropertySimpleTwoWay[${this.id()}IP, '${this.info() || "unknown"}']: set with unchanged value '${newValue}'- ignoring.`);
867            return;
868        }
869        // aceConsole.error(`SynchedPropertySimpleTwoWay[${this.id()}IP, '${this.info() || "unknown"}']: set to newValue: '${newValue}'.`);
870        // the source_ ObservedProeprty will call: this.hasChanged(newValue);
871        this.notifyHasChanged(newValue, isCrossWindow);
872        return this.source_.set(newValue);
873    }
874    /**
875  * These functions are meant for use in connection with the App Stoage and
876  * business logic implementation.
877  * the created Link and Prop will update when 'this' property value
878  * changes.
879  */
880    createLink(subscribeOwner, linkPropName, contentObserver) {
881        return new SynchedPropertySimpleTwoWay(this, subscribeOwner, linkPropName, contentObserver);
882    }
883    createProp(subscribeOwner, propPropName, contentObserver) {
884        return new SynchedPropertySimpleOneWaySubscribing(this, subscribeOwner, propPropName, contentObserver);
885    }
886}
887class ContentStorage {
888    constructor() {
889        this.storage_ = new Map();
890    }
891    has(propName) {
892        return this.storage_.has(propName);
893    }
894    get(key) {
895        var p = this.storage_.get(key);
896        return (p) ? p.get() : undefined;
897    }
898    set(propName, newValue) {
899        var p = this.storage_.get(propName);
900        if (p) {
901            p.set(newValue);
902            return true;
903        }
904        else {
905            return false;
906        }
907    }
908    setOrCreate(propName, newValue) {
909        var p = this.storage_.get(propName);
910        if (p) {
911            aceConsole.log(`ContentStorage.setOrCreate(${propName}, ${newValue}) update existing property`);
912            p.set(newValue);
913        }
914        else {
915            aceConsole.log(`ContentStorage.setOrCreate(${propName}, ${newValue}) create new entry and set value`);
916            const newProp = (typeof newValue === "object") ?
917                new ObservedPropertyObject(newValue, undefined, propName)
918                : new ObservedPropertySimple(newValue, undefined, propName);
919            this.storage_.set(propName, newProp);
920        }
921    }
922    delete(propName) {
923        var p = this.storage_.get(propName);
924        if (p) {
925            if (p.numberOfSubscrbers()) {
926                aceConsole.error(`Attempt to delete property ${propName} that has ${p.numberOfSubscrbers()} subscribers. Subscribers need to unsubscribe before prop deletion.`);
927                return false;
928            }
929            p.aboutToBeDeleted();
930            this.storage_.delete(propName);
931            return true;
932        }
933        else {
934            aceConsole.warn(`Attempt to delete unknown property ${propName}.`);
935            return false;
936        }
937    }
938    keys() {
939        return this.storage_.keys();
940    }
941    size() {
942        return this.storage_.size;
943    }
944    aboutToBeDeleted() {
945        return this.clear();
946    }
947    numberOfSubscribersTo(propName) {
948        var p = this.storage_.get(propName);
949        if (p) {
950            return p.numberOfSubscrbers();
951        }
952        return undefined;
953    }
954    subscribeToChangesOf(propName, subscriber) {
955        var p = this.storage_.get(propName);
956        if (p) {
957            p.subscribeMe(subscriber);
958            return true;
959        }
960        return false;
961    }
962    unsubscribeFromChangesOf(propName, subscriberId) {
963        var p = this.storage_.get(propName);
964        if (p) {
965            p.unlinkSuscriber(subscriberId);
966            return true;
967        }
968        return false;
969    }
970    isMutable(key) {
971        return true;
972    }
973    clear() {
974        for (let propName of this.keys()) {
975            var p = this.storage_.get(propName);
976            if (p.numberOfSubscrbers()) {
977                aceConsole.error(`ContentStorage.deleteAll: Attempt to delete property ${propName} that has ${p.numberOfSubscrbers()} subscribers. Subscribers need to unsubscribe before prop deletion.`);
978                return false;
979            }
980        }
981        for (let propName of this.keys()) {
982            var p = this.storage_.get(propName);
983            p.aboutToBeDeleted();
984        }
985        aceConsole.debug(`ContentStorage.deleteAll: success`);
986    }
987    link(propName, linkUser) {
988        var p = this.storage_.get(propName);
989        return (p) ? p.createLink(linkUser, propName) : undefined;
990    }
991    setAndLink(propName, defaultValue, linkUser) {
992        var p = this.storage_.get(propName);
993        if (!p) {
994            this.setOrCreate(propName, defaultValue);
995        }
996        return this.link(propName, linkUser);
997    }
998    prop(propName, propUser) {
999        var p = this.storage_.get(propName);
1000        return (p) ? p.createProp(propUser, propName) : undefined;
1001    }
1002    setAndProp(propName, defaultValue, propUser) {
1003        var p = this.storage_.get(propName);
1004        if (!p) {
1005            if (typeof defaultValue === "boolean" ||
1006                typeof defaultValue === "number" || typeof defaultValue === "string") {
1007                this.setOrCreate(propName, defaultValue);
1008            }
1009            else {
1010                return undefined;
1011            }
1012        }
1013        return this.prop(propName, propUser);
1014    }
1015}
1016
1017class AppStorage {
1018    constructor() {
1019        this.storage_ = new Map();
1020    }
1021    // FIXME: Perhaps "GetInstance" would be better name for this
1022    // static Get(): AppStorage { return AppStorage.Instance_; }
1023    static GetOrCreate() {
1024        if (!AppStorage.Instance_) {
1025            AppStorage.Instance_ = new AppStorage();
1026        }
1027        return AppStorage.Instance_;
1028    }
1029    static Link(key) {
1030        return AppStorage.GetOrCreate().link(key);
1031    }
1032    static SetAndLink(key, defaultValue) {
1033        return AppStorage.GetOrCreate().setAndLink(key, defaultValue);
1034    }
1035    static Prop(key) {
1036        return AppStorage.GetOrCreate().prop(key);
1037    }
1038    static SetAndProp(key, defaultValue) {
1039        return AppStorage.GetOrCreate().setAndProp(key, defaultValue);
1040    }
1041    static Has(key) {
1042        return AppStorage.GetOrCreate().has(key);
1043    }
1044    static Get(key) {
1045        return AppStorage.GetOrCreate().get(key);
1046    }
1047    static Set(key, newValue) {
1048        return AppStorage.GetOrCreate().set(key, newValue);
1049    }
1050    // FIXME(cvetan): No mechanism to create "immutable" properties
1051    static SetOrCreate(key, newValue) {
1052        AppStorage.GetOrCreate().setOrCreate(key, newValue);
1053    }
1054    static Delete(key) {
1055        return AppStorage.GetOrCreate().delete(key);
1056    }
1057    static Keys() {
1058        return AppStorage.GetOrCreate().keys();
1059    }
1060    static Size() {
1061        return AppStorage.GetOrCreate().size();
1062    }
1063    static Clear() {
1064        return AppStorage.GetOrCreate().clear();
1065    }
1066    static AboutToBeDeleted() {
1067        AppStorage.GetOrCreate().aboutToBeDeleted();
1068    }
1069    static NumberOfSubscribersTo(propName) {
1070        return AppStorage.GetOrCreate().numberOfSubscrbersTo(propName);
1071    }
1072    static SubscribeToChangesOf(propName, subscriber) {
1073        return AppStorage.GetOrCreate().subscribeToChangesOf(propName, subscriber);
1074    }
1075    static UnsubscribeFromChangesOf(propName, subscriberId) {
1076        return AppStorage.GetOrCreate().unsubscribeFromChangesOf(propName, subscriberId);
1077    }
1078    static IsMutable(key) {
1079        // FIXME(cvetan): No mechanism for immutable/mutable properties
1080        return true;
1081    }
1082    /**
1083     * App should call this method to order close down app storage before
1084     * terminating itself.
1085     * Before deleting a prop from app storage all its subscribers need to
1086     * unsubscribe from the property.
1087     *
1088     * @returns true if all properties could be removed from app storage
1089     */
1090    aboutToBeDeleted() {
1091        return this.clear();
1092    }
1093    get(propName) {
1094        var p = this.storage_.get(propName);
1095        return (p) ? p.get() : undefined;
1096    }
1097    set(propName, newValue) {
1098        var p = this.storage_.get(propName);
1099        if (p) {
1100            p.set(newValue);
1101            return true;
1102        }
1103        else {
1104            return false;
1105        }
1106    }
1107    setOrCreate(propName, newValue) {
1108        var p = this.storage_.get(propName);
1109        if (p) {
1110            aceConsole.log(`AppStorage.setOrCreate(${propName}, ${newValue}) update existing property`);
1111            p.set(newValue);
1112        }
1113        else {
1114            aceConsole.log(`AppStorage.setOrCreate(${propName}, ${newValue}) create new entry and set value`);
1115            const newProp = (typeof newValue === "object") ?
1116                new ObservedPropertyObject(newValue, undefined, propName)
1117                : new ObservedPropertySimple(newValue, undefined, propName);
1118            this.storage_.set(propName, newProp);
1119        }
1120    }
1121    has(propName) {
1122        aceConsole.log(`AppStorage.has(${propName})`);
1123        return this.storage_.has(propName);
1124    }
1125    /**
1126     * Delete poperty from AppStorage
1127     * must only use with caution:
1128     * Before deleting a prop from app storage all its subscribers need to
1129     * unsubscribe from the property.
1130     * This method fails and returns false if given property still has subscribers
1131     * Another reason for failing is unkmown property.
1132     *
1133     * @param propName
1134     * @returns false if method failed
1135     */
1136    delete(propName) {
1137        var p = this.storage_.get(propName);
1138        if (p) {
1139            if (p.numberOfSubscrbers()) {
1140                aceConsole.error(`Attempt to delete property ${propName} that has ${p.numberOfSubscrbers()} subscribers. Subscribers need to unsubscribe before prop deletion.`);
1141                return false;
1142            }
1143            p.aboutToBeDeleted();
1144            this.storage_.delete(propName);
1145            return true;
1146        }
1147        else {
1148            aceConsole.warn(`Attempt to delete unknown property ${propName}.`);
1149            return false;
1150        }
1151    }
1152    /**
1153     * delete all properties from the AppStorage
1154     * precondition is that there are no subscribers anymore
1155     * method returns false and deletes no poperties if there is any property
1156     * that still has subscribers
1157     */
1158    clear() {
1159        for (let propName of this.keys()) {
1160            var p = this.storage_.get(propName);
1161            if (p.numberOfSubscrbers()) {
1162                aceConsole.error(`AppStorage.deleteAll: Attempt to delete property ${propName} that has ${p.numberOfSubscrbers()} subscribers. Subscribers need to unsubscribe before prop deletion.`);
1163                return false;
1164            }
1165        }
1166        for (let propName of this.keys()) {
1167            var p = this.storage_.get(propName);
1168            p.aboutToBeDeleted();
1169        }
1170        aceConsole.debug(`AppStorage.deleteAll: success`);
1171    }
1172    keys() {
1173        return this.storage_.keys();
1174    }
1175    size() {
1176        return this.storage_.size;
1177    }
1178    link(propName, linkUser, contentObserver) {
1179        var p = this.storage_.get(propName);
1180        return (p) ? p.createLink(linkUser, propName, contentObserver) : undefined;
1181    }
1182    setAndLink(propName, defaultValue, linkUser) {
1183        var p = this.storage_.get(propName);
1184        if (!p) {
1185            this.setOrCreate(propName, defaultValue);
1186        }
1187        if (linkUser && (linkUser instanceof View) && linkUser.getContentStorage()) {
1188            var contentObserver = linkUser.getContentStorage().setAndLink(propName, defaultValue, linkUser);
1189            return this.link(propName, linkUser, contentObserver);
1190        }
1191        return this.link(propName, linkUser);
1192    }
1193    prop(propName, propUser, contentObserver) {
1194        var p = this.storage_.get(propName);
1195        return (p) ? p.createProp(propUser, propName, contentObserver) : undefined;
1196    }
1197    setAndProp(propName, defaultValue, propUser) {
1198        var p = this.storage_.get(propName);
1199        if (!p) {
1200            if (typeof defaultValue === "boolean" ||
1201                typeof defaultValue === "number" || typeof defaultValue === "string") {
1202                this.setOrCreate(propName, defaultValue);
1203            }
1204            else {
1205                return undefined;
1206            }
1207        }
1208        if (propUser && propUser.getContentStorage()) {
1209            var contentObserver = propUser.getContentStorage().setAndProp(propName, defaultValue, propUser);
1210            return this.prop(propName, propUser, contentObserver);
1211        }
1212        return this.prop(propName, propUser);
1213    }
1214    subscribeToChangesOf(propName, subscriber) {
1215        var p = this.storage_.get(propName);
1216        if (p) {
1217            p.subscribeMe(subscriber);
1218            return true;
1219        }
1220        return false;
1221    }
1222    unsubscribeFromChangesOf(propName, subscriberId) {
1223        var p = this.storage_.get(propName);
1224        if (p) {
1225            p.unlinkSuscriber(subscriberId);
1226            return true;
1227        }
1228        return false;
1229    }
1230    /*
1231    return number of subscribers to this property
1232    mostly useful for unit testin
1233    */
1234    numberOfSubscrbersTo(propName) {
1235        var p = this.storage_.get(propName);
1236        if (p) {
1237            return p.numberOfSubscrbers();
1238        }
1239        return undefined;
1240    }
1241    /*
1242    cross window notify
1243    */
1244    crossWindowNotify(propName, newValue) {
1245        var p = this.storage_.get(propName);
1246        try {
1247            newValue = JSON.parse(newValue);
1248        }
1249        catch (error) {
1250            aceConsole.error(`PersistentStorage: convert for ${propName} has error: ` + error.toString());
1251        }
1252        if (p) {
1253            aceConsole.debug(`crossWindowNotify(${propName}, ${newValue}) update existing property`);
1254            p.set(newValue, true);
1255        }
1256        else {
1257            aceConsole.debug(`crossWindowNotify(${propName}, ${newValue}) create new entry and set value`);
1258            const newProp = (typeof newValue === "object") ?
1259                new ObservedPropertyObject(newValue, undefined, propName)
1260                : new ObservedPropertySimple(newValue, undefined, propName);
1261            this.storage_.set(propName, newProp);
1262        }
1263    }
1264}
1265AppStorage.Instance_ = undefined;