<commit_before>/*
 Copyright (C) 2011 Sencha Inc.

 Author: Ariya Hidayat.

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

/*jslint continue: true, indent: 4 */

function cssbeautify(style, opt) {
    "use strict";
    var options, index = 0, length = style.length, formatted = '',
        ch, ch2, str, state, State,
        openbrace = ' {',
        trimRight;

    options = arguments.length > 1 ? opt : {};
    if (typeof options.indent === 'undefined') {
        options.indent = '    ';
    }
    if (typeof options.openbrace === 'string') {
        openbrace = (options.openbrace === 'end-of-line') ? ' {' : '\n{';
    }

    function isWhitespace(c) {
        return ' \t\n\r\f'.indexOf(c) >= 0;
    }

    if (String.prototype.trimRight) {
        trimRight = function (s) {
            return s.trimRight();
        };
    } else {
        // old Internet Explorer
        trimRight = function (s) {
            return s.replace(/\s+$/, '');
        };
    }

    State = {
        Start: 0,
        BlockComment: 1,
        Selectors: 2,
        Ruleset: 3,
        PropertyName: 4,
        Separator: 5,
        PropertyValue: 6,
        SingleQuotedString: 7,
        DoubleQuotedString: 8
    };
    state = State.Start;

    // We want to deal with LF (\n) only
    style = style.replace(/\r\n/g, '\n');

    while (index < length) {
        ch = style.charAt(index);
        ch2 = style.charAt(index + 1);
        index += 1;

        if (state === State.Start) {

            // Copy white spaces and control characters
            if (ch <= ' ' || ch.charCodeAt(0) >= 128) {
                state = State.Start;
                formatted += ch;
                continue;
            }

            // Block comment
            if (ch === '/' && ch2 === '*') {
                state = State.BlockComment;
                formatted += ch;
                formatted += ch2;
                index += 1;
                continue;
            }

            // Selectors
            // FIXME: handle Unicode characters
            if ((ch >= 'a' && ch <= 'z') ||
                    (ch >= 'A' && ch <= 'Z') ||
                    (ch >= '0' && ch <= '9') ||
                    (ch === '-') || (ch === '_') ||
                    (ch === '-') || (ch === '_') ||
                    (ch === '*') ||
                    (ch === '.') || (ch === ':')) {

                // Clear trailing whitespaces and linefeeds.
                str = trimRight(formatted);

                // After finishing a ruleset, there should be one blank line.
                if (str.charAt(str.length - 1) === '}') {
                    formatted = str + '\n\n';
                } else {
                    // After block comment, keep all the linefeeds but
                    // start from the first column (remove whitespaces prefix).
                    while (true) {
                        ch2 = formatted.charAt(formatted.length - 1);
                        if (ch2 !== ' ' && ch2.charCodeAt(0) !== 9) {
                            break;
                        }
                        formatted = formatted.substr(0, formatted.length - 1);
                    }
                }
                formatted += ch;
                state = State.Selectors;
                continue;
            }
        }

        if (state ===  State.BlockComment) {
            // Continue until we hit the final marker '*/' (star, forward slash).
            formatted += ch;
            if (ch === '*' && ch2 === '/') {
                state = State.Start;
                formatted += ch2;
                index += 1;
            }
            continue;
        }

        if (state === State.Selectors) {
            // Continue until we hit '{'
            if (ch === '{') {
                formatted = trimRight(formatted);
                formatted += openbrace;
                if (ch2 !== '\n') {
                    formatted += '\n';
                }
                state = State.Ruleset;
            } else {
                formatted += ch;
            }
            continue;
        }

        if (state === State.Ruleset) {
            // Continue until we hit '}'
            if (ch === '}') {
                formatted = trimRight(formatted);
                formatted += '\n}';
                state = State.Start;
                continue;
            }
            // Make sure there is no blank line or trailing spaces inbetween
            if (ch === '\n') {
                formatted = trimRight(formatted);
                formatted += '\n';
                continue;
            }
            // property name
            if (!isWhitespace(ch)) {
                formatted = trimRight(formatted);
                formatted += '\n';
                formatted += options.indent;
                formatted += ch;
                state = State.PropertyName;
                continue;
            }
            formatted += ch;
            continue;
        }

        if (state === State.PropertyName) {
            // Continue until we hit ':'
            if (ch === ':') {
                formatted = trimRight(formatted);
                formatted += ': ';
                state = State.Separator;
                continue;
            }
            // or until we hit '}'
            if (ch === '}') {
                formatted = trimRight(formatted);
                formatted += '\n}';
                state = State.Start;
                continue;
            }
            formatted += ch;
            continue;
        }

        if (state === State.Separator) {
            if (!isWhitespace(ch)) {
                formatted += ch;
                if (ch === '\'') {
                    state = State.SingleQuotedString;
                    continue;
                } else if (ch === '"') {
                    state = State.DoubleQuotedString;
                    continue;
                }
                state = State.PropertyValue;
                continue;
            }
            continue;
        }

        if (state === State.PropertyValue) {
            if (ch === '\'') {
                formatted += ch;
                state = State.SingleQuotedString;
                continue;
            }
            if (ch === '"') {
                formatted += ch;
                state = State.DoubleQuotedString;
                continue;
            }
            // Continue until we hit ';''
            if (ch === ';') {
                formatted = trimRight(formatted);
                formatted += ';\n';
                state = State.Ruleset;
                continue;
            }
            // or until we hit '}'
            if (ch === '}') {
                formatted = trimRight(formatted);
                formatted += '\n}';
                state = State.Start;
                continue;
            }
            formatted += ch;
            continue;
        }

        // TODO: fully implement http://www.w3.org/TR/CSS2/syndata.html#strings
        if (state === State.SingleQuotedString) {
            // Continue until we hit another single quote
            if (ch === '\'') {
                state = State.PropertyValue;
                formatted += ch;
                continue;
            }
            formatted += ch;
            continue;
        }

        // TODO: fully implement http://www.w3.org/TR/CSS2/syndata.html#strings
        if (state === State.DoubleQuotedString) {
            // Continue until we hit another double quote
            if (ch === '"') {
                state = State.PropertyValue;
                formatted += ch;
                continue;
            }
            formatted += ch;
            continue;
        }

        // The default action is to copy the character (to prevent
        // infinite loop).
        formatted += ch;
    }

    return formatted;
}
<commit_msg>Recognize quoted string in the selector.<commit_after>/*
 Copyright (C) 2011 Sencha Inc.

 Author: Ariya Hidayat.

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

/*jslint continue: true, indent: 4 */

