<commit_before>define(["sprd/manager/IBasketManager", "flow", "sprd/model/Basket", "xaml!sprd/data/SprdApiDataSource",
        "js/data/LocalStorage", "js/data/Entity"],
    function (IBasketManager, flow, Basket, SprdApiDataSource, LocalStorage, Entity) {

    /***
     * @summary A BasketManager to interact with the Spreadshirt Basket API
     *
     * @see http://developer.spreadshirt.net/display/API/Basket+Resources
     */
    return IBasketManager.inherit('sprd.manager.ApiBasketManager', {

        defaults: {
            /***
             * the basket model
             * @type {sprd.model.Basket}
             */
            basket: null,

            /***
             * the id of the api basket which should try to be loaded.
             * If the id isn't set the id is retrieved from the local storage or generated by the api
             * after the basket has been created successfully. The id is written back to this property.
             */
            apiBasketId: null,

            /***
             * the shop used for creating a new basket
             * @type {sprd.model.Shop}
             */
            shop: null,

            /***
             * continueShopping link is used in checkout as link for continue shopping button.
             * It will be automatically added to the element added to basket
             * @type String
             */
            continueShoppingLink: null,

            /***
             * edit link is the link displayed in checkout for editing the basket item.
             * It will be automatically added to the element added to the basked.
             *
             * The following values are replaced
             *
             *  + ${productId} - with the current productId
             *
             * @type String
             */
            editBasketItemLinkTemplate: null,

            /***
             * the origin id used for basked items
             * @type {Number|String}
             */
            originId: null
        },

        events: [
        /***
         * the basketChanged event is dispatched after the basket has been modified e.g. added, removed or updated an
         * BasketItem
         */
            "on:basketChanged",
            "on:basketUpdated",
            "on:basketUpdating"
        ],

        inject: {
            api: SprdApiDataSource,
            localStorage: LocalStorage
        },

        /**
         * Adds an element to the basket without saving it
         * @param element
         * @param quantity
         * @param callback
         */
        addElementToBasket: function (element, quantity, callback) {

            if (this.$.basket) {
                var basketItem = this.$.basket.addElement(element, quantity);
                element = basketItem.$.element;

                var originId = this.$.originId;
                if (originId) {
                    basketItem.set('origin', new Entity({
                        id: originId
                    }));
                }

                var continueShoppingLink = this.$.continueShoppingLink;

                if (continueShoppingLink) {
                    element.set("continueShoppingLink", continueShoppingLink);
                }

                var editBasketItemLinkTemplate = this.$.editBasketItemLinkTemplate;
                if (editBasketItemLinkTemplate) {
                    element.set("editLink", editBasketItemLinkTemplate.replace("$productId", element.get("item.id")));
                }
            }

            callback && callback();
        },

        _triggerBasketChanged: function () {
            this.trigger("on:basketChanged", this.$.basket, this);
        },

        _triggerBasketUpdated: function () {
            this.trigger("on:basketUpdated", this.$.basket, this);
        },

        _triggerBasketUpdating: function () {
            this.trigger("on:basketUpdating", this.$.basket, this);
        },

        _initBasket: function (callback) {

            var api = this.$.api,
                localStorage = this.$.localStorage,
                basketId = this.$.apiBasketId,
                basket,
                self = this;

            basketId = basketId || localStorage.getItem("basketId");

            basket = api.createEntity(Basket, basketId);

            this.set({
                basket: basket,
                apiBasketId: basketId
            });

            var basketSaveCallback = function (err) {
                if (!err) {
                    self.set("apiBasketId", basketId);
                    try {
                        self.$.localStorage.setItem("basketId", basket.$.id);
                        self._triggerBasketChanged();
                        self.fetchBasketDiscounts(callback);
                    } catch (e) {
                        callback && callback(e);
                    }

                } else {
                    console.warn(err);
                    callback && callback(err);
                }

            };

            if (basket.isNew()) {

                basket.set({
                    shop: this.$.shop,
                    currency: this.get("shop.currency")
                });

                basket.save(null, basketSaveCallback);
            } else {
                flow()
                    .seq(function (cb) {
                        basket.fetch({
                            noCache: true,
                            fetchSubModels: ["currency"]
                        }, cb);
                    })
                    .seq(function (cb) {
                        self.fetchBasketDiscounts(cb);
                    })
                    .exec(function (err) {
                        if (err) {
                            // something went wrong
                            basket.set('id', undefined);
                            basket.save(null, basketSaveCallback);
                            console.warn(err);
                        } else {

                            self.set("shop", basket.$.shop);

                            flow()
                                .parEach(basket.$.basketItems.toArray(), function (item, cb) {
                                    item.$.element.getProduct().fetch({
                                        fetchSubModels: ["productType"]
                                    }, cb);
                                })
                                .exec(callback);
                        }
                    });
            }
        },

        fetchBasketDiscounts: function (cb) {
            var basket = this.$.basket;
            if (basket.$.discounts && basket.$.discounts.size()) {
                var discount = basket.$.discounts.at(0);
                if (discount && discount.$.discountScale) {
                    discount.$.discountScale.fetch(null, cb);
                } else {
                    cb();
                }
            } else {
                cb();
            }
        },

        /**
         *
         * @param {sprd.model.BasketItem} basketItem
         */
        saveBasketItem: function (basketItem) {
            var self = this;
            this.$itemSaveTimeout && clearTimeout(this.$itemSaveTimeout);

            this.$itemSaveTimeout = setTimeout(function () {
                basketItem.save(null, function () {
                    self.$.basket.fetch({noCache: true});
                });
                self._triggerBasketChanged();
            }, 300);
        },

        /**
         *
         * @param {sprd.model.BasketItem} basketItem
         */
        removeBasketItem: function (basketItem) {
            this.$.basket.$.basketItems.remove(basketItem);
            this.saveBasketDebounced();
        },

        saveBasketDebounced: function () {
            this._triggerBasketUpdating();
            this._debounceFunctionCall(function () {
                this.saveBasket();
            }, "saveBasketCall", 700, this);
        },

        saveBasket: function (callback) {
            if (!this.$savingBasket) {
                this._triggerBasketUpdating();
                this.$savingBasket = true;
                this.$callSaveBasketAgain = false;
                this.$saveCallbacks = this.$saveCallbacks || [];
                if (callback) {
                    this.$saveCallbacks.push(callback);
                }
                var self = this,
                    basket = this.$.basket;

                function callCallbacks(err, basket) {
                    var cb;
                    while (self.$saveCallbacks && self.$saveCallbacks.length) {
                        cb = self.$saveCallbacks.shift();
                        cb && cb(err, basket);
                    }
                }

                flow()
                    .seq(function (cb) {
                        basket.save(null, cb);
                    })
                    .seq(function (cb) {
                        self._triggerBasketChanged();

                        basket.fetch({noCache: true}, cb);
                    })
                    .seq(function(cb){
                        self.fetchBasketDiscounts(cb);
                    })
                    .exec(function (err) {
                        if (self.$basketChanged) {
                            self.$basketChanged = false;
                            self.$savingBasket = false;
                            self.saveBasket();
                        } else {
                            callCallbacks(err, basket);
                            self.$savingBasket = false;
                            self._triggerBasketUpdated();
                        }
                    });
            } else {
                if (callback) {
                    this.$saveCallbacks.push(callback);
                }
                this.$basketChanged = true;
            }
        }
    });

});<commit_msg>Added error handling for not existing Article.<commit_after>define(["sprd/manager/IBasketManager", "flow", "sprd/model/Basket", "xaml!sprd/data/SprdApiDataSource",
        "js/data/LocalStorage", "js/data/Entity"],
    function (IBasketManager, flow, Basket, SprdApiDataSource, LocalStorage, Entity) {

    /***
     * @summary A BasketManager to interact with the Spreadshirt Basket API
     *
     * @see http://developer.spreadshirt.net/display/API/Basket+Resources
     */
    return IBasketManager.inherit('sprd.manager.ApiBasketManager', {

        defaults: {
            /***
             * the basket model
             * @type {sprd.model.Basket}
             */
            basket: null,

            /***
             * the id of the api basket which should try to be loaded.
             * If the id isn't set the id is retrieved from the local storage or generated by the api
             * after the basket has been created successfully. The id is written back to this property.
             */
            apiBasketId: null,

            /***
             * the shop used for creating a new basket
             * @type {sprd.model.Shop}
             */
            shop: null,

            /***
             * continueShopping link is used in checkout as link for continue shopping button.
             * It will be automatically added to the element added to basket
             * @type String
             */
            continueShoppingLink: null,

            /***
             * edit link is the link displayed in checkout for editing the basket item.
             * It will be automatically added to the element added to the basked.
             *
             * The following values are replaced
             *
             *  + ${productId} - with the current productId
             *
             * @type String
             */
            editBasketItemLinkTemplate: null,

            /***
             * the origin id used for basked items
             * @type {Number|String}
             */
            originId: null
        },

        events: [
        /***
         * the basketChanged event is dispatched after the basket has been modified e.g. added, removed or updated an
         * BasketItem
         */
            "on:basketChanged",
            "on:basketUpdated",
            "on:basketUpdating"
        ],

        inject: {
            api: SprdApiDataSource,
            localStorage: LocalStorage
        },

        /**
         * Adds an element to the basket without saving it
         * @param element
         * @param quantity
         * @param callback
         */
        addElementToBasket: function (element, quantity, callback) {

            if (this.$.basket) {
                var basketItem = this.$.basket.addElement(element, quantity);
                element = basketItem.$.element;

                var originId = this.$.originId;
                if (originId) {
                    basketItem.set('origin', new Entity({
                        id: originId
                    }));
                }

                var continueShoppingLink = this.$.continueShoppingLink;

                if (continueShoppingLink) {
                    element.set("continueShoppingLink", continueShoppingLink);
                }

                var editBasketItemLinkTemplate = this.$.editBasketItemLinkTemplate;
                if (editBasketItemLinkTemplate) {
                    element.set("editLink", editBasketItemLinkTemplate.replace("$productId", element.get("item.id")));
                }
            }

            callback && callback();
        },

        _triggerBasketChanged: function () {
            this.trigger("on:basketChanged", this.$.basket, this);
        },

        _triggerBasketUpdated: function () {
            this.trigger("on:basketUpdated", this.$.basket, this);
        },

        _triggerBasketUpdating: function () {
            this.trigger("on:basketUpdating", this.$.basket, this);
        },

        _initBasket: function (callback) {

            var api = this.$.api,
                localStorage = this.$.localStorage,
                basketId = this.$.apiBasketId,
                basket,
                self = this;

            basketId = basketId || localStorage.getItem("basketId");

            basket = api.createEntity(Basket, basketId);

            this.set({
                basket: basket,
                apiBasketId: basketId
            });

            var basketSaveCallback = function (err) {
                if (!err) {
                    self.set("apiBasketId", basketId);
                    try {
                        self.$.localStorage.setItem("basketId", basket.$.id);
                        self._triggerBasketChanged();
                        self.fetchBasketDiscounts(callback);
                    } catch (e) {
                        callback && callback(e);
                    }

                } else {
                    console.warn(err);
                    callback && callback(err);
                }

            };

            if (basket.isNew()) {

                basket.set({
                    shop: this.$.shop,
                    currency: this.get("shop.currency")
                });

                basket.save(null, basketSaveCallback);
            } else {
                flow()
                    .seq(function (cb) {
                        basket.fetch({
                            noCache: true,
                            fetchSubModels: ["currency"]
                        }, cb);
                    })
                    .seq(function (cb) {
                        self.fetchBasketDiscounts(cb);
                    })
                    .exec(function (err) {
                        if (err) {
                            // something went wrong
                            basket.set('id', undefined);
                            basket.save(null, basketSaveCallback);
                            console.warn(err);
                        } else {

                            self.set("shop", basket.$.shop);

                            flow()
                                .parEach(basket.$.basketItems.toArray(), function (item, cb) {
                                    flow()
                                        .seq(function (cb) {
                                            item.$.element.init(cb);
                                        })
                                        .seq(function (cb) {
                                            item.$.element.getProduct().fetch({
                                                fetchSubModels: ["productType"]
                                            }, cb);
                                        })
                                        .exec(function (err) {
                                            if (err) {
                                                basket.$.basketItems.remove(item);
                                            }

                                            cb();
                                        });
                                })
                                .exec(callback);
                        }
                    });
            }
        },

        fetchBasketDiscounts: function (cb) {
            var basket = this.$.basket;
            if (basket.$.discounts && basket.$.discounts.size()) {
                var discount = basket.$.discounts.at(0);
                if (discount && discount.$.discountScale) {
                    discount.$.discountScale.fetch(null, cb);
                } else {
                    cb();
                }
            } else {
                cb();
            }
        },

        /**
         *
         * @param {sprd.model.BasketItem} basketItem
         */
        saveBasketItem: function (basketItem) {
            var self = this;
            this.$itemSaveTimeout && clearTimeout(this.$itemSaveTimeout);

            this.$itemSaveTimeout = setTimeout(function () {
                basketItem.save(null, function () {
                    self.$.basket.fetch({noCache: true});
                });
                self._triggerBasketChanged();
            }, 300);
        },

        /**
         *
         * @param {sprd.model.BasketItem} basketItem
         */
        removeBasketItem: function (basketItem) {
            this.$.basket.$.basketItems.remove(basketItem);
            this.saveBasketDebounced();
        },

        saveBasketDebounced: function () {
            this._triggerBasketUpdating();
            this._debounceFunctionCall(function () {
                this.saveBasket();
            }, "saveBasketCall", 700, this);
        },

        saveBasket: function (callback) {
            if (!this.$savingBasket) {
                this._triggerBasketUpdating();
                this.$savingBasket = true;
                this.$callSaveBasketAgain = false;
                this.$saveCallbacks = this.$saveCallbacks || [];
                if (callback) {
                    this.$saveCallbacks.push(callback);
                }
                var self = this,
                    basket = this.$.basket;

                function callCallbacks(err, basket) {
                    var cb;
                    while (self.$saveCallbacks && self.$saveCallbacks.length) {
                        cb = self.$saveCallbacks.shift();
                        cb && cb(err, basket);
                    }
                }

                flow()
                    .seq(function (cb) {
                        basket.save(null, cb);
                    })
                    .seq(function (cb) {
                        self._triggerBasketChanged();

                        basket.fetch({noCache: true}, cb);
                    })
                    .seq(function(cb){
                        self.fetchBasketDiscounts(cb);
                    })
                    .exec(function (err) {
                        if (self.$basketChanged) {
                            self.$basketChanged = false;
                            self.$savingBasket = false;
                            self.saveBasket();
                        } else {
                            callCallbacks(err, basket);
                            self.$savingBasket = false;
                            self._triggerBasketUpdated();
                        }
                    });
            } else {
                if (callback) {
                    this.$saveCallbacks.push(callback);
                }
                this.$basketChanged = true;
            }
        }
    });

});<|endoftext|>


<commit_before>define(["sprd/manager/IBasketManager", "flow", "sprd/model/Basket", "xaml!sprd/data/SprdApiDataSource", "js/data/LocalStorage", "js/data/Entity"], function (IBasketManager, flow, Basket, SprdApiDataSource, LocalStorage, Entity) {

    /***
     * @summary A BasketManager to interact with the Spreadshirt Basket API
     *
     * @see http://developer.spreadshirt.net/display/API/Basket+Resources
     */
    return IBasketManager.inherit('sprd.manager.ApiBasketManager', {

        defaults: {
            /***
             * the basket model
             * @type {sprd.model.Basket}
             */
            basket: null,

            /***
             * the id of the api basket which should try to be loaded.
             * If the id isn't set the id is retrieved from the local storage or generated by the api
             * after the basket has been created successfully. The id is written back to this property.
             */
            apiBasketId: null,

            /***
             * the shop used for creating a new basket
             * @type {sprd.model.Shop}
             */
            shop: null,

            /***
             * continueShopping link is used in checkout as link for continue shopping button.
             * It will be automatically added to the element added to basket
             * @type String
             */
            continueShoppingLink: null,

            /***
             * edit link is the link displayed in checkout for editing the basket item.
             * It will be automatically added to the element added to the basked.
             *
             * The following values are replaced
             *
             *  + ${productId} - with the current productId
             *
             * @type String
             */
            editBasketItemLinkTemplate: null,

            /***
             * the origin id used for basked items
             * @type {Number|String}
             */
            originId: null
        },

        events: [
        /***
         * the basketChanged event is dispatched after the basket has been modified e.g. added, removed or updated an
         * BasketItem
         */
            "on:basketChanged",
            "on:basketUpdated"
        ],

        inject: {
            api: SprdApiDataSource,
            localStorage: LocalStorage
        },

        /**
         * Adds an element to the basket without savoing it
         * @param element
         * @param quantity
         */
        addElementToBasket: function (element, quantity) {

            if (this.$.basket) {
                var basketItem = this.$.basket.addElement(element, quantity);
                element = basketItem.$.element;

                var originId = this.$.originId;
                if (originId) {
                    basketItem.set('origin', new Entity({
                        id: originId
                    }));
                }

                var continueShoppingLink = this.$.continueShoppingLink;

                if (continueShoppingLink) {
                    element.set("continueShoppingLink", continueShoppingLink)
                }

                var editBasketItemLinkTemplate = this.$.editBasketItemLinkTemplate;
                if (editBasketItemLinkTemplate) {
                    element.set("editLink", editBasketItemLinkTemplate.replace("$productId", element.get("item.id")))
                }
            }
        },

        _triggerBasketChanged: function () {
            this.trigger("on:basketChanged", this.$.basket, this);
        },

        _triggerBasketUpdated: function () {
            this.trigger("on:basketUpdated", this.$.basket, this);
        },

        _initBasket: function (callback) {

            var api = this.$.api,
                localStorage = this.$.localStorage,
                basketId = this.$.apiBasketId,
                basket,
                self = this;

            basketId = basketId || localStorage.getItem("basketId");

            basket = api.createEntity(Basket, basketId);

            this.set({
                basket: basket,
                apiBasketId: basketId
            });

            var basketSaveCallback = function (err) {
                if (!err) {
                    self.set("apiBasketId", basketId);
                    self.$.localStorage.setItem("basketId", basket.$.id);
                    self._triggerBasketChanged();
                    fetchBasketDiscountScales(basket, callback);
                } else {
                    console.warn(err);
                    callback(err);
                }

            };

            var fetchBasketDiscountScales = function (basket, cb) {
                if (basket.$.discounts && basket.$.discounts.size()) {
                    var discount = basket.$.discounts.at(0);
                    if (discount && discount.$.discountScale) {
                        discount.$.discountScale.fetch(null, cb);
                    } else {
                        cb();
                    }
                } else {
                    cb();
                }
            };

            if (basket.isNew()) {

                basket.set({
                    shop: this.$.shop,
                    currency: this.get("shop.currency")
                });

                basket.save(null, basketSaveCallback);
            } else {
                flow()
                    .seq(function (cb) {
                        basket.fetch({
                            noCache: true,
                            fetchSubModels: ["currency"]
                        }, cb)
                    })
                    .seq(function (cb) {
                        fetchBasketDiscountScales(basket, cb);
                    })
                    .exec(function (err) {
                        if (err) {
                            // something went wrong
                            basket.set('id', undefined);
                            basket.save(null, basketSaveCallback);
                            console.warn(err)
                        } else {

                            self.set("shop", basket.$.shop);

                            flow()
                                .parEach(basket.$.basketItems.toArray(), function (item, cb) {
                                    item.$.element.getProduct().fetch({
                                        fetchSubModels: ["productType"]
                                    }, cb);
                                })
                                .exec(callback);
                        }
                    });
            }
        },

        /**
         *
         * @param {sprd.model.BasketItem} basketItem
         */
        saveBasketItem: function (basketItem) {
            var self = this;
            this.$itemSaveTimeout && clearTimeout(this.$itemSaveTimeout);

            this.$itemSaveTimeout = setTimeout(function () {
                basketItem.save(null, function () {
                    self.$.basket.fetch({noCache: true});
                });
                self._triggerBasketChanged();
            }, 300);
        },

        /**
         *
         * @param {sprd.model.BasketItem} basketItem
         */
        removeBasketItem: function (basketItem) {
            this.$.basket.$.basketItems.remove(basketItem);
            this.saveBasket();
        },

        saveBasket: function () {
            this._debounceFunctionCall(this._saveBasket, "saveBasketCall", 700, this);
        },

        _saveBasket: function () {
            if (!this.$savingBasket) {
                var self = this;
                this.$callSaveBasketAgain = false;
                this.$savingBasket = true;
                this.$.basket.save(null, function (err, basket) {
                    self._triggerBasketChanged();
                    if (!err) {
                        basket.fetch({noCache: true}, function (err) {
                            self.$savingBasket = false;
                            if (!err) {
                                if (self.$callSaveBasketAgain) {
                                    self.saveBasket();
                                } else {
                                    self._triggerBasketUpdated();
                                }
                            }
                        });
                    } else {
                        self.$savingBasket = false;
                    }
                });
            } else {
                this.$callSaveBasketAgain = true;
            }
        }
    });

});<commit_msg>DEV-53992 - Add callback to saveBasket<commit_after>define(["sprd/manager/IBasketManager", "flow", "sprd/model/Basket", "xaml!sprd/data/SprdApiDataSource", "js/data/LocalStorage", "js/data/Entity"], function (IBasketManager, flow, Basket, SprdApiDataSource, LocalStorage, Entity) {

    /***
     * @summary A BasketManager to interact with the Spreadshirt Basket API
     *
     * @see http://developer.spreadshirt.net/display/API/Basket+Resources
     */
    return IBasketManager.inherit('sprd.manager.ApiBasketManager', {

        defaults: {
            /***
             * the basket model
             * @type {sprd.model.Basket}
             */
            basket: null,

            /***
             * the id of the api basket which should try to be loaded.
             * If the id isn't set the id is retrieved from the local storage or generated by the api
             * after the basket has been created successfully. The id is written back to this property.
             */
            apiBasketId: null,

            /***
             * the shop used for creating a new basket
             * @type {sprd.model.Shop}
             */
            shop: null,

            /***
             * continueShopping link is used in checkout as link for continue shopping button.
             * It will be automatically added to the element added to basket
             * @type String
             */
            continueShoppingLink: null,

            /***
             * edit link is the link displayed in checkout for editing the basket item.
             * It will be automatically added to the element added to the basked.
             *
             * The following values are replaced
             *
             *  + ${productId} - with the current productId
             *
             * @type String
             */
            editBasketItemLinkTemplate: null,

            /***
             * the origin id used for basked items
             * @type {Number|String}
             */
            originId: null
        },

        events: [
        /***
         * the basketChanged event is dispatched after the basket has been modified e.g. added, removed or updated an
         * BasketItem
         */
            "on:basketChanged",
            "on:basketUpdated"
        ],

        inject: {
            api: SprdApiDataSource,
            localStorage: LocalStorage
        },

        /**
         * Adds an element to the basket without savoing it
         * @param element
         * @param quantity
         */
        addElementToBasket: function (element, quantity) {

            if (this.$.basket) {
                var basketItem = this.$.basket.addElement(element, quantity);
                element = basketItem.$.element;

                var originId = this.$.originId;
                if (originId) {
                    basketItem.set('origin', new Entity({
                        id: originId
                    }));
                }

                var continueShoppingLink = this.$.continueShoppingLink;

                if (continueShoppingLink) {
                    element.set("continueShoppingLink", continueShoppingLink)
                }

                var editBasketItemLinkTemplate = this.$.editBasketItemLinkTemplate;
                if (editBasketItemLinkTemplate) {
                    element.set("editLink", editBasketItemLinkTemplate.replace("$productId", element.get("item.id")))
                }
            }
        },

        _triggerBasketChanged: function () {
            this.trigger("on:basketChanged", this.$.basket, this);
        },

        _triggerBasketUpdated: function () {
            this.trigger("on:basketUpdated", this.$.basket, this);
        },

        _initBasket: function (callback) {

            var api = this.$.api,
                localStorage = this.$.localStorage,
                basketId = this.$.apiBasketId,
                basket,
                self = this;

            basketId = basketId || localStorage.getItem("basketId");

            basket = api.createEntity(Basket, basketId);

            this.set({
                basket: basket,
                apiBasketId: basketId
            });

            var basketSaveCallback = function (err) {
                if (!err) {
                    self.set("apiBasketId", basketId);
                    self.$.localStorage.setItem("basketId", basket.$.id);
                    self._triggerBasketChanged();
                    fetchBasketDiscountScales(basket, callback);
                } else {
                    console.warn(err);
                    callback(err);
                }

            };

            var fetchBasketDiscountScales = function (basket, cb) {
                if (basket.$.discounts && basket.$.discounts.size()) {
                    var discount = basket.$.discounts.at(0);
                    if (discount && discount.$.discountScale) {
                        discount.$.discountScale.fetch(null, cb);
                    } else {
                        cb();
                    }
                } else {
                    cb();
                }
            };

            if (basket.isNew()) {

                basket.set({
                    shop: this.$.shop,
                    currency: this.get("shop.currency")
                });

                basket.save(null, basketSaveCallback);
            } else {
                flow()
                    .seq(function (cb) {
                        basket.fetch({
                            noCache: true,
                            fetchSubModels: ["currency"]
                        }, cb)
                    })
                    .seq(function (cb) {
                        fetchBasketDiscountScales(basket, cb);
                    })
                    .exec(function (err) {
                        if (err) {
                            // something went wrong
                            basket.set('id', undefined);
                            basket.save(null, basketSaveCallback);
                            console.warn(err)
                        } else {

                            self.set("shop", basket.$.shop);

                            flow()
                                .parEach(basket.$.basketItems.toArray(), function (item, cb) {
                                    item.$.element.getProduct().fetch({
                                        fetchSubModels: ["productType"]
                                    }, cb);
                                })
                                .exec(callback);
                        }
                    });
            }
        },

        /**
         *
         * @param {sprd.model.BasketItem} basketItem
         */
        saveBasketItem: function (basketItem) {
            var self = this;
            this.$itemSaveTimeout && clearTimeout(this.$itemSaveTimeout);

            this.$itemSaveTimeout = setTimeout(function () {
                basketItem.save(null, function () {
                    self.$.basket.fetch({noCache: true});
                });
                self._triggerBasketChanged();
            }, 300);
        },

        /**
         *
         * @param {sprd.model.BasketItem} basketItem
         */
        removeBasketItem: function (basketItem) {
            this.$.basket.$.basketItems.remove(basketItem);
            this.saveBasketDebounced();
        },

        saveBasketDebounced: function () {
            this._debounceFunctionCall(function () {
                this.saveBasket();
            }, "saveBasketCall", 700, this);
        },

        saveBasket: function (callback) {
            if (!this.$savingBasket) {
                this.$savingBasket = true;
                this.$callSaveBasketAgain = false;
                this.$saveCallbacks = this.$saveCallbacks || [];
                if (callback) {
                    this.$saveCallbacks.push(callback);
                }
                var self = this,
                    basket = this.$.basket;

                function callCallbacks(err, basket) {
                    while (self.$saveCallbacks.length) {
                        self.$saveCallbacks.shift()(err, basket);
                    }
                }

                flow()
                    .seq(function (cb) {
                        basket.save(null, cb);
                    })
                    .seq(function (cb) {
                        self._triggerBasketChanged();

                        basket.fetch({noCache: true}, cb);
                    })
                    .exec(function (err) {
                        if (self.$basketChanged) {
                            self.$basketChanged = false;
                            self.$savingBasket = false;
                            self.saveBasket();
                        } else {
                            callCallbacks(err, basket);
                            self.$savingBasket = false;
                            self._triggerBasketUpdated();
                        }
                    });
            } else {
                this.$saveCallbacks.push(callback);
                this.$basketChanged = true;
            }

        }
    });

});<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Text, View, ToastAndroid } from 'react-native';