function cssbeautify(style, opt) {
    "use strict";
    var options, index = 0, length = style.length, formatted = '',
        ch, ch2, str, state, State,
        openbrace = ' {',
        trimRight;

    options = arguments.length > 1 ? opt : {};
    if (typeof options.indent === 'undefined') {
        options.indent = '    ';
    }
    if (typeof options.openbrace === 'string') {
        openbrace = (options.openbrace === 'end-of-line') ? ' {' : '\n{';
    }

    function isWhitespace(c) {
        return ' \t\n\r\f'.indexOf(c) >= 0;
    }

    if (String.prototype.trimRight) {
        trimRight = function (s) {
            return s.trimRight();
        };
    } else {
        // old Internet Explorer
        trimRight = function (s) {
            return s.replace(/\s+$/, '');
        };
    }

    State = {
        Start: 0,
        BlockComment: 1,
        Selectors: 2,
        QuotedStringSelector: 3,
        Ruleset: 4,
        PropertyName: 5,
        Separator: 6,
        PropertyValue: 7,
        SingleQuotedString: 8,
        DoubleQuotedString: 9
    };
    state = State.Start;

    // We want to deal with LF (\n) only
    style = style.replace(/\r\n/g, '\n');

    while (index < length) {
        ch = style.charAt(index);
        ch2 = style.charAt(index + 1);
        index += 1;

        if (state === State.Start) {

            // Copy white spaces and control characters
            if (ch <= ' ' || ch.charCodeAt(0) >= 128) {
                state = State.Start;
                formatted += ch;
                continue;
            }

            // Block comment
            if (ch === '/' && ch2 === '*') {
                state = State.BlockComment;
                formatted += ch;
                formatted += ch2;
                index += 1;
                continue;
            }

            // Selectors
            // FIXME: handle Unicode characters
            if ((ch >= 'a' && ch <= 'z') ||
                    (ch >= 'A' && ch <= 'Z') ||
                    (ch >= '0' && ch <= '9') ||
                    (ch === '-') || (ch === '_') ||
                    (ch === '-') || (ch === '_') ||
                    (ch === '*') ||
                    (ch === '.') || (ch === ':')) {

                // Clear trailing whitespaces and linefeeds.
                str = trimRight(formatted);

                // After finishing a ruleset, there should be one blank line.
                if (str.charAt(str.length - 1) === '}') {
                    formatted = str + '\n\n';
                } else {
                    // After block comment, keep all the linefeeds but
                    // start from the first column (remove whitespaces prefix).
                    while (true) {
                        ch2 = formatted.charAt(formatted.length - 1);
                        if (ch2 !== ' ' && ch2.charCodeAt(0) !== 9) {
                            break;
                        }
                        formatted = formatted.substr(0, formatted.length - 1);
                    }
                }
                formatted += ch;
                state = State.Selectors;
                continue;
            }
        }

        if (state ===  State.BlockComment) {
            // Continue until we hit the final marker '*/' (star, forward slash).
            formatted += ch;
            if (ch === '*' && ch2 === '/') {
                state = State.Start;
                formatted += ch2;
                index += 1;
            }
            continue;
        }

        if (state === State.Selectors) {
            // Skip any quoted string
            if (ch === '"') {
                formatted += ch;
                state = State.QuotedStringSelector;
                continue;
            }

            // Continue until we hit '{'
            if (ch === '{') {
                formatted = trimRight(formatted);
                formatted += openbrace;
                if (ch2 !== '\n') {
                    formatted += '\n';
                }
                state = State.Ruleset;
            } else {
                formatted += ch;
            }
            continue;
        }

        if (state === State.QuotedStringSelector) {
            // Continue until we hit another double quote
            if (ch === '"') {
                state = State.Selectors;
                formatted += ch;
                continue;
            }
            formatted += ch;
            continue;
        }

        if (state === State.Ruleset) {
            // Continue until we hit '}'
            if (ch === '}') {
                formatted = trimRight(formatted);
                formatted += '\n}';
                state = State.Start;
                continue;
            }
            // Make sure there is no blank line or trailing spaces inbetween
            if (ch === '\n') {
                formatted = trimRight(formatted);
                formatted += '\n';
                continue;
            }
            // property name
            if (!isWhitespace(ch)) {
                formatted = trimRight(formatted);
                formatted += '\n';
                formatted += options.indent;
                formatted += ch;
                state = State.PropertyName;
                continue;
            }
            formatted += ch;
            continue;
        }

        if (state === State.PropertyName) {
            // Continue until we hit ':'
            if (ch === ':') {
                formatted = trimRight(formatted);
                formatted += ': ';
                state = State.Separator;
                continue;
            }
            // or until we hit '}'
            if (ch === '}') {
                formatted = trimRight(formatted);
                formatted += '\n}';
                state = State.Start;
                continue;
            }
            formatted += ch;
            continue;
        }

        if (state === State.Separator) {
            if (!isWhitespace(ch)) {
                formatted += ch;
                if (ch === '\'') {
                    state = State.SingleQuotedString;
                    continue;
                } else if (ch === '"') {
                    state = State.DoubleQuotedString;
                    continue;
                }
                state = State.PropertyValue;
                continue;
            }
            continue;
        }

        if (state === State.PropertyValue) {
            if (ch === '\'') {
                formatted += ch;
                state = State.SingleQuotedString;
                continue;
            }
            if (ch === '"') {
                formatted += ch;
                state = State.DoubleQuotedString;
                continue;
            }
            // Continue until we hit ';''
            if (ch === ';') {
                formatted = trimRight(formatted);
                formatted += ';\n';
                state = State.Ruleset;
                continue;
            }
            // or until we hit '}'
            if (ch === '}') {
                formatted = trimRight(formatted);
                formatted += '\n}';
                state = State.Start;
                continue;
            }
            formatted += ch;
            continue;
        }

        // TODO: fully implement http://www.w3.org/TR/CSS2/syndata.html#strings
        if (state === State.SingleQuotedString) {
            // Continue until we hit another single quote
            if (ch === '\'') {
                state = State.PropertyValue;
                formatted += ch;
                continue;
            }
            formatted += ch;
            continue;
        }

        // TODO: fully implement http://www.w3.org/TR/CSS2/syndata.html#strings
        if (state === State.DoubleQuotedString) {
            // Continue until we hit another double quote
            if (ch === '"') {
                state = State.PropertyValue;
                formatted += ch;
                continue;
            }
            formatted += ch;
            continue;
        }

        // The default action is to copy the character (to prevent
        // infinite loop).
        formatted += ch;
    }

    return formatted;
}
<|endoftext|>


<commit_before>/*
 Import all product specific js
 */
import $ from 'jquery';
import utils from '@bigcommerce/stencil-utils';
import 'foundation-sites/js/foundation/foundation';
import 'foundation-sites/js/foundation/foundation.reveal';
import ImageGallery from '../product/image-gallery';
import modalFactory from '../global/modal';
import _ from 'lodash';

// We want to ensure that the events are bound to a single instance of the product details component
let previewModal = null;
let productSingleton = null;

utils.hooks.on('cart-item-add', (event, form) => {
    if (productSingleton) {
        productSingleton.addProductToCart(event, form);
    }
});

utils.hooks.on('product-option-change', (event, changedOption) => {
    if (productSingleton) {
        productSingleton.productOptionsChanged(event, changedOption);
    }
});

export default class Product {
    constructor($scope, context) {
        const productAttributesData = window.BCData.product_attributes || {};

        this.$scope = $scope;
        this.context = context;
        this.imageGallery = new ImageGallery($('[data-image-gallery]', this.$scope));
        this.imageGallery.init();
        this.listenQuantityChange();
        this.initRadioAttributes();
        this.updateProductAttributes(productAttributesData);

        previewModal = modalFactory('#previewModal')[0];
        productSingleton = this;
    }

    /**
     * Since $productView can be dynamically inserted using render_with,
     * We have to retrieve the respective elements
     *
     * @param $scope
     */
    getViewModel($scope) {
        return {
            $priceWithTax: $('[data-product-price-with-tax]', $scope),
            $rrpWithTax: $('[data-product-rrp-with-tax]', $scope),
            $priceWithoutTax: $('[data-product-price-without-tax]', $scope),
            $rrpWithoutTax: $('[data-product-rrp-without-tax]', $scope),
            $weight: $('.productView-info [data-product-weight]', $scope),
            $increments: $('.form-field--increments :input', $scope),
            $addToCart: $('#form-action-addToCart', $scope),
            $wishlistVariation: $('[data-wishlist-add] [name="variation_id"]', $scope),
            stock: {
                $container: $('.form-field--stock', $scope),
                $input: $('[data-product-stock]', $scope),
            },
            $sku: $('[data-product-sku]'),
            quantity: {
                $text: $('.incrementTotal', $scope),
                $input: $('[name=qty\\[\\]]', $scope),
            },
        };
    }

    /**
     *
     * Handle product options changes
     *
     */
    productOptionsChanged(event, changedOption) {
        const $changedOption = $(changedOption);
        const $form = $changedOption.parents('form');
        const productId = $('[name="product_id"]', $form).val();

        // Do not trigger an ajax request if it's a file or if the browser doesn't support FormData
        if ($changedOption.attr('type') === 'file' || window.FormData === undefined) {
            return;
        }

        utils.api.productAttributes.optionChange(productId, $form.serialize(), (err, response) => {
            const viewModel = this.getViewModel(this.$scope);
            const $messageBox = $('.productAttributes-message');
            const data = response.data || {};

            this.updateProductAttributes(data);

            if (data.purchasing_message) {
                $('.alertBox-message', $messageBox).text(data.purchasing_message);
                $messageBox.show();
            } else {
                $messageBox.hide();
            }

            if (_.isObject(data.price)) {
                if (data.price.with_tax) {
                    viewModel.$priceWithTax.html(data.price.with_tax.formatted);
                }

                if (data.price.without_tax) {
                    viewModel.$priceWithoutTax.html(data.price.without_tax.formatted);
                }

                if (data.price.rrp_with_tax) {
                    viewModel.$rrpWithTax.html(data.price.rrp_with_tax.formatted);
                }

                if (data.price.rrp_without_tax) {
                    viewModel.$rrpWithoutTax.html(data.price.rrp_without_tax.formatted);
                }
            }

            if (_.isObject(data.weight)) {
                viewModel.$weight.html(data.weight.formatted);
            }

            // Set variation_id if it exists for adding to wishlist
            if (data.variantId) {
                viewModel.$wishlistVariation.val(data.variantId);
            }

            // If SKU is available
            if (data.sku) {
                viewModel.$sku.text(data.sku);
            }

            // if stock view is on (CP settings)
            if (viewModel.stock.$container.length && _.isNumber(data.stock)) {
                // if the stock container is hidden, show
                viewModel.stock.$container.removeClass('u-hiddenVisually');
                viewModel.stock.$input.text(data.stock);
            }

            if (!data.purchasable || !data.instock) {
                viewModel.$addToCart.prop('disabled', true);
                viewModel.$increments.prop('disabled', true);
            } else {
                viewModel.$addToCart.prop('disabled', false);
                viewModel.$increments.prop('disabled', false);
            }
        });
    }

    showProductImage(image) {
        if (_.isPlainObject(image)) {
            const zoomImageUrl = utils.tools.image.getSrc(
                image.data,
                this.context.themeImageSizes.zoom
            );

            const mainImageUrl = utils.tools.image.getSrc(
                image.data,
                this.context.themeImageSizes.product
            );

            this.imageGallery.setAlternateImage({
                mainImageUrl,
                zoomImageUrl,
            });
        } else {
            this.imageGallery.restoreImage();
        }
    }

    /**
     *
     * Handle action when the shopper clicks on + / - for quantity
     *
     */
    listenQuantityChange() {
        this.$scope.on('click', '[data-quantity-change] button', (event) => {
            event.preventDefault();
            const $target = $(event.currentTarget);
            const viewModel = this.getViewModel(this.$scope);
            const $input = viewModel.quantity.$input;
            const quantityMin = parseInt($input.data('quantity-min'), 10);
            const quantityMax = parseInt($input.data('quantity-max'), 10);

            let qty = parseInt($input.val(), 10);

            // If action is incrementing
            if ($target.data('action') === 'inc') {
                // If quantity max option is set
                if (quantityMax > 0) {
                    // Check quantity does not exceed max
                    if ((qty + 1) <= quantityMax) {
                        qty++;
                    }
                } else {
                    qty++;
                }
            } else if (qty > 1) {
                // If quantity min option is set
                if (quantityMin > 0) {
                    // Check quantity does not fall below min
                    if ((qty - 1) >= quantityMin) {
                        qty--;
                    }
                } else {
                    qty--;
                }
            }

            // update hidden input
            viewModel.quantity.$input.val(qty);
            // update text
            viewModel.quantity.$text.text(qty);
        });
    }