import { Button } from 'react-native-ui-components';
import Icon from 'react-native-vector-icons/FontAwesome';
import { CustomerImage, SupplierImage, StockImage, ModulesImage, InfoBadge } from '../widgets';

import { ROUTES } from '../navigation/constants';
import { navStrings } from '../localization';

import { SETTINGS_KEYS } from '../settings';
import { UIDatabase } from '../database';

import {
  gotoCustomerInvoices,
  gotoCustomerRequisitions,
  gotoSupplierInvoices,
  gotoSupplierRequisitions,
  gotoStock,
  gotoStocktakes,
  gotoRealmExplorer,
  gotoSettings,
} from '../navigation/actions';

import globalStyles, { SHADOW_BORDER, GREY } from '../globalStyles';
import { UserActions } from '../actions/index';

const exportData = async () => {
  const syncSiteName = UIDatabase.getSetting(SETTINGS_KEYS.SYNC_SITE_NAME);
  const { success, message } = await UIDatabase.exportData(syncSiteName);
  const toastMessage = success ? 'Exported data file' : `Couldn't export data: ${message}`;
  ToastAndroid.show(toastMessage, ToastAndroid.SHORT);
};

const Menu = ({
  isInAdminMode,
  logout,
  toCustomerInvoices,
  toCustomerRequisitions,
  toStock,
  toStocktakes,
  toSupplierInvoices,
  toSupplierRequisitions,
  toRealmExplorer,
  toSettings,
  usingDashboard,
  usingDispensary,
  usingModules,
  isAdmin,
}) => {
  const { menuButton, menuButtonText: buttonText, appBackground } = globalStyles;
  const { image, originalContainer, moduleContainer, container, bottomIcon, moduleRow } = styles;

  const containerStyle = { ...container, ...(usingModules ? moduleContainer : originalContainer) };

  const MenuButton = useCallback(
    props => <Button style={menuButton} textStyle={buttonText} {...props} />,
    [usingDashboard, usingDispensary, usingModules]
  );

  const CustomerSection = useCallback(
    () => (
      <View style={containerStyle}>
        <CustomerImage style={image} />
        <View>
          <InfoBadge routeName={ROUTES.CUSTOMER_INVOICES}>
            <MenuButton text={navStrings.customer_invoices} onPress={toCustomerInvoices} />
          </InfoBadge>
          <InfoBadge routeName={ROUTES.CUSTOMER_REQUISITIONS}>
            <MenuButton text={navStrings.customer_requisitions} onPress={toCustomerRequisitions} />
          </InfoBadge>
        </View>
      </View>
    ),
    [usingDashboard, usingDispensary, usingModules]
  );

  const SupplierSection = useCallback(
    () => (
      <View style={containerStyle}>
        <SupplierImage style={image} />
        <View>
          <InfoBadge routeName={ROUTES.SUPPLIER_INVOICES}>
            <MenuButton text={navStrings.supplier_invoices} onPress={toSupplierInvoices} />
          </InfoBadge>
          <InfoBadge routeName={ROUTES.SUPPLIER_REQUISITIONS}>
            <MenuButton text={navStrings.supplier_requisitions} onPress={toSupplierRequisitions} />
          </InfoBadge>
        </View>
      </View>
    ),
    [usingDashboard, usingDispensary, usingModules]
  );

  const StockSection = useCallback(
    () => (
      <View style={containerStyle}>
        <StockImage style={image} />
        <View>
          <MenuButton text={navStrings.current_stock} onPress={toStock} />
          <InfoBadge routeName={ROUTES.STOCKTAKES}>
            <MenuButton text={navStrings.stocktake} onPress={toStocktakes} />
          </InfoBadge>
        </View>
      </View>
    ),
    [usingDashboard, usingDispensary, usingModules]
  );

  const ModulesSection = useCallback(
    () => (
      <View style={containerStyle}>
        <ModulesImage style={image} />
        <View>
          {usingDispensary && <MenuButton text="Dispensary" />}
          {usingDashboard && <MenuButton text="Dashboard" />}
        </View>
      </View>
    ),
    [usingDashboard, usingDispensary, usingModules]
  );

  const AdminRow = useCallback(
    () => (
      <View style={styles.bottomRow}>
        <View style={styles.bottomIconView}>
          <Icon.Button
            name="power-off"
            iconStyle={bottomIcon}
            backgroundColor="rgba(255,255,255,0)"
            onPress={logout}
          >
            <Text>{navStrings.log_out}</Text>
          </Icon.Button>
        </View>
        {isInAdminMode && <MenuButton text="Realm Explorer" onPress={toRealmExplorer} />}
        {isInAdminMode && <MenuButton text="Export Data" onPress={exportData} />}
        {isAdmin && (
          <Icon.Button
            name="cog"
            iconStyle={bottomIcon}
            backgroundColor="rgba(255,255,255,0)"
            onPress={toSettings}
          >
            <Text>SETTINGS</Text>
          </Icon.Button>
        )}
      </View>
    ),
    [isInAdminMode, isAdmin]
  );

  const ModuleLayout = useCallback(
    () => (
      <View style={styles.moduleTopRow}>
        <View style={moduleRow}>
          <CustomerSection />
          <SupplierSection />
        </View>
        <View style={moduleRow}>
          <StockSection />
          <ModulesSection />
        </View>
      </View>
    ),
    []
  );

  const OriginalLayout = useCallback(
    () => (
      <View style={styles.originalTopRow}>
        <CustomerSection />
        <SupplierSection />
        <StockSection />
      </View>
    ),
    []
  );

  return (
    <View style={{ ...appBackground }}>
      {usingModules ? <ModuleLayout /> : <OriginalLayout />}
      <AdminRow />
    </View>
  );
};