    /**
     *
     * Add a product to cart
     *
     */
    addProductToCart(event, form) {
        const $addToCartBtn = $('#form-action-addToCart', $(event.target));
        const originalBtnVal = $addToCartBtn.val();
        const waitMessage = $addToCartBtn.data('waitMessage');

        // Do not do AJAX if browser doesn't support FormData
        if (window.FormData === undefined) {
            return;
        }

        // Prevent default
        event.preventDefault();

        $addToCartBtn
            .val(waitMessage)
            .prop('disabled', true);

        // Add item to cart
        utils.api.cart.itemAdd(new FormData(form), (err, response) => {
            const errorMessage = err || response.data.error;

            $addToCartBtn
                .val(originalBtnVal)
                .prop('disabled', false);

            // Guard statement
            if (errorMessage) {
                // Strip the HTML from the error message
                const tmp = document.createElement('DIV');
                tmp.innerHTML = errorMessage;

                alert(tmp.textContent || tmp.innerText);

                return;
            }

            // Open preview modal and update content
            previewModal.open();

            this.updateCartContent(previewModal, response.data.cart_item.hash);
        });
    }

    /**
     * Get cart contents
     *
     * @param {String} cartItemHash
     * @param {Function} onComplete
     */
    getCartContent(cartItemHash, onComplete) {
        const options = {
            template: 'cart/preview',
            params: {
                suggest: cartItemHash,
            },
            config: {
                cart: {
                    suggestions: {
                        limit: 4,
                    },
                },
            },
        };

        utils.api.cart.getContent(options, onComplete);
    }

    /**
     * Update cart content
     *
     * @param {Modal} modal
     * @param {String} cartItemHash
     * @param {Function} onComplete
     */
    updateCartContent(modal, cartItemHash, onComplete) {
        this.getCartContent(cartItemHash, (err, response) => {
            if (err) {
                return;
            }

            modal.updateContent(response);

            // Update cart counter
            const $body = $('body');
            const $cartQuantity = $('[data-cart-quantity]', modal.$content);
            const $cartCounter = $('.navUser-action .cart-count');
            const quantity = $cartQuantity.data('cart-quantity') || 0;

            $cartCounter.addClass('cart-count--positive');
            $body.trigger('cart-quantity-update', quantity);

            if (onComplete) {
                onComplete(response);
            }
        });
    }

    /**
     * Hide or mark as unavailable out of stock attributes if enabled
     * @param  {Object} data
     */
    updateProductAttributes(data) {
        const behavior = data.out_of_stock_behavior;
        const inStockIds = data.in_stock_attributes;
        const outOfStockMessage = ` (${data.out_of_stock_message})`;

        this.showProductImage(data.image);

        if (behavior !== 'hide_option' && behavior !== 'label_option') {
            return;
        }

        $('[data-product-attribute-value]', this.$scope).each((i, attribute) => {
            const $attribute = $(attribute);
            const attrId = parseInt($attribute.data('product-attribute-value'), 10);

            if (inStockIds.indexOf(attrId) !== -1) {
                this.enableAttribute($attribute, behavior, outOfStockMessage);
            } else {
                this.disableAttribute($attribute, behavior, outOfStockMessage);
            }
        });
    }

    disableAttribute($attribute, behavior, outOfStockMessage) {
        const attrType = this.getAttributeType($attribute);
        let $select = null;

        if (behavior === 'hide_option') {
            $attribute.hide();

            // If the attribute is the selected option in a select dropdown, select the first option (MERC-639)
            if (attrType === 'set-select' && $attribute.parent().val() === $attribute.attr('value')) {
                $select = $attribute.parent();
                $select[0].selectedIndex = 0;
            }
        } else {
            if (attrType === 'set-select') {
                $attribute.html($attribute.html().replace(outOfStockMessage, '') + outOfStockMessage);
            } else {
                $attribute.addClass('unavailable');
            }
        }
    }

    enableAttribute($attribute, behavior, outOfStockMessage) {
        if (behavior === 'hide_option') {
            $attribute.show();
        } else {
            if (this.getAttributeType($attribute) === 'set-select') {
                $attribute.html($attribute.html().replace(outOfStockMessage, ''));
            } else {
                $attribute.removeClass('unavailable');
            }
        }
    }

    getAttributeType($attribute) {
        const $parent = $attribute.closest('[data-product-attribute]');

        return $parent ? $parent.data('product-attribute') : null;
    }

    /**
     * Allow radio buttons to get deselected
     */
    initRadioAttributes() {
        $('[data-product-attribute] input[type="radio"]', this.$scope).each((i, radio) => {
            const $radio = $(radio);

            // Only bind to click once
            if ($radio.attr('data-state') !== undefined) {
                $radio.click(() => {
                    if ($radio.data('state') === true) {
                        $radio.prop('checked', false);
                        $radio.data('state', false);

                        $radio.change();
                    } else {
                        $radio.data('state', true);
                    }

                    this.initRadioAttributes();
                });
            }

            $radio.attr('data-state', $radio.prop('checked'));
        });
    }
}
<commit_msg>MERC-678 display OOS message and update view when the default attributes are OOS<commit_after>/*
 Import all product specific js
 */
import $ from 'jquery';
import utils from '@bigcommerce/stencil-utils';
import 'foundation-sites/js/foundation/foundation';
import 'foundation-sites/js/foundation/foundation.reveal';
import ImageGallery from '../product/image-gallery';
import modalFactory from '../global/modal';
import _ from 'lodash';

// We want to ensure that the events are bound to a single instance of the product details component
let previewModal = null;
let productSingleton = null;

utils.hooks.on('cart-item-add', (event, form) => {
    if (productSingleton) {
        productSingleton.addProductToCart(event, form);
    }
});

utils.hooks.on('product-option-change', (event, changedOption) => {
    if (productSingleton) {
        productSingleton.productOptionsChanged(event, changedOption);
    }
});

export default class Product {
    constructor($scope, context) {
        const productAttributesData = window.BCData.product_attributes || {};

        this.$scope = $scope;
        this.context = context;
        this.imageGallery = new ImageGallery($('[data-image-gallery]', this.$scope));
        this.imageGallery.init();
        this.listenQuantityChange();
        this.initRadioAttributes();
        this.updateProductAttributes(productAttributesData);
        this.updateView(productAttributesData);

        previewModal = modalFactory('#previewModal')[0];
        productSingleton = this;
    }

    /**
     * Since $productView can be dynamically inserted using render_with,
     * We have to retrieve the respective elements
     *
     * @param $scope
     */
    getViewModel($scope) {
        return {
            $priceWithTax: $('[data-product-price-with-tax]', $scope),
            $rrpWithTax: $('[data-product-rrp-with-tax]', $scope),
            $priceWithoutTax: $('[data-product-price-without-tax]', $scope),
            $rrpWithoutTax: $('[data-product-rrp-without-tax]', $scope),
            $weight: $('.productView-info [data-product-weight]', $scope),
            $increments: $('.form-field--increments :input', $scope),
            $addToCart: $('#form-action-addToCart', $scope),
            $wishlistVariation: $('[data-wishlist-add] [name="variation_id"]', $scope),
            stock: {
                $container: $('.form-field--stock', $scope),
                $input: $('[data-product-stock]', $scope),
            },
            $sku: $('[data-product-sku]'),
            quantity: {
                $text: $('.incrementTotal', $scope),
                $input: $('[name=qty\\[\\]]', $scope),
            },
        };
    }

    /**
     *
     * Handle product options changes
     *
     */
    productOptionsChanged(event, changedOption) {
        const $changedOption = $(changedOption);
        const $form = $changedOption.parents('form');
        const productId = $('[name="product_id"]', $form).val();

        // Do not trigger an ajax request if it's a file or if the browser doesn't support FormData
        if ($changedOption.attr('type') === 'file' || window.FormData === undefined) {
            return;
        }

        utils.api.productAttributes.optionChange(productId, $form.serialize(), (err, response) => {
            const productAttributesData = response.data || {};

            this.updateProductAttributes(productAttributesData);
            this.updateView(productAttributesData);
        });
    }

    showProductImage(image) {
        if (_.isPlainObject(image)) {
            const zoomImageUrl = utils.tools.image.getSrc(
                image.data,
                this.context.themeImageSizes.zoom
            );

            const mainImageUrl = utils.tools.image.getSrc(
                image.data,
                this.context.themeImageSizes.product
            );

            this.imageGallery.setAlternateImage({
                mainImageUrl,
                zoomImageUrl,
            });
        } else {
            this.imageGallery.restoreImage();
        }
    }

    /**
     *
     * Handle action when the shopper clicks on + / - for quantity
     *
     */
    listenQuantityChange() {
        this.$scope.on('click', '[data-quantity-change] button', (event) => {
            event.preventDefault();
            const $target = $(event.currentTarget);
            const viewModel = this.getViewModel(this.$scope);
            const $input = viewModel.quantity.$input;
            const quantityMin = parseInt($input.data('quantity-min'), 10);
            const quantityMax = parseInt($input.data('quantity-max'), 10);

            let qty = parseInt($input.val(), 10);

            // If action is incrementing
            if ($target.data('action') === 'inc') {
                // If quantity max option is set
                if (quantityMax > 0) {
                    // Check quantity does not exceed max
                    if ((qty + 1) <= quantityMax) {
                        qty++;
                    }
                } else {
                    qty++;
                }
            } else if (qty > 1) {
                // If quantity min option is set
                if (quantityMin > 0) {
                    // Check quantity does not fall below min
                    if ((qty - 1) >= quantityMin) {
                        qty--;
                    }
                } else {
                    qty--;
                }
            }

            // update hidden input
            viewModel.quantity.$input.val(qty);
            // update text
            viewModel.quantity.$text.text(qty);
        });
    }