const styles = {
  moduleTopRow: { flex: 9 },
  originalTopRow: { flex: 9, flexDirection: 'row' },
  moduleRow: { flex: 1, flexDirection: 'row' },
  image: { height: 150, width: 150, marginBottom: 30 },
  bottomIcon: { color: GREY },
  bottomRow: {
    flex: 1,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginHorizontal: 20,
  },
  container: {
    alignItems: 'center',
    borderColor: SHADOW_BORDER,
    backgroundColor: 'white',
    marginTop: 30,
    flex: 1,
    borderWidth: 1,
  },
  moduleContainer: {
    justifyContent: 'center',
    flexDirection: 'row',
    marginHorizontal: 15,
  },
  originalContainer: {
    justifyContent: 'flex-start',
    paddingTop: 50,
    marginHorizontal: 30,
  },
};

const mapDispatchToProps = dispatch => ({
  toCustomerInvoices: () => dispatch(gotoCustomerInvoices()),
  toCustomerRequisitions: () => dispatch(gotoCustomerRequisitions()),
  toStock: () => dispatch(gotoStock()),
  toStocktakes: () => dispatch(gotoStocktakes()),
  toSupplierInvoices: () => dispatch(gotoSupplierInvoices()),
  toSupplierRequisitions: () => dispatch(gotoSupplierRequisitions()),
  toRealmExplorer: () => dispatch(gotoRealmExplorer()),
  toSettings: () => dispatch(gotoSettings()),
  logout: () => dispatch(UserActions.logout()),
});

const mapStateToProps = state => {
  const { modules, user } = state;
  const { currentUser } = user;
  const { usingDashboard, usingDispensary } = modules;
  return {
    usingDashboard,
    usingDispensary,
    usingModules: usingDashboard || usingDispensary,
    isAdmin: currentUser?.isAdmin,
  };
};

export const MenuPage = connect(mapStateToProps, mapDispatchToProps)(Menu);

Menu.defaultProps = {
  isInAdminMode: false,
};

Menu.propTypes = {
  isInAdminMode: PropTypes.bool,
  logout: PropTypes.func.isRequired,
  toCustomerInvoices: PropTypes.func.isRequired,
  toCustomerRequisitions: PropTypes.func.isRequired,
  toStock: PropTypes.func.isRequired,
  toStocktakes: PropTypes.func.isRequired,
  toSupplierInvoices: PropTypes.func.isRequired,
  toSupplierRequisitions: PropTypes.func.isRequired,
  toRealmExplorer: PropTypes.func.isRequired,
  toSettings: PropTypes.func.isRequired,
  isAdmin: PropTypes.bool.isRequired,
  usingDashboard: PropTypes.bool.isRequired,
  usingDispensary: PropTypes.bool.isRequired,
  usingModules: PropTypes.bool.isRequired,
};
<commit_msg>Destructure usingModules<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */
import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Text, View, ToastAndroid } from 'react-native';

import { Button } from 'react-native-ui-components';
import Icon from 'react-native-vector-icons/FontAwesome';
import { CustomerImage, SupplierImage, StockImage, ModulesImage, InfoBadge } from '../widgets';

import { ROUTES } from '../navigation/constants';
import { navStrings } from '../localization';

import { SETTINGS_KEYS } from '../settings';
import { UIDatabase } from '../database';

import {
  gotoCustomerInvoices,
  gotoCustomerRequisitions,
  gotoSupplierInvoices,
  gotoSupplierRequisitions,
  gotoStock,
  gotoStocktakes,
  gotoRealmExplorer,
  gotoSettings,
} from '../navigation/actions';

import globalStyles, { SHADOW_BORDER, GREY } from '../globalStyles';
import { UserActions } from '../actions/index';

const exportData = async () => {
  const syncSiteName = UIDatabase.getSetting(SETTINGS_KEYS.SYNC_SITE_NAME);
  const { success, message } = await UIDatabase.exportData(syncSiteName);
  const toastMessage = success ? 'Exported data file' : `Couldn't export data: ${message}`;
  ToastAndroid.show(toastMessage, ToastAndroid.SHORT);
};

const Menu = ({
  isInAdminMode,
  logout,
  toCustomerInvoices,
  toCustomerRequisitions,
  toStock,
  toStocktakes,
  toSupplierInvoices,
  toSupplierRequisitions,
  toRealmExplorer,
  toSettings,
  usingDashboard,
  usingDispensary,
  usingModules,
  isAdmin,
}) => {
  const { menuButton, menuButtonText: buttonText, appBackground } = globalStyles;
  const { image, originalContainer, moduleContainer, container, bottomIcon, moduleRow } = styles;

  const containerStyle = { ...container, ...(usingModules ? moduleContainer : originalContainer) };

  const MenuButton = useCallback(
    props => <Button style={menuButton} textStyle={buttonText} {...props} />,
    [usingDashboard, usingDispensary, usingModules]
  );

  const CustomerSection = useCallback(
    () => (
      <View style={containerStyle}>
        <CustomerImage style={image} />
        <View>
          <InfoBadge routeName={ROUTES.CUSTOMER_INVOICES}>
            <MenuButton text={navStrings.customer_invoices} onPress={toCustomerInvoices} />
          </InfoBadge>
          <InfoBadge routeName={ROUTES.CUSTOMER_REQUISITIONS}>
            <MenuButton text={navStrings.customer_requisitions} onPress={toCustomerRequisitions} />
          </InfoBadge>
        </View>
      </View>
    ),
    [usingDashboard, usingDispensary, usingModules]
  );

  const SupplierSection = useCallback(
    () => (
      <View style={containerStyle}>
        <SupplierImage style={image} />
        <View>
          <InfoBadge routeName={ROUTES.SUPPLIER_INVOICES}>
            <MenuButton text={navStrings.supplier_invoices} onPress={toSupplierInvoices} />
          </InfoBadge>
          <InfoBadge routeName={ROUTES.SUPPLIER_REQUISITIONS}>
            <MenuButton text={navStrings.supplier_requisitions} onPress={toSupplierRequisitions} />
          </InfoBadge>
        </View>
      </View>
    ),
    [usingDashboard, usingDispensary, usingModules]
  );

  const StockSection = useCallback(
    () => (
      <View style={containerStyle}>
        <StockImage style={image} />
        <View>
          <MenuButton text={navStrings.current_stock} onPress={toStock} />
          <InfoBadge routeName={ROUTES.STOCKTAKES}>
            <MenuButton text={navStrings.stocktake} onPress={toStocktakes} />
          </InfoBadge>
        </View>
      </View>
    ),
    [usingDashboard, usingDispensary, usingModules]
  );

  const ModulesSection = useCallback(
    () => (
      <View style={containerStyle}>
        <ModulesImage style={image} />
        <View>
          {usingDispensary && <MenuButton text="Dispensary" />}
          {usingDashboard && <MenuButton text="Dashboard" />}
        </View>
      </View>
    ),
    [usingDashboard, usingDispensary, usingModules]
  );

  const AdminRow = useCallback(
    () => (
      <View style={styles.bottomRow}>
        <View style={styles.bottomIconView}>
          <Icon.Button
            name="power-off"
            iconStyle={bottomIcon}
            backgroundColor="rgba(255,255,255,0)"
            onPress={logout}
          >
            <Text>{navStrings.log_out}</Text>
          </Icon.Button>
        </View>
        {isInAdminMode && <MenuButton text="Realm Explorer" onPress={toRealmExplorer} />}
        {isInAdminMode && <MenuButton text="Export Data" onPress={exportData} />}
        {isAdmin && (
          <Icon.Button
            name="cog"
            iconStyle={bottomIcon}
            backgroundColor="rgba(255,255,255,0)"
            onPress={toSettings}
          >
            <Text>SETTINGS</Text>
          </Icon.Button>
        )}
      </View>
    ),
    [isInAdminMode, isAdmin]
  );

  const ModuleLayout = useCallback(
    () => (
      <View style={styles.moduleTopRow}>
        <View style={moduleRow}>
          <CustomerSection />
          <SupplierSection />
        </View>
        <View style={moduleRow}>
          <StockSection />
          <ModulesSection />
        </View>
      </View>
    ),
    []
  );

  const OriginalLayout = useCallback(
    () => (
      <View style={styles.originalTopRow}>
        <CustomerSection />
        <SupplierSection />
        <StockSection />
      </View>
    ),
    []
  );

  return (
    <View style={{ ...appBackground }}>
      {usingModules ? <ModuleLayout /> : <OriginalLayout />}
      <AdminRow />
    </View>
  );
};

const styles = {
  moduleTopRow: { flex: 9 },
  originalTopRow: { flex: 9, flexDirection: 'row' },
  moduleRow: { flex: 1, flexDirection: 'row' },
  image: { height: 150, width: 150, marginBottom: 30 },
  bottomIcon: { color: GREY },
  bottomRow: {
    flex: 1,
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
    marginHorizontal: 20,
  },
  container: {
    alignItems: 'center',
    borderColor: SHADOW_BORDER,
    backgroundColor: 'white',
    marginTop: 30,
    flex: 1,
    borderWidth: 1,
  },
  moduleContainer: {
    justifyContent: 'center',
    flexDirection: 'row',
    marginHorizontal: 15,
  },
  originalContainer: {
    justifyContent: 'flex-start',
    paddingTop: 50,
    marginHorizontal: 30,
  },
};