    /**
     *
     * Add a product to cart
     *
     */
    addProductToCart(event, form) {
        const $addToCartBtn = $('#form-action-addToCart', $(event.target));
        const originalBtnVal = $addToCartBtn.val();
        const waitMessage = $addToCartBtn.data('waitMessage');

        // Do not do AJAX if browser doesn't support FormData
        if (window.FormData === undefined) {
            return;
        }

        // Prevent default
        event.preventDefault();

        $addToCartBtn
            .val(waitMessage)
            .prop('disabled', true);

        // Add item to cart
        utils.api.cart.itemAdd(new FormData(form), (err, response) => {
            const errorMessage = err || response.data.error;

            $addToCartBtn
                .val(originalBtnVal)
                .prop('disabled', false);

            // Guard statement
            if (errorMessage) {
                // Strip the HTML from the error message
                const tmp = document.createElement('DIV');
                tmp.innerHTML = errorMessage;

                alert(tmp.textContent || tmp.innerText);

                return;
            }

            // Open preview modal and update content
            previewModal.open();

            this.updateCartContent(previewModal, response.data.cart_item.hash);
        });
    }

    /**
     * Get cart contents
     *
     * @param {String} cartItemHash
     * @param {Function} onComplete
     */
    getCartContent(cartItemHash, onComplete) {
        const options = {
            template: 'cart/preview',
            params: {
                suggest: cartItemHash,
            },
            config: {
                cart: {
                    suggestions: {
                        limit: 4,
                    },
                },
            },
        };

        utils.api.cart.getContent(options, onComplete);
    }

    /**
     * Update cart content
     *
     * @param {Modal} modal
     * @param {String} cartItemHash
     * @param {Function} onComplete
     */
    updateCartContent(modal, cartItemHash, onComplete) {
        this.getCartContent(cartItemHash, (err, response) => {
            if (err) {
                return;
            }

            modal.updateContent(response);

            // Update cart counter
            const $body = $('body');
            const $cartQuantity = $('[data-cart-quantity]', modal.$content);
            const $cartCounter = $('.navUser-action .cart-count');
            const quantity = $cartQuantity.data('cart-quantity') || 0;

            $cartCounter.addClass('cart-count--positive');
            $body.trigger('cart-quantity-update', quantity);

            if (onComplete) {
                onComplete(response);
            }
        });
    }

    /**
     * Show an message box if a message is passed
     * Hide the box if the message is empty
     * @param  {String} message
     */
    showMessageBox(message) {
        const $messageBox = $('.productAttributes-message');

        if (message) {
            $('.alertBox-message', $messageBox).text(message);
            $messageBox.show();
        } else {
            $messageBox.hide();
        }
    }

    /**
     * Update the view of price, messages, SKU and stock options when a product option changes
     * @param  {Object} data Product attribute data
     */
    updateView(data) {
        const viewModel = this.getViewModel(this.$scope);

        this.showMessageBox(data.stock_message || data.purchasing_message);

        if (_.isObject(data.price)) {
            if (data.price.with_tax) {
                viewModel.$priceWithTax.html(data.price.with_tax.formatted);
            }

            if (data.price.without_tax) {
                viewModel.$priceWithoutTax.html(data.price.without_tax.formatted);
            }

            if (data.price.rrp_with_tax) {
                viewModel.$rrpWithTax.html(data.price.rrp_with_tax.formatted);
            }

            if (data.price.rrp_without_tax) {
                viewModel.$rrpWithoutTax.html(data.price.rrp_without_tax.formatted);
            }
        }

        if (_.isObject(data.weight)) {
            viewModel.$weight.html(data.weight.formatted);
        }

        // Set variation_id if it exists for adding to wishlist
        if (data.variantId) {
            viewModel.$wishlistVariation.val(data.variantId);
        }

        // If SKU is available
        if (data.sku) {
            viewModel.$sku.text(data.sku);
        }

        // if stock view is on (CP settings)
        if (viewModel.stock.$container.length && _.isNumber(data.stock)) {
            // if the stock container is hidden, show
            viewModel.stock.$container.removeClass('u-hiddenVisually');

            viewModel.stock.$input.text(data.stock);
        }

        if (!data.purchasable || !data.instock) {
            viewModel.$addToCart.prop('disabled', true);
            viewModel.$increments.prop('disabled', true);
        } else {
            viewModel.$addToCart.prop('disabled', false);
            viewModel.$increments.prop('disabled', false);
        }
    }

    /**
     * Hide or mark as unavailable out of stock attributes if enabled
     * @param  {Object} data Product attribute data
     */
    updateProductAttributes(data) {
        const behavior = data.out_of_stock_behavior;
        const inStockIds = data.in_stock_attributes;
        const outOfStockMessage = ` (${data.out_of_stock_message})`;

        this.showProductImage(data.image);

        if (behavior !== 'hide_option' && behavior !== 'label_option') {
            return;
        }

        $('[data-product-attribute-value]', this.$scope).each((i, attribute) => {
            const $attribute = $(attribute);
            const attrId = parseInt($attribute.data('product-attribute-value'), 10);

            if (inStockIds.indexOf(attrId) !== -1) {
                this.enableAttribute($attribute, behavior, outOfStockMessage);
            } else {
                this.disableAttribute($attribute, behavior, outOfStockMessage);
            }
        });
    }

    disableAttribute($attribute, behavior, outOfStockMessage) {
        const attrType = this.getAttributeType($attribute);
        let $select = null;

        if (behavior === 'hide_option') {
            $attribute.hide();

            // If the attribute is the selected option in a select dropdown, select the first option (MERC-639)
            if (attrType === 'set-select' && $attribute.parent().val() === $attribute.attr('value')) {
                $select = $attribute.parent();
                $select[0].selectedIndex = 0;
            }
        } else {
            if (attrType === 'set-select') {
                $attribute.html($attribute.html().replace(outOfStockMessage, '') + outOfStockMessage);
            } else {
                $attribute.addClass('unavailable');
            }
        }
    }

    enableAttribute($attribute, behavior, outOfStockMessage) {
        if (behavior === 'hide_option') {
            $attribute.show();
        } else {
            if (this.getAttributeType($attribute) === 'set-select') {
                $attribute.html($attribute.html().replace(outOfStockMessage, ''));
            } else {
                $attribute.removeClass('unavailable');
            }
        }
    }

    getAttributeType($attribute) {
        const $parent = $attribute.closest('[data-product-attribute]');

        return $parent ? $parent.data('product-attribute') : null;
    }

    /**
     * Allow radio buttons to get deselected
     */
    initRadioAttributes() {
        $('[data-product-attribute] input[type="radio"]', this.$scope).each((i, radio) => {
            const $radio = $(radio);

            // Only bind to click once
            if ($radio.attr('data-state') !== undefined) {
                $radio.click(() => {
                    if ($radio.data('state') === true) {
                        $radio.prop('checked', false);
                        $radio.data('state', false);

                        $radio.change();
                    } else {
                        $radio.data('state', true);
                    }

                    this.initRadioAttributes();
                });
            }

            $radio.attr('data-state', $radio.prop('checked'));
        });
    }
}
<|endoftext|>


<commit_before>/**
 * External dependencies
 */
import classnames from 'classnames';

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import {
	RichText,
	getColorClassName,
	getFontSize,
	getColorObjectByAttributeValues,
} from '@wordpress/block-editor';
import { select } from '@wordpress/data';

/**
 * Internal dependencies
 */
import edit from './edit';
import { getPercentageFromPixels, getRgbaFromHex } from '../../helpers';
import { STORY_PAGE_INNER_WIDTH } from '../../constants';

export const name = 'amp/amp-story-text';

const supports = {
	className: false,
	anchor: true,
	reusable: true,
};

const schema = {
	placeholder: {
		type: 'string',
	},
	content: {
		type: 'string',
		source: 'html',
		selector: '.amp-text-content',
		default: '',
	},
	type: {
		type: 'string',
		default: 'auto',
	},
	tagName: {
		type: 'string',
		default: 'p',
	},
	align: {
		type: 'string',
	},
	fontSize: {
		type: 'string',
	},
	customFontSize: {
		type: 'number',
	},
	autoFontSize: {
		type: 'number',
	},
	ampFitText: {
		type: 'boolean',
		default: true,
	},
	ampFontFamily: {
		type: 'string',
	},
	textColor: {
		type: 'string',
	},
	customTextColor: {
		type: 'string',
	},
	backgroundColor: {
		type: 'string',
	},
	customBackgroundColor: {
		type: 'string',
	},
	opacity: {
		default: 100,
		type: 'number',
	},
	height: {
		default: 50,
		type: 'number',
	},
	width: {
		default: 250,
		type: 'number',
	},
};

export const settings = {
	title: __( 'Text', 'amp' ),

	description: __( 'Add free-form text to your story', 'amp' ),

	icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 4v3h5.5v12h3V7H19V4z" /><path fill="none" d="M0 0h24v24H0V0z" /></svg>,

	category: 'common',

	keywords: [
		__( 'title', 'amp' ),
		__( 'heading', 'amp' ),
		__( 'paragraph', 'amp' ),
	],

	supports,

	attributes: schema,

	edit,

	save: ( { attributes } ) => {
		const {
			content,
			align,
			fontSize,
			customFontSize,
			ampFitText,
			autoFontSize,
			backgroundColor,
			textColor,
			customBackgroundColor,
			customTextColor,
			width,
			height,
			tagName,
			opacity,
		} = attributes;

		const textClass = getColorClassName( 'color', textColor );
		const backgroundClass = getColorClassName( 'background-color', backgroundColor );

		const hasOpacity = opacity && opacity < 100;

		const className = classnames( {
			'amp-text-content': ! ampFitText,
			'has-text-color': textColor || customTextColor,
			'has-background': backgroundColor || customBackgroundColor,
			[ textClass ]: textClass,
			[ backgroundClass ]: ! hasOpacity ? backgroundClass : undefined,
		} );

		const { colors, fontSizes } = select( 'core/block-editor' ).getSettings();

		/*
         * Calculate fontsize using vw to make it responsive.
         *
         * Get the font size in px based on the slug with fallback to customFontSize.
         */
		const userFontSize = fontSize ? getFontSize( fontSizes, fontSize, customFontSize ).size : customFontSize;
		const fontSizeResponsive = ( ( userFontSize / STORY_PAGE_INNER_WIDTH ) * 100 ).toFixed( 2 ) + 'vw';

		let appliedBackgroundColor;

		// If we need to assign opacity.
		if ( hasOpacity && ( backgroundColor || customBackgroundColor ) ) {
			const hexColor = getColorObjectByAttributeValues( colors, backgroundColor, customBackgroundColor );

			if ( hexColor ) {
				const [ r, g, b, a ] = getRgbaFromHex( hexColor.color, opacity );

				appliedBackgroundColor = `rgba( ${ r }, ${ g }, ${ b }, ${ a })`;
			}
		} else if ( ! backgroundClass ) {
			appliedBackgroundColor = customBackgroundColor;
		}

		const styles = {
			backgroundColor: appliedBackgroundColor,
			color: textClass ? undefined : customTextColor,
			fontSize: ampFitText ? autoFontSize : fontSizeResponsive,
			width: `${ getPercentageFromPixels( 'x', width ) }%`,
			height: `${ getPercentageFromPixels( 'y', height ) }%`,
			textAlign: align,
		};

		if ( ! ampFitText ) {
			return (
				<RichText.Content
					tagName={ tagName }
					style={ styles }
					className={ className }
					value={ content }
				/>
			);
		}

		const ContentTag = tagName;

		return (
			<ContentTag
				style={ styles }
				className={ className }>
				<amp-fit-text layout="fill" className="amp-text-content">{ content }</amp-fit-text>
			</ContentTag>
		);
	},
};
<commit_msg>Fix indent.<commit_after>/**
 * External dependencies
 */
import classnames from 'classnames';

/**
 * WordPress dependencies
 */
import { __ } from '@wordpress/i18n';
import {
	RichText,
	getColorClassName,
	getFontSize,
	getColorObjectByAttributeValues,
} from '@wordpress/block-editor';
import { select } from '@wordpress/data';

/**
 * Internal dependencies
 */
import edit from './edit';
import { getPercentageFromPixels, getRgbaFromHex } from '../../helpers';
import { STORY_PAGE_INNER_WIDTH } from '../../constants';

export const name = 'amp/amp-story-text';

const supports = {
	className: false,
	anchor: true,
	reusable: true,
};

const schema = {
	placeholder: {
		type: 'string',
	},
	content: {
		type: 'string',
		source: 'html',
		selector: '.amp-text-content',
		default: '',
	},
	type: {
		type: 'string',
		default: 'auto',
	},
	tagName: {
		type: 'string',
		default: 'p',
	},
	align: {
		type: 'string',
	},
	fontSize: {
		type: 'string',
	},
	customFontSize: {
		type: 'number',
	},
	autoFontSize: {
		type: 'number',
	},
	ampFitText: {
		type: 'boolean',
		default: true,
	},
	ampFontFamily: {
		type: 'string',
	},
	textColor: {
		type: 'string',
	},
	customTextColor: {
		type: 'string',
	},
	backgroundColor: {
		type: 'string',
	},
	customBackgroundColor: {
		type: 'string',
	},
	opacity: {
		default: 100,
		type: 'number',
	},
	height: {
		default: 50,
		type: 'number',
	},
	width: {
		default: 250,
		type: 'number',
	},
};

export const settings = {
	title: __( 'Text', 'amp' ),

	description: __( 'Add free-form text to your story', 'amp' ),

	icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M5 4v3h5.5v12h3V7H19V4z" /><path fill="none" d="M0 0h24v24H0V0z" /></svg>,

	category: 'common',

	keywords: [
		__( 'title', 'amp' ),
		__( 'heading', 'amp' ),
		__( 'paragraph', 'amp' ),
	],

	supports,

	attributes: schema,

	edit,

	save: ( { attributes } ) => {
		const {
			content,
			align,
			fontSize,
			customFontSize,
			ampFitText,
			autoFontSize,
			backgroundColor,
			textColor,
			customBackgroundColor,
			customTextColor,
			width,
			height,
			tagName,
			opacity,
		} = attributes;

		const textClass = getColorClassName( 'color', textColor );
		const backgroundClass = getColorClassName( 'background-color', backgroundColor );

		const hasOpacity = opacity && opacity < 100;

		const className = classnames( {
			'amp-text-content': ! ampFitText,
			'has-text-color': textColor || customTextColor,
			'has-background': backgroundColor || customBackgroundColor,
			[ textClass ]: textClass,
			[ backgroundClass ]: ! hasOpacity ? backgroundClass : undefined,
		} );

		const { colors, fontSizes } = select( 'core/block-editor' ).getSettings();

		/*
		 * Calculate fontsize using vw to make it responsive.
		 *
		 * Get the font size in px based on the slug with fallback to customFontSize.
		 */
		const userFontSize = fontSize ? getFontSize( fontSizes, fontSize, customFontSize ).size : customFontSize;
		const fontSizeResponsive = ( ( userFontSize / STORY_PAGE_INNER_WIDTH ) * 100 ).toFixed( 2 ) + 'vw';

		let appliedBackgroundColor;

		// If we need to assign opacity.
		if ( hasOpacity && ( backgroundColor || customBackgroundColor ) ) {
			const hexColor = getColorObjectByAttributeValues( colors, backgroundColor, customBackgroundColor );

			if ( hexColor ) {
				const [ r, g, b, a ] = getRgbaFromHex( hexColor.color, opacity );

				appliedBackgroundColor = `rgba( ${ r }, ${ g }, ${ b }, ${ a })`;
			}
		} else if ( ! backgroundClass ) {
			appliedBackgroundColor = customBackgroundColor;
		}

		const styles = {
			backgroundColor: appliedBackgroundColor,
			color: textClass ? undefined : customTextColor,
			fontSize: ampFitText ? autoFontSize : fontSizeResponsive,
			width: `${ getPercentageFromPixels( 'x', width ) }%`,
			height: `${ getPercentageFromPixels( 'y', height ) }%`,
			textAlign: align,
		};

		if ( ! ampFitText ) {
			return (
				<RichText.Content
					tagName={ tagName }
					style={ styles }
					className={ className }
					value={ content }
				/>
			);
		}

		const ContentTag = tagName;

		return (
			<ContentTag
				style={ styles }
				className={ className }>
				<amp-fit-text layout="fill" className="amp-text-content">{ content }</amp-fit-text>
			</ContentTag>
		);
	},
};
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