const mapDispatchToProps = dispatch => ({
  toCustomerInvoices: () => dispatch(gotoCustomerInvoices()),
  toCustomerRequisitions: () => dispatch(gotoCustomerRequisitions()),
  toStock: () => dispatch(gotoStock()),
  toStocktakes: () => dispatch(gotoStocktakes()),
  toSupplierInvoices: () => dispatch(gotoSupplierInvoices()),
  toSupplierRequisitions: () => dispatch(gotoSupplierRequisitions()),
  toRealmExplorer: () => dispatch(gotoRealmExplorer()),
  toSettings: () => dispatch(gotoSettings()),
  logout: () => dispatch(UserActions.logout()),
});

const mapStateToProps = state => {
  const { modules, usingModules, user } = state;
  const { currentUser } = user;
  const { usingDashboard, usingDispensary, usingVaccines } = modules;
  return {
    usingDashboard,
    usingDispensary,
    usingVaccines,
    usingModules,
    isAdmin: currentUser?.isAdmin,
  };
};

export const MenuPage = connect(mapStateToProps, mapDispatchToProps)(Menu);

Menu.defaultProps = {
  isInAdminMode: false,
};

Menu.propTypes = {
  isInAdminMode: PropTypes.bool,
  logout: PropTypes.func.isRequired,
  toCustomerInvoices: PropTypes.func.isRequired,
  toCustomerRequisitions: PropTypes.func.isRequired,
  toStock: PropTypes.func.isRequired,
  toStocktakes: PropTypes.func.isRequired,
  toSupplierInvoices: PropTypes.func.isRequired,
  toSupplierRequisitions: PropTypes.func.isRequired,
  toRealmExplorer: PropTypes.func.isRequired,
  toSettings: PropTypes.func.isRequired,
  isAdmin: PropTypes.bool.isRequired,
  usingDashboard: PropTypes.bool.isRequired,
  usingDispensary: PropTypes.bool.isRequired,
  usingModules: PropTypes.bool.isRequired,
};
<|endoftext|>