define(['jquery', 'backbone', 'lodash', 'log', /** void module - jquery plugin **/ 'js_tree'], function ($, Backbone, _, log) {

    var FileBrowser = Backbone.View.extend({

        initialize: function (config) {
            var errMsg;
            if (!_.has(config, 'container')) {
                errMsg = 'unable to find configuration for container';
                log.error(errMsg);
                throw errMsg;
            }
            var container = $(_.get(config, 'container'));
            // check whether container element exists in dom
            if (!container.length > 0) {
                errMsg = 'unable to find container for file browser with selector: ' + _.get(config, 'container');
                log.error(errMsg);
                throw errMsg;
            }
            this._$parent_el = container;

            if (!_.has(config, 'application')) {
                log.error('Cannot init file browser. config: application not found.')
            }

            this.application = _.get(config, 'application');
            this._options = config;
            this._workspaceServiceURL = _.get(this._options, 'application.config.services.workspace.endpoint');
            this._serviceURL = _.get(this._options, 'application.config.baseUrl');
            this._isActive = false;
            this._fetchFiles = _.get(config, 'fetchFiles', false);
            this._root = _.get(config, 'root');
            this._showWorkspace = _.get(config, 'showWorkspace');
            this._showSamples = _.get(config, 'showSamples');
            this._showBundles = _.get(config, 'showBundles');
            this._multiSelect = _.get(config, 'multiSelect', false);

            this._treeConfig = {
                'core': {
                    'data': {
                        'url': this.getURLProvider(),
                        'dataType': "json",
                        'data': function (node) {
                            return {'id': node.id};
                        }
                    },
                    'multiple': this._multiSelect,
                    'check_callback': false,
                    'force_text': true,
                    'expand_selected_onload': true,
                    'themes': {
                        'responsive': false,
                        'variant': 'small',
                        'stripes': false,
                        'dots': false
                    }
                },
                'types': {
                    'default': {
                        'icon': 'fw fw-folder'
                    },
                    'folder': {
                        'icon': 'fw fw-folder'
                    },
                    'file': {
                        'icon': 'fw-document'
                    }
                },
                'sort': function (a, b) {
                    return this.get_text(a).toLowerCase() > this.get_text(b).toLowerCase() ? 1 : -1;
                }
            };

            this._plugins = ['types', 'wholerow', 'sort'];
            if (this._multiSelect) {
                this._plugins.push('checkbox');
            }
            this._contextMenuProvider = _.get(config, 'contextMenuProvider');
            if(!_.isNil(this._contextMenuProvider)){
                this._plugins.push('contextmenu');
                _.set(this._treeConfig, 'contextmenu.items', this._contextMenuProvider);
                _.set(this._treeConfig, 'contextmenu.show_at_node', false);
            }
            _.set(this._treeConfig, 'plugins', this._plugins);
        },

        getURLProvider: function(){
            var self = this;
            return function (node) {

                if (self._showBundles) {
                    if (node.id === '#') {
                        return self._serviceURL + "/filterDirectories?" +
                            "directory=" + self.application.utils.base64EncodeUnicode("bundles,jars");
                    } else {
                        return self._serviceURL + "/listFilesInPath?path=" + self.application.utils.
                        base64EncodeUnicode(node.id);
                    }
                } else if(self._showWorkspace && node.id === '#'){
                    return self._workspaceServiceURL + "/listFilesInPath?path=" + self.application.utils.
                    base64EncodeUnicode("");
                } else if(self._showSamples && node.id === '#'){
                    var samplesRelativeUrl = "";
                    return self._workspaceServiceURL + "/listFiles/samples?path=" + self.application.utils.
                    base64EncodeUnicode(samplesRelativeUrl);
                } else if (node.id === '#') {
                    if(!_.isNil(self._root)){
                        if (self._fetchFiles) {
                            return self._workspaceServiceURL + "/listFiles/workspace?path=" + self.application.utils.
                            base64EncodeUnicode(self._root);
                        }
                    }
                    return self._workspaceServiceURL + "/root";
                } else {
                    if (self._fetchFiles) {
                        return self._workspaceServiceURL + "/listFiles?path=" + self.application.utils.
                        base64EncodeUnicode(node.id);
                    } else {
                        return self._workspaceServiceURL + "/list?path=" + self.application.utils.
                        base64EncodeUnicode(node.id);
                    }
                }
            }
        },

        /**
         * @param path a single path or an array of folder paths to select
         */
        select: function(path){
            this._$parent_el.jstree(true).deselect_all();
            var pathSeparator = this.application.getPathSeperator(),
                pathParts = _.split(path, pathSeparator),
                currentPart = "/",
                self = this;
            pathParts.forEach(function(part){
                currentPart += part;
                self._$parent_el.jstree(true).open_node(currentPart);
                currentPart += pathSeparator;
            });

            this._$parent_el.jstree(true).select_node(path);
        },

        refresh: function(node){
            this._$parent_el.jstree(true).load_node(node);
        },

        getNode: function(id){
            return this._$parent_el.jstree(true).get_node(id);
        },

        getSelected: function() {
            return this._$parent_el.jstree(true).get_selected(true);
        },

        getBottomSelected: function() {
            return this._$parent_el.jstree(true).get_bottom_selected(true);
        },

        render: function () {
            var self = this;
            this._$parent_el
                .jstree(self._treeConfig).on('changed.jstree', function (e, data) {
                if (data && data.selected && data.selected.length) {
                    if (self._multiSelect) {
                        self.selected = data.selected;
                        self.trigger("selected", data.selected);
                    } else {
                        self.selected = data.selected[0];
                        self.trigger("selected", data.selected[0]);
                    }
                } else {
                    self.selected = false;
                    self.trigger("selected", self._multiSelect ? [] : null);
                }
            }).on('open_node.jstree', function (e, data) {
                data.instance.set_icon(data.node, "fw fw-folder");
            }).on('close_node.jstree', function (e, data) {
                data.instance.set_icon(data.node, "fw fw-folder");
            }).on('ready', function(){
                self.trigger("ready");
            }).on('hover_node.jstree', function (e, data) {
                var linkId = data.node.a_attr.id;
                var fileName = data.node.text;
                $("a[id='"+linkId+"']").attr('title', fileName);
            }).on('select_node.jstree', function (e, data) {
                data.instance.toggle_node(data.node);
            }).on("dblclick.jstree", function (event) {
                if("folder-tree_-1" !== event.currentTarget.id){
                    return;
                }
                var item = $(event.target).closest("li");
                var node = self._$parent_el.jstree(true).get_node(item[0].id);
                var path = node.id;
                var fileName = _.last(path.split(self.application.getPathSeperator()));
                node.id = "workspace" + self.application.getPathSeperator() + fileName;
                self.trigger("double-click-node", node);
            });
            return this;
        }
    });

    return FileBrowser;

});
<commit_msg>Call updated API<commit_after>/**
 * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

define(['jquery', 'backbone', 'lodash', 'log', /** void module - jquery plugin **/ 'js_tree'], function ($, Backbone, _, log) {

    var FileBrowser = Backbone.View.extend({

        initialize: function (config) {
            var errMsg;
            if (!_.has(config, 'container')) {
                errMsg = 'unable to find configuration for container';
                log.error(errMsg);
                throw errMsg;
            }
            var container = $(_.get(config, 'container'));
            // check whether container element exists in dom
            if (!container.length > 0) {
                errMsg = 'unable to find container for file browser with selector: ' + _.get(config, 'container');
                log.error(errMsg);
                throw errMsg;
            }
            this._$parent_el = container;

            if (!_.has(config, 'application')) {
                log.error('Cannot init file browser. config: application not found.')
            }

            this.application = _.get(config, 'application');
            this._options = config;
            this._workspaceServiceURL = _.get(this._options, 'application.config.services.workspace.endpoint');
            this._serviceURL = _.get(this._options, 'application.config.baseUrl');
            this._isActive = false;
            this._fetchFiles = _.get(config, 'fetchFiles', false);
            this._root = _.get(config, 'root');
            this._showWorkspace = _.get(config, 'showWorkspace');
            this._showSamples = _.get(config, 'showSamples');
            this._showBundles = _.get(config, 'showBundles');
            this._multiSelect = _.get(config, 'multiSelect', false);

            this._treeConfig = {
                'core': {
                    'data': {
                        'url': this.getURLProvider(),
                        'dataType': "json",
                        'data': function (node) {
                            return {'id': node.id};
                        }
                    },
                    'multiple': this._multiSelect,
                    'check_callback': false,
                    'force_text': true,
                    'expand_selected_onload': true,
                    'themes': {
                        'responsive': false,
                        'variant': 'small',
                        'stripes': false,
                        'dots': false
                    }
                },
                'types': {
                    'default': {
                        'icon': 'fw fw-folder'
                    },
                    'folder': {
                        'icon': 'fw fw-folder'
                    },
                    'file': {
                        'icon': 'fw-document'
                    }
                },
                'sort': function (a, b) {
                    return this.get_text(a).toLowerCase() > this.get_text(b).toLowerCase() ? 1 : -1;
                }
            };

            this._plugins = ['types', 'wholerow', 'sort'];
            if (this._multiSelect) {
                this._plugins.push('checkbox');
            }
            this._contextMenuProvider = _.get(config, 'contextMenuProvider');
            if(!_.isNil(this._contextMenuProvider)){
                this._plugins.push('contextmenu');
                _.set(this._treeConfig, 'contextmenu.items', this._contextMenuProvider);
                _.set(this._treeConfig, 'contextmenu.show_at_node', false);
            }
            _.set(this._treeConfig, 'plugins', this._plugins);
        },

        getURLProvider: function(){
            var self = this;
            return function (node) {

                if (self._showBundles) {
                    if (node.id === '#') {
                        return self._serviceURL + "/listDirectoriesInPath" +
                            "?path=" + self.application.utils.base64EncodeUnicode("") +
                            "&directory=" + self.application.utils.base64EncodeUnicode("bundles,jars");
                    } else {
                        return self._serviceURL + "/listFilesInPath?path=" + self.application.utils.
                        base64EncodeUnicode(node.id);
                    }
                } else if(self._showWorkspace && node.id === '#'){
                    return self._workspaceServiceURL + "/listFilesInPath?path=" + self.application.utils.
                    base64EncodeUnicode("");
                } else if(self._showSamples && node.id === '#'){
                    var samplesRelativeUrl = "";
                    return self._workspaceServiceURL + "/listFiles/samples?path=" + self.application.utils.
                    base64EncodeUnicode(samplesRelativeUrl);
                } else if (node.id === '#') {
                    if(!_.isNil(self._root)){
                        if (self._fetchFiles) {
                            return self._workspaceServiceURL + "/listFiles/workspace?path=" + self.application.utils.
                            base64EncodeUnicode(self._root);
                        }
                    }
                    return self._workspaceServiceURL + "/root";
                } else {
                    if (self._fetchFiles) {
                        return self._workspaceServiceURL + "/listFiles?path=" + self.application.utils.
                        base64EncodeUnicode(node.id);
                    } else {
                        return self._workspaceServiceURL + "/list?path=" + self.application.utils.
                        base64EncodeUnicode(node.id);
                    }
                }
            }
        },

        /**
         * @param path a single path or an array of folder paths to select
         */
        select: function(path){
            this._$parent_el.jstree(true).deselect_all();
            var pathSeparator = this.application.getPathSeperator(),
                pathParts = _.split(path, pathSeparator),
                currentPart = "/",
                self = this;
            pathParts.forEach(function(part){
                currentPart += part;
                self._$parent_el.jstree(true).open_node(currentPart);
                currentPart += pathSeparator;
            });

            this._$parent_el.jstree(true).select_node(path);
        },

        refresh: function(node){
            this._$parent_el.jstree(true).load_node(node);
        },

        getNode: function(id){
            return this._$parent_el.jstree(true).get_node(id);
        },

        getSelected: function() {
            return this._$parent_el.jstree(true).get_selected(true);
        },

        getBottomSelected: function() {
            return this._$parent_el.jstree(true).get_bottom_selected(true);
        },

        render: function () {
            var self = this;
            this._$parent_el
                .jstree(self._treeConfig).on('changed.jstree', function (e, data) {
                if (data && data.selected && data.selected.length) {
                    if (self._multiSelect) {
                        self.selected = data.selected;
                        self.trigger("selected", data.selected);
                    } else {
                        self.selected = data.selected[0];
                        self.trigger("selected", data.selected[0]);
                    }
                } else {
                    self.selected = false;
                    self.trigger("selected", self._multiSelect ? [] : null);
                }
            }).on('open_node.jstree', function (e, data) {
                data.instance.set_icon(data.node, "fw fw-folder");
            }).on('close_node.jstree', function (e, data) {
                data.instance.set_icon(data.node, "fw fw-folder");
            }).on('ready', function(){
                self.trigger("ready");
            }).on('hover_node.jstree', function (e, data) {
                var linkId = data.node.a_attr.id;
                var fileName = data.node.text;
                $("a[id='"+linkId+"']").attr('title', fileName);
            }).on('select_node.jstree', function (e, data) {
                data.instance.toggle_node(data.node);
            }).on("dblclick.jstree", function (event) {
                if("folder-tree_-1" !== event.currentTarget.id){
                    return;
                }
                var item = $(event.target).closest("li");
                var node = self._$parent_el.jstree(true).get_node(item[0].id);
                var path = node.id;
                var fileName = _.last(path.split(self.application.getPathSeperator()));
                node.id = "workspace" + self.application.getPathSeperator() + fileName;
                self.trigger("double-click-node", node);
            });
            return this;
        }
    });

    return FileBrowser;

});
<|endoftext|>