<commit_before>/*
 This file is a part of libertysoil.org website
 Copyright (C) 2016  Loki Education (Social Enterprise)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
import React, { PropTypes } from 'react';
import { pick } from 'lodash';

import {
  Page,
  PageMain,
  PageHero,
  PageBody,
  PageContent
}                       from '../../components/page';
import Header           from '../../components/header';
import MapboxMap        from '../../components/mapbox-map';
import HeaderLogo       from '../../components/header-logo';
import CreatePost       from '../../components/create-post';
import TagBreadcrumbs   from '../../components/breadcrumbs/tag-breadcrumbs';
import Footer           from '../../components/footer';
import TagHeader        from '../../components/tag-header';
import Sidebar          from '../../components/sidebar';
import SidebarAlt       from '../../components/sidebarAlt';
import AddedTags        from '../../components/post/added-tags';
import UpdatePicture    from '../../components/update-picture/update-picture';
import Tag              from '../../components/tag';
import { TAG_SCHOOL, TAG_LOCATION, TAG_HASHTAG, TAG_HEADER_SIZE, DEFAULT_HEADER_PICTURE } from '../../consts/tags';

function formInitialTags(type, value) {
  switch (type) {
    case TAG_SCHOOL:
      return { schools: value };
    case TAG_HASHTAG:
      return { hashtags: value };
    case TAG_LOCATION:
      return { geotags: value };
    default:
      return {};
  }
}

function getPageCaption(type, name) {
  let caption;
  switch (type) {
    case TAG_LOCATION: {
      caption = [`${name} `, Education];
      break;
    }
    default:
      caption = name;
  }

  return (

        {caption}

        <Tag size="BIG" type={type} />

  );
}

function GeotagPageHero({ geotag }) {
  let type = geotag.type;
  const location = {
    lat: geotag.get('lat'),
    lon: geotag.get('lon')
  };

  // A lot of admin divisions don't have lat/lon. Attempt to take coords from the country.
  if (!(location.lat && location.lon) && geotag.get('country')) {
    type = 'Country';
    location.lat = geotag.getIn(['country', 'lat']);
    location.lon = geotag.getIn(['country', 'lon']);
  }

  let zoom;
  switch (type) {
    case 'Planet': zoom = 3; break;
    case 'Continent': zoom = 4; break;
    case 'Country': zoom = 5; break;
    case 'AdminDivision1': zoom = 6; break;
    case 'City': zoom = 12; break;
    default: zoom = 10;
  }

  if (location.lat && location.lon) {
    return (
      <PageHero>
        <MapboxMap
          className="page__hero_map"
          frozen
          viewLocation={location}
          zoom={zoom}
        />
      </PageHero>
    );
  }

  return <PageHero url="/images/hero/welcome.jpg" />;
}

function TagPageHero({ type, tag, url, editable, onSubmit, limits, preview, flexible }) {
  switch (type) {
    case TAG_HASHTAG:
    case TAG_SCHOOL:
      return (
        <PageHero url={url}>
          {editable &&

                <UpdatePicture
                  flexible={flexible}
                  limits={limits}
                  preview={preview}
                  what="header image"
                  where={({tag.get('name')})}
                  onSubmit={onSubmit}
                />

          }
        </PageHero>
      );
    case TAG_LOCATION:
      return <GeotagPageHero geotag={tag} />;
    default:
      return null;
  }
}

export default class BaseTagPage extends React.Component {
  static displayName = 'BaseTagPage';

  static propTypes = {
    actions: PropTypes.shape({
      resetCreatePostForm: PropTypes.func,
      updateCreatePostForm: PropTypes.func
    }).isRequired,
    children: PropTypes.node,
    postsAmount: PropTypes.number,
    tag: PropTypes.shape({}).isRequired,
    type: PropTypes.string.isRequired
  };

  constructor(props) {
    super(props);

    this.postsAmount = null;
    this.state = {
      form: false,
      head_pic: null
    };
  }

  componentWillMount() {
    this.postsAmount = this.props.postsAmount;
  }

  componentWillReceiveProps(nextProps) {
    if (this.state.form) {
      if (this.postsAmount != nextProps.postsAmount) {
        this.setState({ form: false });
      }
    }

    this.postsAmount = nextProps.postsAmount;
  }

  componentWillUnmount() {
    this.props.actions.resetCreatePostForm();
  }

  _getNewPictures() {
    const pictures = {};

    if (this.state.head_pic) {
      pictures.head_pic = this.state.head_pic.production;
    }

    return pictures;
  }

  _clearPreview() {
    this.setState({ head_pic: null });
  }

  addPicture = async ({ production, preview }) => {
    if (production) {
      const _production = { picture: production.picture };

      // properties assign order is important
      _production.crop = pick(production.crop, ['left', 'top', 'right', 'bottom']);

      if (production.crop.width > TAG_HEADER_SIZE.BIG.width) {
        _production.scale = { wRatio: TAG_HEADER_SIZE.BIG.width / production.crop.width };
      } else {
        _production.scale = { wRatio: TAG_HEADER_SIZE.NORMAL.width / production.crop.width };
      }

      this.setState({ head_pic: { production: _production, preview } });
    } else {
      this.setState({ head_pic: null });
    }
  };

  toggleForm = () => {
    if (!this.state.form) {
      const { tag, type } = this.props;
      this.props.actions.resetCreatePostForm();
      this.props.actions.updateCreatePostForm(formInitialTags(type, [tag]));
    }

    this.setState({ form: !this.state.form });
  };

  render() {
    const {
      is_logged_in,
      current_user,
      create_post_form,
      actions,
      triggers,
      type,
      tag,
      postsAmount,
      editable
    } = this.props;

    const name = tag.get('name') || tag.get('url_name');
    const pageCaption = getPageCaption(type, name);

    let headerPictureUrl;
    if (this.state.head_pic) {
      headerPictureUrl = this.state.head_pic.preview.url;
    } else if (tag.getIn(['more', 'head_pic', 'url'])) {
      headerPictureUrl = tag.getIn(['more', 'head_pic', 'url']);
    } else {
      headerPictureUrl = DEFAULT_HEADER_PICTURE;
    }

    let createPostForm;
    let addedTags;
    let tagsAttached = false;
    if (is_logged_in) {
      if (this.state.form) {
        const geotags = create_post_form.get('geotags');
        const hashtags = create_post_form.get('hashtags');
        const schools = create_post_form.get('schools');
        tagsAttached = geotags.size || hashtags.size || schools.size;
        tagsAttached = !!tagsAttached;

        createPostForm = (
          <CreatePost
            actions={actions}
            addedGeotags={geotags}
            addedHashtags={hashtags}
            addedSchools={schools}
            defaultText={create_post_form.get('text')}
            triggers={triggers}
            userRecentTags={current_user.get('recent_tags')}
          />
        );
        addedTags = (
          <AddedTags
            geotags={geotags}
            hashtags={hashtags}
            schools={schools}
          />
        );
      }
    }

    return (

          <HeaderLogo small />
          <TagBreadcrumbs type={type} tag={tag} />

        <Page>
          <PageMain className={!tagsAttached && 'page__main--without-right'}>
            <PageBody className="page__body-up">
              <Sidebar current_user={current_user} />
              <PageContent>
                {createPostForm}
                {pageCaption}
                <TagPageHero
                  editable={editable}
                  flexible
                  limits={{ min: TAG_HEADER_SIZE.MIN, max: TAG_HEADER_SIZE.BIG }}
                  preview={TAG_HEADER_SIZE.PREVIEW}
                  tag={tag}
                  type={type}
                  url={headerPictureUrl}
                  onSubmit={this.addPicture}
                />
                <TagHeader
                  current_user={current_user}
                  editable={editable}
                  is_logged_in={is_logged_in}
                  newPost={this.toggleForm}
                  postsAmount={postsAmount}
                  tag={tag}
                  triggers={triggers}
                  type={type}
                />

                  {this.props.children}

              </PageContent>
              {tagsAttached &&
                <SidebarAlt>
                  {addedTags}
                </SidebarAlt>
              }
            </PageBody>
          </PageMain>
        </Page>

    );
  }
}
<commit_msg>Add ability for base tag page to customize alt sidebar<commit_after>/*
 This file is a part of libertysoil.org website
 Copyright (C) 2016  Loki Education (Social Enterprise)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
import React, { PropTypes } from 'react';
import { pick } from 'lodash';

import {
  Page,
  PageMain,
  PageHero,
  PageBody,
  PageContent
}                       from '../../components/page';
import Header           from '../../components/header';
import MapboxMap        from '../../components/mapbox-map';
import HeaderLogo       from '../../components/header-logo';
import CreatePost       from '../../components/create-post';
import TagBreadcrumbs   from '../../components/breadcrumbs/tag-breadcrumbs';
import Footer           from '../../components/footer';
import TagHeader        from '../../components/tag-header';
import Sidebar          from '../../components/sidebar';
import SidebarAlt       from '../../components/sidebarAlt';
import AddedTags        from '../../components/post/added-tags';
import UpdatePicture    from '../../components/update-picture/update-picture';
import Tag              from '../../components/tag';
import { TAG_SCHOOL, TAG_LOCATION, TAG_HASHTAG, TAG_HEADER_SIZE, DEFAULT_HEADER_PICTURE } from '../../consts/tags';

function formInitialTags(type, value) {
  switch (type) {
    case TAG_SCHOOL:
      return { schools: value };
    case TAG_HASHTAG:
      return { hashtags: value };
    case TAG_LOCATION:
      return { geotags: value };
    default:
      return {};
  }
}

function getPageCaption(type, name) {
  let caption;
  switch (type) {
    case TAG_LOCATION: {
      caption = [`${name} `, Education];
      break;
    }
    default:
      caption = name;
  }

  return (

        {caption}

        <Tag size="BIG" type={type} />

  );
}

function GeotagPageHero({ geotag }) {
  let type = geotag.type;
  const location = {
    lat: geotag.get('lat'),
    lon: geotag.get('lon')
  };

  // A lot of admin divisions don't have lat/lon. Attempt to take coords from the country.
  if (!(location.lat && location.lon) && geotag.get('country')) {
    type = 'Country';
    location.lat = geotag.getIn(['country', 'lat']);
    location.lon = geotag.getIn(['country', 'lon']);
  }

  let zoom;
  switch (type) {
    case 'Planet': zoom = 3; break;
    case 'Continent': zoom = 4; break;
    case 'Country': zoom = 5; break;
    case 'AdminDivision1': zoom = 6; break;
    case 'City': zoom = 12; break;
    default: zoom = 10;
  }

  if (location.lat && location.lon) {
    return (
      <PageHero>
        <MapboxMap
          className="page__hero_map"
          frozen
          viewLocation={location}
          zoom={zoom}
        />
      </PageHero>
    );
  }

  return <PageHero url="/images/hero/welcome.jpg" />;
}

function TagPageHero({ type, tag, url, editable, onSubmit, limits, preview, flexible }) {
  switch (type) {
    case TAG_HASHTAG:
    case TAG_SCHOOL:
      return (
        <PageHero url={url}>
          {editable &&

                <UpdatePicture
                  flexible={flexible}
                  limits={limits}
                  preview={preview}
                  what="header image"
                  where={({tag.get('name')})}
                  onSubmit={onSubmit}
                />

          }
        </PageHero>
      );
    case TAG_LOCATION:
      return <GeotagPageHero geotag={tag} />;
    default:
      return null;
  }
}

// TODO: Should we get rid of the base tag page at all? There aren't many similarities between the tag pages now.

export default class BaseTagPage extends React.Component {
  static displayName = 'BaseTagPage';

  static propTypes = {
    actions: PropTypes.shape({
      resetCreatePostForm: PropTypes.func,
      updateCreatePostForm: PropTypes.func
    }).isRequired,
    children: PropTypes.node,
    postsAmount: PropTypes.number,
    tag: PropTypes.shape({}).isRequired,
    type: PropTypes.string.isRequired
  };

  constructor(props) {
    super(props);

    this.postsAmount = null;
    this.state = {
      form: false,
      head_pic: null
    };
  }

  componentWillMount() {
    this.postsAmount = this.props.postsAmount;
  }

  componentWillReceiveProps(nextProps) {
    if (this.state.form) {
      if (this.postsAmount != nextProps.postsAmount) {
        this.setState({ form: false });
      }
    }

    this.postsAmount = nextProps.postsAmount;
  }

  componentWillUnmount() {
    this.props.actions.resetCreatePostForm();
  }

  _getNewPictures() {
    const pictures = {};

    if (this.state.head_pic) {
      pictures.head_pic = this.state.head_pic.production;
    }

    return pictures;
  }

  _clearPreview() {
    this.setState({ head_pic: null });
  }

  addPicture = async ({ production, preview }) => {
    if (production) {
      const _production = { picture: production.picture };

      // properties assign order is important
      _production.crop = pick(production.crop, ['left', 'top', 'right', 'bottom']);

      if (production.crop.width > TAG_HEADER_SIZE.BIG.width) {
        _production.scale = { wRatio: TAG_HEADER_SIZE.BIG.width / production.crop.width };
      } else {
        _production.scale = { wRatio: TAG_HEADER_SIZE.NORMAL.width / production.crop.width };
      }

      this.setState({ head_pic: { production: _production, preview } });
    } else {
      this.setState({ head_pic: null });
    }
  };

  toggleForm = () => {
    if (!this.state.form) {
      const { tag, type } = this.props;
      this.props.actions.resetCreatePostForm();
      this.props.actions.updateCreatePostForm(formInitialTags(type, [tag]));
    }

    this.setState({ form: !this.state.form });
  };

  render() {
    const {
      is_logged_in,
      current_user,
      create_post_form,
      actions,
      triggers,
      type,
      tag,
      postsAmount,
      editable,
      sidebarAlt
    } = this.props;

    const name = tag.get('name') || tag.get('url_name');
    const pageCaption = getPageCaption(type, name);

    let headerPictureUrl;
    if (this.state.head_pic) {
      headerPictureUrl = this.state.head_pic.preview.url;
    } else if (tag.getIn(['more', 'head_pic', 'url'])) {
      headerPictureUrl = tag.getIn(['more', 'head_pic', 'url']);
    } else {
      headerPictureUrl = DEFAULT_HEADER_PICTURE;
    }

    let createPostForm;
    let addedTags;
    let tagsAttached = false;
    if (is_logged_in) {
      if (this.state.form) {
        const geotags = create_post_form.get('geotags');
        const hashtags = create_post_form.get('hashtags');
        const schools = create_post_form.get('schools');
        tagsAttached = geotags.size || hashtags.size || schools.size;
        tagsAttached = !!tagsAttached;

        createPostForm = (
          <CreatePost
            actions={actions}
            addedGeotags={geotags}
            addedHashtags={hashtags}
            addedSchools={schools}
            defaultText={create_post_form.get('text')}
            triggers={triggers}
            userRecentTags={current_user.get('recent_tags')}
          />
        );
        addedTags = (
          <AddedTags
            geotags={geotags}
            hashtags={hashtags}
            schools={schools}
          />
        );
      }
    }

    return (

          <HeaderLogo small />
          <TagBreadcrumbs type={type} tag={tag} />

        <Page>
          <PageMain className={!tagsAttached && 'page__main--without-right'}>
            <PageBody className="page__body-up">
              <Sidebar current_user={current_user} />
              <PageContent>
                {createPostForm}
                {pageCaption}
                <TagPageHero
                  editable={editable}
                  flexible
                  limits={{ min: TAG_HEADER_SIZE.MIN, max: TAG_HEADER_SIZE.BIG }}
                  preview={TAG_HEADER_SIZE.PREVIEW}
                  tag={tag}
                  type={type}
                  url={headerPictureUrl}
                  onSubmit={this.addPicture}
                />
                <TagHeader
                  current_user={current_user}
                  editable={editable}
                  is_logged_in={is_logged_in}
                  newPost={this.toggleForm}
                  postsAmount={postsAmount}
                  tag={tag}
                  triggers={triggers}
                  type={type}
                />

                  {this.props.children}

              </PageContent>
              <SidebarAlt>
                {tagsAttached &&
                  addedTags
                }
                {sidebarAlt}
              </SidebarAlt>
            </PageBody>
          </PageMain>
        </Page>

    );
  }
}
<|endoftext|>


<commit_before>function loginRegister1(url)
{
	var params = Form.serialize('register1_form');
	new Ajax.Request(url, {
		asynchronous:true,
		method: "post",
		evalScripts: true,
		parameters: params,
		onLoading: function (transport) {
			$('register1_button').hide();
			$('register1_indicator').show();
		},
		onSuccess: function (transport) {
			var json = transport.responseJSON;
			if (json.failed)
			{
				Form.reset('register1_form');
				$('register1_indicator').hide();
				$('register1_button').show();
				failedMessage(json.error);
			}
			else
			{
				$('register1_indicator').hide();
				$('register1').hide();
				$('username').value = json.message;
				$('fieldusername').value = json.message;
				$('register2').show();
			}
		},
		onFailure: function (transport) {
			$('register1_indicator').hide();
			$('register1_button').show();
			var json = transport.responseJSON;
			if (json && (json.failed || json.error))
			{
				failedMessage(json.error);
			}
		}
	});
}

function loginRegister2(url)
{
	var params = Form.serialize('register2_form');
	new Ajax.Request(url, {
		asynchronous:true,
		method: "post",
		evalScripts: true,
		parameters: params,
		onLoading: function (transport) {
			$$('input.required').each(function(field) {
				$(field).setStyle({ backgroundColor: '' });
			});
			$('register2_button').hide();
			$('register2_indicator').show();
		},
		onSuccess: function (transport) {
			var json = transport.responseJSON;
			if (json.failed)
			{
				$('register2_indicator').hide();
				$('register2_button').show();
				json.fields.each(function(field) {
					$(field).setStyle({ backgroundColor: '#FBB' });
				});
				failedMessage(json.error);
			}
			else
			{
				$('register2_indicator').hide();
				$('register2').hide();
				$('register_message').update(json.message);
				$('register_message').innerHTML;
				$('register3').show();
			}
		},
		onFailure: function (transport) {
			$('register2_indicator').hide();
			$('register2_button').show();
			var json = transport.responseJSON;
			if (json && (json.failed || json.error))
			{
				failedMessage(json.error);
			}
		}
	});
}

function loginUser(url)
{
	var params = Form.serialize('login_form');
	new Ajax.Request(url, {
		asynchronous:true,
		method: "post",
		evalScripts: true,
		parameters: params,
		onLoading: function (transport) {
			$('login_button').hide();
			$('login_indicator').show();
		},
		onSuccess: function (transport) {
			var json = transport.responseJSON;
			if (json.failed)
			{
				Form.reset('login_form');
				$('tbg3_referer').value = json.referer;
				$('login_indicator').hide();
				$('login_button').show();
				failedMessage(json.error);
			}
			else
			{
				document.location = json.forward;
			}
		},
		onFailure: function (transport) {
			$('login_indicator').hide();
			$('login_button').show();
			var json = transport.responseJSON;
			if (json && (json.failed || json.error))
			{
				Form.reset('login_form');
				$('tbg3_referer').value = json.referer;
				failedMessage(json.error);
			}
		}
	});
}<commit_msg>fix HTML special char entity inside text input in "register new account" (bug #681)<commit_after>function loginRegister1(url)
{
	var params = Form.serialize('register1_form');
	new Ajax.Request(url, {
		asynchronous:true,
		method: "post",
		evalScripts: true,
		parameters: params,
		onLoading: function (transport) {
			$('register1_button').hide();
			$('register1_indicator').show();
		},
		onSuccess: function (transport) {
			var json = transport.responseJSON;
			if (json.failed)
			{
				Form.reset('register1_form');
				$('register1_indicator').hide();
				$('register1_button').show();
				failedMessage(json.error);
			}
			else
			{
				$('register1_indicator').hide();
				$('register1').hide();
				$('username').value = json.message.unescapeHTML();
				$('fieldusername').value = json.message.unescapeHTML();
				$('register2').show();
			}
		},
		onFailure: function (transport) {
			$('register1_indicator').hide();
			$('register1_button').show();
			var json = transport.responseJSON;
			if (json && (json.failed || json.error))
			{
				failedMessage(json.error);
			}
		}
	});
}

function loginRegister2(url)
{
	var params = Form.serialize('register2_form');
	new Ajax.Request(url, {
		asynchronous:true,
		method: "post",
		evalScripts: true,
		parameters: params,
		onLoading: function (transport) {
			$$('input.required').each(function(field) {
				$(field).setStyle({ backgroundColor: '' });
			});
			$('register2_button').hide();
			$('register2_indicator').show();
		},
		onSuccess: function (transport) {
			var json = transport.responseJSON;
			if (json.failed)
			{
				$('register2_indicator').hide();
				$('register2_button').show();
				json.fields.each(function(field) {
					$(field).setStyle({ backgroundColor: '#FBB' });
				});
				failedMessage(json.error);
			}
			else
			{
				$('register2_indicator').hide();
				$('register2').hide();
				$('register_message').update(json.message);
				$('register_message').innerHTML;
				$('register3').show();
			}
		},
		onFailure: function (transport) {
			$('register2_indicator').hide();
			$('register2_button').show();
			var json = transport.responseJSON;
			if (json && (json.failed || json.error))
			{
				failedMessage(json.error);
			}
		}
	});
}

function loginUser(url)
{
	var params = Form.serialize('login_form');
	new Ajax.Request(url, {
		asynchronous:true,
		method: "post",
		evalScripts: true,
		parameters: params,
		onLoading: function (transport) {
			$('login_button').hide();
			$('login_indicator').show();
		},
		onSuccess: function (transport) {
			var json = transport.responseJSON;
			if (json.failed)
			{
				Form.reset('login_form');
				$('tbg3_referer').value = json.referer;
				$('login_indicator').hide();
				$('login_button').show();
				failedMessage(json.error);
			}
			else
			{
				document.location = json.forward;
			}
		},
		onFailure: function (transport) {
			$('login_indicator').hide();
			$('login_button').show();
			var json = transport.responseJSON;
			if (json && (json.failed || json.error))
			{
				Form.reset('login_form');
				$('tbg3_referer').value = json.referer;
				failedMessage(json.error);
			}
		}
	});
}<|endoftext|>


<commit_before>import * as R from "ramda"

//

function Identity(value) {this.value = value}
const Ident = x => new Identity(x)
Identity.prototype.map = function (x2y) {return new Identity(x2y(this.value))}
Identity.prototype.of = Ident
Identity.prototype.ap = function (x) {return new Identity(this.value(x.value))}

//

function Constant(value) {this.value = value}
const Const = x => new Constant(x)
const Single = x => Const([x])
Constant.prototype.map = function () {return this}
Constant.prototype.of = Const
Constant.prototype.ap = function (x) {return new Const(R.concat(this.value, x.value))}

//

const warn = process.env.NODE_ENV === "production" ? () => {} : (() => {
  const warned = {}

  return message => {
    if (!(message in warned)) {
      warned[message] = message
      console.warn("partial.lenses:", message)
    }
  }
})()

//

const curry2 = fn => function (a, b) {
  switch (arguments.length) {
    case 1:  return b => fn(a, b)
    default: return fn(a, b)
  }
}

const curry3 = fn => function (a, b, c) {
  switch (arguments.length) {
    case 1:  return curry2((b, c) => fn(a, b, c))
    case 2:  return c => fn(a, b, c)
    default: return fn(a, b, c)
  }
}

//

const isArray  = x => x && x.constructor === Array
const isObject = x => x && x.constructor === Object

const unArray  = x =>  isArray(x) ? x : undefined

const mkArray = x => isArray(x) ? x : []

//

const id = x => x
const snd = (_, c) => c

//

const check = (expected, predicate) => x => {
  if (predicate(x))
    return x
  else
    throw new Error(`Expected ${expected}, but got ${x}.`)
}

const assert = process.env.NODE_ENV === "production" ? () => id : check

//

const dropped = xs => {
  for (const _ in xs)
    return xs
  return undefined
}

//

const empty = {}

const deleteKey = (k, o) => {
  const r = Object.assign({}, o)
  delete r[k]
  return dropped(r)
}

const setKey = (k, v, o) => {
  const r = Object.assign({}, o)
  r[k] = v
  return r
}

//

const toPartial = transform => x => undefined === x ? x : transform(x)

//

const filtered = toPartial(xs => dropped(xs.filter(x => x !== undefined)))

//

const seemsLens = x => typeof x === "function" && x.length === 1

const lifted = assert("a lens", seemsLens)

const lift = l => {
  switch (typeof l) {
    case "string": return liftProp(l)
    case "number": return liftIndex(l)
    default:       return lifted(l)
  }
}

export const compose = (...ls) =>
  ls.length === 0 ? identity :
  ls.length === 1 ? ls[0] :
  (toCat => R.compose(...ls.map(l => lift(l)(toCat))))

export const remove = curry2((l, s) => setI(lift(l), undefined, s))

export const removeAll = curry2((lens, data) => {
  warn("`removeAll` is deprecated and will be removed in next major version --- use a different approach.")
  while (get(lens, data) !== undefined)
    data = remove(lens, data)
  return data
})

const setI = (l, x, s) => l(Ident)(() => Ident(x))(s).value
const getI = (l, s) => l(Const)(Const)(s).value
const modifyI = (l, x2x, s) => l(Ident)(y => Ident(x2x(y)))(s).value
const lensI = (getter, setter) => _c => inner => target =>
  inner(getter(target)).map(focus => setter(focus, target))
const collectI = (l, s) => l(Const)(Single)(s).value

export const lens = curry2(lensI)
export const modify = curry3((l, x2y, s) => modifyI(lift(l), x2y, s))
export const set = curry3((l, x, s) => setI(lift(l), x, s))
export const get = curry2((l, s) => getI(lift(l), s))
export const collect = curry2((l, s) =>
  warn("`collect` is experimental and might be removed, renamed or changed semantically before next major release") ||
  mkArray(filtered(collectI(lift(l), s))))

export const chain = curry2((x2yL, xL) =>
  compose(xL, choose(xO => xO === undefined ? nothing : x2yL(xO))))

export const just = x => lensI(R.always(x), snd)

export const choose = x2yL => constructor => inner => target =>
  lift(x2yL(target))(constructor)(inner)(target)

export const nothing = lensI(snd, snd)

export const orElse =
  curry2((d, l) => choose(x => getI(lift(l), x) !== undefined ? l : d))

export const choice = (...ls) => choose(x => {
  const i = ls.findIndex(l => getI(lift(l), x) !== undefined)
  return 0 <= i ? ls[i] : nothing
})

const replacer = (inn, out) => x => R.equals(x, inn) ? out : x
const normalizer = fn => _c => inner => x => inner(fn(x)).map(fn)

export const replace = curry2((inn, out) => _c => inner => x =>
  inner(replacer(inn, out)(x)).map(replacer(out, inn)))

export const defaults = out => _c => inner => x =>
  inner(x === undefined ? out : x).map(replacer(out, undefined))
export const required = inn => replace(inn, undefined)
export const define = v => normalizer(x => x === undefined ? v : x)

export const valueOr = v => _c => inner => x =>
  inner(x === undefined || x === null ? v : x)

export const normalize = transform => normalizer(toPartial(transform))

const isProp = x => typeof x === "string"

export const prop = assert("a string", isProp)

const liftProp = k => _c => inner => o => inner(isObject(o) ? o[k] : undefined).map(v => {
  const oOut = isObject(o) ? o : empty
  return v === undefined ? deleteKey(k, oOut) : setKey(k, v, oOut)
})

export const find = predicate => choose(xs => {
  if (isArray(xs)) {
    const i = xs.findIndex(predicate)
    return i < 0 ? append : i
  } else {
    return append
  }
})

export const findWith = (...ls) => {
  const lls = lift(compose(...ls))
  return compose(find(x => getI(lls, x) !== undefined), lls)
}

const isIndex = x => Number.isInteger(x) && 0 <= x

export const index = assert("a non-negative integer", isIndex)

const liftIndex = i => _c => inner => xs => inner(isArray(xs) ? xs[i] : undefined).map(x => {
  if (x === undefined) {
    if (!isArray(xs))
      return undefined
    if (xs.length <= i)
      return dropped(xs)
    const ys = xs.slice(0)
    ys.splice(i, 1)
    return dropped(ys)
  } else {
    if (!isArray(xs))
      return Array(i).concat([x])
    if (xs.length <= i)
      return xs.concat(Array(i - xs.length), [x])
    const ys = xs.slice(0)
    ys[i] = x
    return ys
  }
})

export const append = lensI(snd, (x, xs) =>
  x === undefined ? unArray(xs) : isArray(xs) ? xs.concat([x]) : [x])

export const filter = p => lensI(xs => unArray(xs) && xs.filter(p), (ys, xs) =>
  dropped(mkArray(ys).concat(mkArray(xs).filter(x => !p(x)))))

export const augment = template => lensI(
  x => {
    if (isObject(x)) {
      const z = {...x}
      for (const k in template)
        z[k] = template[k](z)
      return z
    } else {
      return undefined
    }
  },
  (y, cIn) => {
    if (isObject(y)) {
      const c = isObject(cIn) ? cIn : empty
      let z
      const set = (k, v) => {
        if (undefined === z)
          z = {}
        z[k] = v
      }
      for (const k in y) {
        if (!(k in template))
          set(k, y[k])
        else
          if (k in c)
            set(k, c[k])
      }
      return z
    } else {
      return undefined
    }
  })

export const pick = template => lensI(
  c => {
    let r
    for (const k in template) {
      const v = getI(lift(template[k]), c)
      if (v !== undefined) {
        if (r === undefined)
          r = {}
        r[k] = v
      }
    }
    return r
  },
  (o = empty, cIn) => {
    let c = cIn
    for (const k in template)
      c = setI(lift(template[k]), o[k], c)
    return c
  })

export const identity = _c => inner => inner

export const props = (...ks) => pick(R.zipObj(ks, ks))

const show = (...labels) => x => console.log(...labels, x) || x

export const log = (...labels) =>
  lensI(show(...labels, "get"), show(...labels, "set"))

export const sequence = constructor => inner => target =>
  warn("`sequence` is experimental and might be removed, renamed or changed semantically before next major release") ||
  R.traverse(constructor, inner, mkArray(target))
  .map(filtered)

export const optional =
  compose(lensI(toPartial(x => [x]),
                toPartial(([x]) => x)),
          sequence)

export const fromRamda = l => _c => l
const fantasy = () => {throw new Error("Sorry, `toRamda` is only fantasy!")}
export const toRamda = l => lift(l)(fantasy)

export const fromArrayBy = id =>
  warn("`fromArrayBy` is experimental and might be removed, renamed or changed semantically before next major release") ||
  lensI(xs => {
    if (isArray(xs)) {
      const o = {}
      for (let i=0, n=xs.length; i<n; ++i) {
        const x = xs[i]
        o[x[id]] = x
      }
      return o
    }
  },
  o => isObject(o) ? R.values(o) : undefined)

export default compose
<commit_msg>Minor opt<commit_after>import * as R from "ramda"

//

function Identity(value) {this.value = value}
const Ident = x => new Identity(x)
Identity.prototype.map = function (x2y) {return new Identity(x2y(this.value))}
Identity.prototype.of = Ident
Identity.prototype.ap = function (x) {return new Identity(this.value(x.value))}

//

function Constant(value) {this.value = value}
const Const = x => new Constant(x)
const Single = x => Const([x])
Constant.prototype.map = function () {return this}
Constant.prototype.of = Const
Constant.prototype.ap = function (x) {return new Const(R.concat(this.value, x.value))}

//

const warn = process.env.NODE_ENV === "production" ? () => {} : (() => {
  const warned = {}

  return message => {
    if (!(message in warned)) {
      warned[message] = message
      console.warn("partial.lenses:", message)
    }
  }
})()

//

const curry2 = fn => function (a, b) {
  switch (arguments.length) {
    case 1:  return b => fn(a, b)
    default: return fn(a, b)
  }
}

const curry3 = fn => function (a, b, c) {
  switch (arguments.length) {
    case 1:  return curry2((b, c) => fn(a, b, c))
    case 2:  return c => fn(a, b, c)
    default: return fn(a, b, c)
  }
}

//

const isArray  = x => x && x.constructor === Array
const isObject = x => x && x.constructor === Object

const unArray  = x =>  isArray(x) ? x : undefined

const mkArray = x => isArray(x) ? x : []

//

const id = x => x
const snd = (_, c) => c

//

const check = (expected, predicate) => x => {
  if (predicate(x))
    return x
  else
    throw new Error(`Expected ${expected}, but got ${x}.`)
}

const assert = process.env.NODE_ENV === "production" ? () => id : check

//

const dropped = xs => {
  for (const _ in xs)
    return xs
  return undefined
}

//

const empty = {}

const deleteKey = (k, o) => {
  const r = Object.assign({}, o)
  delete r[k]
  return dropped(r)
}

const setKey = (k, v, o) => {
  const r = Object.assign({}, o)
  r[k] = v
  return r
}

//

const toPartial = transform => x => undefined === x ? x : transform(x)

//

const filtered = toPartial(xs => dropped(xs.filter(x => x !== undefined)))

//

const seemsLens = x => typeof x === "function" && x.length === 1

const lifted = assert("a lens", seemsLens)

const lift = l => {
  switch (typeof l) {
    case "string": return liftProp(l)
    case "number": return liftIndex(l)
    default:       return lifted(l)
  }
}

export const compose = (...ls) =>
  ls.length === 0 ? identity :
  ls.length === 1 ? ls[0] :
  (toCat => R.compose(...ls.map(l => lift(l)(toCat))))

export const remove = curry2((l, s) => setI(lift(l), undefined, s))

export const removeAll = curry2((lens, data) => {
  warn("`removeAll` is deprecated and will be removed in next major version --- use a different approach.")
  while (get(lens, data) !== undefined)
    data = remove(lens, data)
  return data
})

const setI = (l, x, s) => l(Ident)(() => Ident(x))(s).value
const getI = (l, s) => l(Const)(Const)(s).value
const modifyI = (l, x2x, s) => l(Ident)(y => Ident(x2x(y)))(s).value
const lensI = (getter, setter) => _c => inner => target =>
  inner(getter(target)).map(focus => setter(focus, target))
const collectI = (l, s) => l(Const)(Single)(s).value

export const lens = curry2(lensI)
export const modify = curry3((l, x2y, s) => modifyI(lift(l), x2y, s))
export const set = curry3((l, x, s) => setI(lift(l), x, s))
export const get = curry2((l, s) => {
  switch (typeof l) {
    case "string": return getProp(l, s)
    case "number": return getIndex(l, s)
    default:       return getI(lifted(l), s)
  }
})
export const collect = curry2((l, s) =>
  warn("`collect` is experimental and might be removed, renamed or changed semantically before next major release") ||
  mkArray(filtered(collectI(lift(l), s))))

export const chain = curry2((x2yL, xL) =>
  compose(xL, choose(xO => xO === undefined ? nothing : x2yL(xO))))

export const just = x => lensI(R.always(x), snd)

export const choose = x2yL => constructor => inner => target =>
  lift(x2yL(target))(constructor)(inner)(target)

export const nothing = lensI(snd, snd)

export const orElse =
  curry2((d, l) => choose(x => getI(lift(l), x) !== undefined ? l : d))

export const choice = (...ls) => choose(x => {
  const i = ls.findIndex(l => getI(lift(l), x) !== undefined)
  return 0 <= i ? ls[i] : nothing
})

const replacer = (inn, out) => x => R.equals(x, inn) ? out : x
const normalizer = fn => _c => inner => x => inner(fn(x)).map(fn)

export const replace = curry2((inn, out) => _c => inner => x =>
  inner(replacer(inn, out)(x)).map(replacer(out, inn)))

export const defaults = out => _c => inner => x =>
  inner(x === undefined ? out : x).map(replacer(out, undefined))
export const required = inn => replace(inn, undefined)
export const define = v => normalizer(x => x === undefined ? v : x)

export const valueOr = v => _c => inner => x =>
  inner(x === undefined || x === null ? v : x)

export const normalize = transform => normalizer(toPartial(transform))

const isProp = x => typeof x === "string"

export const prop = assert("a string", isProp)

const getProp = (k, o) => isObject(o) ? o[k] : undefined
const liftProp = k => _c => inner => o => inner(getProp(k, o)).map(v => {
  const oOut = isObject(o) ? o : empty
  return v === undefined ? deleteKey(k, oOut) : setKey(k, v, oOut)
})

export const find = predicate => choose(xs => {
  if (isArray(xs)) {
    const i = xs.findIndex(predicate)
    return i < 0 ? append : i
  } else {
    return append
  }
})

export const findWith = (...ls) => {
  const lls = lift(compose(...ls))
  return compose(find(x => getI(lls, x) !== undefined), lls)
}

const isIndex = x => Number.isInteger(x) && 0 <= x

export const index = assert("a non-negative integer", isIndex)

const getIndex = (i, xs) => isArray(xs) ? xs[i] : undefined
const liftIndex = i => _c => inner => xs => inner(getIndex(i, xs)).map(x => {
  if (x === undefined) {
    if (!isArray(xs))
      return undefined
    if (xs.length <= i)
      return dropped(xs)
    const ys = xs.slice(0)
    ys.splice(i, 1)
    return dropped(ys)
  } else {
    if (!isArray(xs))
      return Array(i).concat([x])
    if (xs.length <= i)
      return xs.concat(Array(i - xs.length), [x])
    const ys = xs.slice(0)
    ys[i] = x
    return ys
  }
})

export const append = lensI(snd, (x, xs) =>
  x === undefined ? unArray(xs) : isArray(xs) ? xs.concat([x]) : [x])

export const filter = p => lensI(xs => unArray(xs) && xs.filter(p), (ys, xs) =>
  dropped(mkArray(ys).concat(mkArray(xs).filter(x => !p(x)))))

export const augment = template => lensI(
  x => {
    if (isObject(x)) {
      const z = {...x}
      for (const k in template)
        z[k] = template[k](z)
      return z
    } else {
      return undefined
    }
  },
  (y, cIn) => {
    if (isObject(y)) {
      const c = isObject(cIn) ? cIn : empty
      let z
      const set = (k, v) => {
        if (undefined === z)
          z = {}
        z[k] = v
      }
      for (const k in y) {
        if (!(k in template))
          set(k, y[k])
        else
          if (k in c)
            set(k, c[k])
      }
      return z
    } else {
      return undefined
    }
  })

export const pick = template => lensI(
  c => {
    let r
    for (const k in template) {
      const v = getI(lift(template[k]), c)
      if (v !== undefined) {
        if (r === undefined)
          r = {}
        r[k] = v
      }
    }
    return r
  },
  (o = empty, cIn) => {
    let c = cIn
    for (const k in template)
      c = setI(lift(template[k]), o[k], c)
    return c
  })

export const identity = _c => inner => inner

export const props = (...ks) => pick(R.zipObj(ks, ks))

const show = (...labels) => x => console.log(...labels, x) || x

export const log = (...labels) =>
  lensI(show(...labels, "get"), show(...labels, "set"))

export const sequence = constructor => inner => target =>
  warn("`sequence` is experimental and might be removed, renamed or changed semantically before next major release") ||
  R.traverse(constructor, inner, mkArray(target))
  .map(filtered)

export const optional =
  compose(lensI(toPartial(x => [x]),
                toPartial(([x]) => x)),
          sequence)

export const fromRamda = l => _c => l
const fantasy = () => {throw new Error("Sorry, `toRamda` is only fantasy!")}
export const toRamda = l => lift(l)(fantasy)

export const fromArrayBy = id =>
  warn("`fromArrayBy` is experimental and might be removed, renamed or changed semantically before next major release") ||
  lensI(xs => {
    if (isArray(xs)) {
      const o = {}
      for (let i=0, n=xs.length; i<n; ++i) {
        const x = xs[i]
        o[x[id]] = x
      }
      return o
    }
  },
  o => isObject(o) ? R.values(o) : undefined)

export default compose
<|endoftext|>