<commit_before>/**
 * Copyright 2015-2019 OpenCB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {LitElement, html} from "lit";
import UtilsNew from "../../../core/utilsNew.js";
import {join} from "lodash";

export default class CohortStatsFilter extends LitElement {

    constructor() {
        super();

        // Set status and init private properties
        this._init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            cohorts: {
                type: Array
            },
            onlyCohortAll: {
                type: Boolean
            },
            cohortStatsAlt: {
                type: String
            },
            config: {
                type: Object
            }
        };
    }

    _init() {
        this._prefix = UtilsNew.randomString(8);

        this.defaultComparator = "<";
    }

    connectedCallback() {
        super.connectedCallback();

        this.cohortsPerStudy = this.cohorts ? this.cohorts[this.opencgaSession.study.id] : null;
        this.state = {};
    }

    update(changedProperties) {
        if (changedProperties.has("opencgaSession") || changedProperties.has("cohorts")) {
            this.state = {};
            this.cohortsPerStudy = this._getCohorts();
            // this.requestUpdate();
        }

        if (changedProperties.has("cohortStatsAlt")) {
            this.state = {};
            if (this.cohortStatsAlt) {
                const cohorts = this.cohortStatsAlt.split(";");
                cohorts.forEach(cohortStat => {
                    const [studyId, cohortFreq] = cohortStat.split(":");
                    if (!this.state[studyId]) {
                        this.state[studyId] = [];
                    }
                    const [cohort, comparator, value] = cohortFreq.split(/(<=?|>=?|=)/);
                    this.state[studyId].push({cohort, comparator, value});
                });
            }
            // this.requestUpdate();
        }
        super.update(changedProperties);
    }

    _getCohorts() {
        let studiesAndCohorts = [];
        if (this.opencgaSession?.project?.studies) { //  && this.onlyCohortAll
            for (const study of this.opencgaSession.project.studies) {
                if (study.cohorts?.length) {
                    if (this.onlyCohortAll) {
                        studiesAndCohorts.push({
                            ...study,
                            cohorts: [{id: "ALL"}]
                        });
                    } else {
                        studiesAndCohorts = this.cohorts;
                    }
                }
            }
        }
        return studiesAndCohorts;
    }

    handleCollapseAction(e) {
        const id = e.target.dataset.id;
        const elem = $("#" + id)[0];
        elem.hidden = !elem.hidden;
        if (elem.hidden) {
            e.target.className = "fa fa-plus";
        } else {
            e.target.className = "fa fa-minus";
        }
    }

    getStudyIdFromFqn(fqn) {
        // replaces characters not valid in an DOM Element ID
        return fqn.split(":")[1]?.replace(/@|:/g, "_");
    }

    filterChange(e, study, cohort) {
        e.stopPropagation();

        // e.detail.value is not defined iff you are changing the comparator and a value hasn't been set yet
        const index = this.state[study]?.findIndex(c => c.cohort === cohort);
        if (e?.detail?.value) {
            if (index >= 0) {
                this.state[study][index].comparator = e.detail.comparator;
                this.state[study][index].value = e.detail.numValue;
            } else {
                if (!this.state[study]) {
                    this.state[study] = [];
                }
                this.state[study].push({cohort: cohort, comparator: e.detail.comparator, value: e.detail.numValue});
            }
        } else {
            if (index >= 0) {
                this.state[study].splice(index, 1);
            }

            // If not cohort are left then we remove everything?
            if (this.state[study].length === 0) {
                delete this.state[study];
            }
        }

        // serialize this.state in the form of "STUDY_ID:COHORT_ID<VALUE;.."
        const value = Object.entries(this.state)
            // .filter(([, v]) => v.value)
            .map(([studyId, cohorts]) => {
                return cohorts.map(c => `${studyId}:${c.cohort}${c.comparator}${c.value}`).join(";");
            })
            .join(";");
        const event = new CustomEvent("filterChange", {
            detail: {
                value: value
            }
        });
        this.dispatchEvent(event);
    }

    render() {
        if (!this.cohortsPerStudy?.length) {
            return html`<span>Cohort Variants Stats not available.</span>`;
        }

        return html`
            ${this.cohortsPerStudy
            .map(study => html`

                        Study ${this.getStudyIdFromFqn(study.fqn)} cohorts

                        ${study.cohorts.map(cohort => {
                            const stateCohort = this.state?.[study.id]?.find(c => c.id === cohort.id);
                            return html`

                                    <number-field-filter
                                        .value="${stateCohort?.value ?
                                            (this.state?.[study.id]?.comparator ?? this.defaultComparator) + (this.state?.[study.id]?.value ?? "") :
                                            ""}"
                                        .config="${{comparator: true, layout: [3, 4, 5]}}"
                                        .label="${cohort.id}"
                                        type="text"
                                        data-study="${study.id}"
                                        data-cohort="${cohort.id}"
                                        data-action="comparator"
                                        @filterChange="${e => this.filterChange(e, study.id, cohort.id)}">
                                </number-field-filter>

`;
                        })}

            `)}`;
    }

}

customElements.define("cohort-stats-filter", CohortStatsFilter);
<commit_msg>wc: display cohort stats collapseb by default<commit_after>/**
 * Copyright 2015-2019 OpenCB
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {LitElement, html} from "lit";
import UtilsNew from "../../../core/utilsNew.js";
import {join} from "lodash";

export default class CohortStatsFilter extends LitElement {

    constructor() {
        super();

        // Set status and init private properties
        this._init();
    }

    createRenderRoot() {
        return this;
    }

    static get properties() {
        return {
            opencgaSession: {
                type: Object
            },
            cohorts: {
                type: Array
            },
            onlyCohortAll: {
                type: Boolean
            },
            cohortStatsAlt: {
                type: String
            },
            config: {
                type: Object
            }
        };
    }

    _init() {
        this._prefix = UtilsNew.randomString(8);

        this.defaultComparator = "<";
    }

    connectedCallback() {
        super.connectedCallback();

        this.cohortsPerStudy = this.cohorts ? this.cohorts[this.opencgaSession.study.id] : null;
        this.state = {};
    }

    update(changedProperties) {
        if (changedProperties.has("opencgaSession") || changedProperties.has("cohorts")) {
            this.state = {};
            this.cohortsPerStudy = this._getCohorts();
            // this.requestUpdate();
        }

        if (changedProperties.has("cohortStatsAlt")) {
            this.state = {};
            if (this.cohortStatsAlt) {
                const cohorts = this.cohortStatsAlt.split(";");
                cohorts.forEach(cohortStat => {
                    const [studyId, cohortFreq] = cohortStat.split(":");
                    if (!this.state[studyId]) {
                        this.state[studyId] = [];
                    }
                    const [cohort, comparator, value] = cohortFreq.split(/(<=?|>=?|=)/);
                    this.state[studyId].push({cohort, comparator, value});
                });
            }
            // this.requestUpdate();
        }
        super.update(changedProperties);
    }

    _getCohorts() {
        let studiesAndCohorts = [];
        if (this.opencgaSession?.project?.studies) { //  && this.onlyCohortAll
            for (const study of this.opencgaSession.project.studies) {
                if (study.cohorts?.length) {
                    if (this.onlyCohortAll) {
                        studiesAndCohorts.push({
                            ...study,
                            cohorts: [{id: "ALL"}]
                        });
                    } else {
                        studiesAndCohorts = this.cohorts;
                    }
                }
            }
        }
        return studiesAndCohorts;
    }

    handleCollapseAction(e) {
        const id = e.target.dataset.id;
        const elem = $("#" + id)[0];
        elem.hidden = !elem.hidden;
        if (elem.hidden) {
            e.target.className = "fa fa-plus";
        } else {
            e.target.className = "fa fa-minus";
        }
    }

    getStudyIdFromFqn(fqn) {
        // replaces characters not valid in an DOM Element ID
        return fqn.split(":")[1]?.replace(/@|:/g, "_");
    }

    filterChange(e, study, cohort) {
        e.stopPropagation();

        // e.detail.value is not defined iff you are changing the comparator and a value hasn't been set yet
        const index = this.state[study]?.findIndex(c => c.cohort === cohort);
        if (e?.detail?.value) {
            if (index >= 0) {
                this.state[study][index].comparator = e.detail.comparator;
                this.state[study][index].value = e.detail.numValue;
            } else {
                if (!this.state[study]) {
                    this.state[study] = [];
                }
                this.state[study].push({cohort: cohort, comparator: e.detail.comparator, value: e.detail.numValue});
            }
        } else {
            if (index >= 0) {
                this.state[study].splice(index, 1);
            }

            // If not cohort are left then we remove everything?
            if (this.state[study].length === 0) {
                delete this.state[study];
            }
        }

        // serialize this.state in the form of "STUDY_ID:COHORT_ID<VALUE;.."
        const value = Object.entries(this.state)
            // .filter(([, v]) => v.value)
            .map(([studyId, cohorts]) => {
                return cohorts.map(c => `${studyId}:${c.cohort}${c.comparator}${c.value}`).join(";");
            })
            .join(";");
        const event = new CustomEvent("filterChange", {
            detail: {
                value: value
            }
        });
        this.dispatchEvent(event);
    }

    render() {
        if (!this.cohortsPerStudy?.length) {
            return html`<span>Cohort Variants Stats not available.</span>`;
        }

        return html`
            ${this.cohortsPerStudy
            .map(study => html`

                        Study ${this.getStudyIdFromFqn(study.fqn)} cohorts

                        ${study.cohorts.map(cohort => {
                            const stateCohort = this.state?.[study.id]?.find(c => c.id === cohort.id);
                            return html`

                                    <number-field-filter
                                        .value="${stateCohort?.value ?
                                            (this.state?.[study.id]?.comparator ?? this.defaultComparator) + (this.state?.[study.id]?.value ?? "") :
                                            ""}"
                                        .config="${{comparator: true, layout: [3, 4, 5]}}"
                                        .label="${cohort.id}"
                                        type="text"
                                        data-study="${study.id}"
                                        data-cohort="${cohort.id}"
                                        data-action="comparator"
                                        @filterChange="${e => this.filterChange(e, study.id, cohort.id)}">
                                </number-field-filter>

`;
                        })}

            `)}`;
    }

}

customElements.define("cohort-stats-filter", CohortStatsFilter);
<|endoftext|>


<commit_before>import ButtonBar        from '../../../../../../panels/ButtonBar';
import client           from '../../../../../../network';
import JobMonitor       from '../../../../../../panels/JobMonitor';
import merge            from 'mout/src/object/merge';
import React            from 'react';
import TaskflowManager  from '../../../../../../network/TaskflowManager';

const ACTIONS = {
  terminate: {
    name: 'terminateTaskflow',
    label: 'Terminate',
    icon: '',
  },
  visualize: {
    name: 'visualizeTaskflow',
    label: 'Visualize',
    icon: '',
  },
  rerun: {
    name: 'deleteTaskflow',
    label: 'Rerun',
    icon: '',
  },
};

export default React.createClass({
  displayName: 'pyfr/common/steps/Simulation/View',

  propTypes: {
    location: React.PropTypes.object,
    project: React.PropTypes.object,
    simulation: React.PropTypes.object,
    step: React.PropTypes.string,
    taskFlowName: React.PropTypes.string,
    view: React.PropTypes.string,
  },

  contextTypes: {
    router: React.PropTypes.object,
  },

  getInitialState() {
    return {
      taskflowId: '',
      error: '',
      actions: [
        ACTIONS.terminate,
      ],
    };
  },

  componentWillMount() {
    const taskflowId = this.props.simulation.steps[this.props.simulation.active].metadata.taskflowId;
    this.setState({ taskflowId });

    this.subscription = TaskflowManager.monitorTaskflow(taskflowId, (pkt) => {
      const actions = [];

      // some running -> terminate
      if (pkt.jobs.some(job => job.status === 'running') && pkt.jobs.some(job => job.name === 'pyfr')) {
        actions.push(ACTIONS.terminate);
      // every complete -> visualize
      } else if (pkt.jobs.every(job => job.status === 'complete')) {
        actions.push(ACTIONS.visualize);
      // every terminated -> rerun
      } else if (pkt.jobs.every(job => job.status === 'terminated')) {
        actions.push(ACTIONS.rerun);
      }

      // Refresh ui
      this.setState({ actions });
    });
  },

  componentWillUnmount() {
    if (this.subscription) {
      this.subscription.unsubscribe();
      this.subscription = null;
    }
  },

  onAction(action) {
    this[action]();
  },

  visualizeTaskflow() {
    client.activateSimulationStep(this.props.simulation, 'Visualization', null)
      .then((resp) => {
        this.context.router.replace({
          pathname: `View/Simulation/${this.props.simulation._id}/Visualization`,
          query: merge(this.props.location.query, { view: 'default' }),
          state: this.props.location.state,
        });
      })
      .catch((err) => {
        console.log('error: ', err);
      });
  },

  terminateTaskflow() {
    TaskflowManager.terminateTaskflow(this.props.simulation.steps[this.props.simulation.active].metadata.taskflowId);
  },

  deleteTaskflow() {
    TaskflowManager.deleteTaskflow(this.props.simulation.steps[this.props.simulation.active].metadata.taskflowId)
      .then((resp) =>
        client.updateSimulationStep(this.props.simulation._id, this.props.step, {
          view: 'default',
          metadata: {},
        })
      )
      .then((resp) => {
        this.context.router.replace({
          pathname: this.props.location.pathname,
          query: { view: 'default' },
          state: this.props.location.state,
        });
      })
      .catch((error) => {
        this.setState({ error: error.data.message });
      });
  },

  render() {
    return (

        <JobMonitor taskFlowId={ this.state.taskflowId } />

            <ButtonBar
              onAction={ this.onAction }
              actions={ this.state.actions }
              error={this.state.error}
            />

);
  },
});
<commit_msg>fix(StartSimulation): Fix Visualize button appearance based on full jobs and tasks completion<commit_after>import ButtonBar        from '../../../../../../panels/ButtonBar';
import client           from '../../../../../../network';
import JobMonitor       from '../../../../../../panels/JobMonitor';
import merge            from 'mout/src/object/merge';
import React            from 'react';
import TaskflowManager  from '../../../../../../network/TaskflowManager';

const ACTIONS = {
  terminate: {
    name: 'terminateTaskflow',
    label: 'Terminate',
    icon: '',
  },
  visualize: {
    name: 'visualizeTaskflow',
    label: 'Visualize',
    icon: '',
  },
  rerun: {
    name: 'deleteTaskflow',
    label: 'Rerun',
    icon: '',
  },
};

export default React.createClass({
  displayName: 'pyfr/common/steps/Simulation/View',

  propTypes: {
    location: React.PropTypes.object,
    project: React.PropTypes.object,
    simulation: React.PropTypes.object,
    step: React.PropTypes.string,
    taskFlowName: React.PropTypes.string,
    view: React.PropTypes.string,
  },

  contextTypes: {
    router: React.PropTypes.object,
  },

  getInitialState() {
    return {
      taskflowId: '',
      error: '',
      actions: [
        ACTIONS.terminate,
      ],
    };
  },

  componentWillMount() {
    const taskflowId = this.props.simulation.steps[this.props.simulation.active].metadata.taskflowId;
    this.setState({ taskflowId });

    this.subscription = TaskflowManager.monitorTaskflow(taskflowId, (pkt) => {
      const actions = [];

      // some running -> terminate
      if (pkt.jobs.some(job => job.status === 'running') && pkt.jobs.some(job => job.name === 'pyfr')) {
        actions.push(ACTIONS.terminate);
      // every complete -> visualize
      } else if (pkt.jobs.every(job => job.status === 'complete') && pkt.tasks.every(task => task.status === 'complete')) {
        actions.push(ACTIONS.visualize);
      // every terminated -> rerun
      } else if (pkt.jobs.every(job => job.status === 'terminated')) {
        actions.push(ACTIONS.rerun);
      }

      // Refresh ui
      this.setState({ actions });
    });
  },

  componentWillUnmount() {
    if (this.subscription) {
      this.subscription.unsubscribe();
      this.subscription = null;
    }
  },

  onAction(action) {
    this[action]();
  },

  visualizeTaskflow() {
    client.activateSimulationStep(this.props.simulation, 'Visualization', null)
      .then((resp) => {
        this.context.router.replace({
          pathname: `View/Simulation/${this.props.simulation._id}/Visualization`,
          query: merge(this.props.location.query, { view: 'default' }),
          state: this.props.location.state,
        });
      })
      .catch((err) => {
        console.log('error: ', err);
      });
  },

  terminateTaskflow() {
    TaskflowManager.terminateTaskflow(this.props.simulation.steps[this.props.simulation.active].metadata.taskflowId);
  },

  deleteTaskflow() {
    TaskflowManager.deleteTaskflow(this.props.simulation.steps[this.props.simulation.active].metadata.taskflowId)
      .then((resp) =>
        client.updateSimulationStep(this.props.simulation._id, this.props.step, {
          view: 'default',
          metadata: {},
        })
      )
      .then((resp) => {
        this.context.router.replace({
          pathname: this.props.location.pathname,
          query: { view: 'default' },
          state: this.props.location.state,
        });
      })
      .catch((error) => {
        this.setState({ error: error.data.message });
      });
  },

  render() {
    return (

        <JobMonitor taskFlowId={ this.state.taskflowId } />

            <ButtonBar
              onAction={ this.onAction }
              actions={ this.state.actions }
              error={this.state.error}
            />

);
  },
});
<|endoftext|>