<commit_before>angular.module('fec3App')

.controller('ordersummaryCtrl', function($scope, $loading, $localstorage, $routeParams, $linq, productService, $message) {

    $scope.id = $routeParams.id;
    $scope.name = $routeParams.name;
    $scope.dataType = 'D';

    var customerInfo = $localstorage.getObject("customerProfile");
    $scope.order_product_item_list = customerInfo.orderObj.order_product_item_list;

    // $scope.order_product_item_list = [{
    //        	"ORDER_ID": "1",
    // 	"SEQUENCE": 1,
    // 	"CAMPAIGN": "string",
    // 	"CAMPAIGN_NAME": "CAMPAIGN 2",
    // 	"PROMOTION_SET": "string",
    // 	"PROMOTION_TYPE": "string",
    // 	"CAMPAIGN_PROMO_ITEM_QTY": 2,
    // 	"GROUP_ID": "222",
    // 	"PRODUCT_TYPE": "string",
    // 	"PRODUCT_CODE": "string",
    // 	"PRODUCT_NAME": "3000036063 iPhone 6 Plus, 16GB, Gold",
    // 	"PRICEPLAN_CODE": "string",
    // 	"PRICEPLAN_NAME": "string",
    // 	"SERVICE_REGISTER_TYPE": "string",
    // 	"MOBILE_NUMBER": "string",
    // 	"PRICE": 29450.00 ,
    // 	"QTY": 1,
    // 	"TOTAL": 1,
    // 	"DISCOUNT_AMOUNT": 1.5,
    // 	"DEPOSIT_AMOUNT": 1.5,
    // 	"NET_AMOUNT": 29450,
    // 	"OTHER_PAYMENT_AMOUNT": 1.5
    // },{
    // 	"ORDER_ID": "2",
    // 	"SEQUENCE": 2,
    // 	"CAMPAIGN": "string",
    // 	"CAMPAIGN_NAME": "CAMPAIGN 1",
    // 	"PROMOTION_SET": "string",
    // 	"PROMOTION_TYPE": "string",
    // 	"CAMPAIGN_PROMO_ITEM_QTY": 2,
    // 	"GROUP_ID": "111",
    // 	"PRODUCT_TYPE": "string",
    // 	"PRODUCT_CODE": "string",
    // 	"PRODUCT_NAME": "3000014920 Sim Card",
    // 	"PRICEPLAN_CODE": "string",
    // 	"PRICEPLAN_NAME": "string",
    // 	"SERVICE_REGISTER_TYPE": "string",
    // 	"MOBILE_NUMBER": "string",
    // 	"PRICE": 69.00,
    // 	"QTY": 1,
    // 	"TOTAL": 1,
    // 	"DISCOUNT_AMOUNT": 1.5,
    // 	"DEPOSIT_AMOUNT": 1.5,
    // 	"NET_AMOUNT": 69,
    // 	"OTHER_PAYMENT_AMOUNT": 1.5
    // },{
    // 	"ORDER_ID": "3",
    // 	"SEQUENCE": 3,
    // 	"CAMPAIGN": "string",
    // 	"CAMPAIGN_NAME": "CAMPAIGN 3",
    // 	"PROMOTION_SET": "string",
    // 	"PROMOTION_TYPE": "string",
    // 	"CAMPAIGN_PROMO_ITEM_QTY": 1,
    // 	"GROUP_ID": "333",
    // 	"PRODUCT_TYPE": "string",
    // 	"PRODUCT_CODE": "string",
    // 	"PRODUCT_NAME": "3000014333 test",
    // 	"PRICEPLAN_CODE": "string",
    // 	"PRICEPLAN_NAME": "string",
    // 	"SERVICE_REGISTER_TYPE": "string",
    // 	"MOBILE_NUMBER": "string",
    // 	"PRICE": 169.00,
    // 	"QTY": 1,
    // 	"TOTAL": 1,
    // 	"DISCOUNT_AMOUNT": 1.5,
    // 	"DEPOSIT_AMOUNT": 1.5,
    // 	"NET_AMOUNT": 169,
    // 	"OTHER_PAYMENT_AMOUNT": 1.5
    // },{
    // 	"ORDER_ID": "4",
    // 	"SEQUENCE": 4,
    // 	"CAMPAIGN": "string",
    // 	"CAMPAIGN_NAME": "CAMPAIGN 1",
    // 	"PROMOTION_SET": "string",
    // 	"PROMOTION_TYPE": "string",
    // 	"CAMPAIGN_PROMO_ITEM_QTY": 2,
    // 	"GROUP_ID": "111",
    // 	"PRODUCT_TYPE": "string",
    // 	"PRODUCT_CODE": "string",
    // 	"PRODUCT_NAME": "2200005555 test2",
    // 	"PRICEPLAN_CODE": "string",
    // 	"PRICEPLAN_NAME": "string",
    // 	"SERVICE_REGISTER_TYPE": "string",
    // 	"MOBILE_NUMBER": "string",
    // 	"PRICE": 569.00,
    // 	"QTY": 1,
    // 	"TOTAL": 1,
    // 	"DISCOUNT_AMOUNT": 1.5,
    // 	"DEPOSIT_AMOUNT": 1.5,
    // 	"NET_AMOUNT": 569,
    // 	"OTHER_PAYMENT_AMOUNT": 1.5
    // },{
    // 	"ORDER_ID": "5",
    // 	"SEQUENCE": 5,
    // 	"CAMPAIGN": "string",
    // 	"CAMPAIGN_NAME": "CAMPAIGN 2",
    // 	"PROMOTION_SET": "string",
    // 	"PROMOTION_TYPE": "string",
    // 	"CAMPAIGN_PROMO_ITEM_QTY": 2,
    // 	"GROUP_ID": "222",
    // 	"PRODUCT_TYPE": "string",
    // 	"PRODUCT_CODE": "string",
    // 	"PRODUCT_NAME": "3344556677 test3",
    // 	"PRICEPLAN_CODE": "string",
    // 	"PRICEPLAN_NAME": "string",
    // 	"SERVICE_REGISTER_TYPE": "string",
    // 	"MOBILE_NUMBER": "string",
    // 	"PRICE": -5000.00,
    // 	"QTY": 1,
    // 	"TOTAL": 1,
    // 	"DISCOUNT_AMOUNT": 1.5,
    // 	"DEPOSIT_AMOUNT": 1.5,
    // 	"NET_AMOUNT": -5000,
    // 	"OTHER_PAYMENT_AMOUNT": 1.5
    // }];

    $scope.order_add = [];
    $scope.removeRow = function(GROUP_ID) {
        var count = 0;
        //var indexlist = [];
        var indexlist = -1;
        var oldgroup;
        var listArr = eval($scope.order_product_item_list);
        for (var i = 0; i < listArr.length; i++) {
            if (listArr[i].GROUP_ID === GROUP_ID) {
                $scope.order_product_item_list.splice(i, 1);
                i--;
                indexlist = i;
                count = count + 1;
                //console.log($scope.order_product_item_list);
                $scope.totalCalculate();
            }
        }
    };

    $scope.totalCalculate = function() {
        $scope.total = 0;
        for (var i = 0; i < $scope.order_product_item_list.length; i++) {
            $scope.total = $scope.total + parseInt($scope.order_product_item_list[i]['TOTAL']);
        }
    };

    $scope.totalCalculate();

    $scope.removeRow_modal = function(PRODUCT_NAME) {
        var count = 0;
        //var indexlist = [];
        var indexlist = -1;
        var oldgroup;
        var listArr = eval($scope.order_list);

        for (var i = 0; i < listArr.length; i++) {
            if (listArr[i].PRODUCT_NAME === PRODUCT_NAME) {
                $scope.order_list.splice(i, 1);
                i--;
                indexlist = i;
                count = count + 1;
                $scope.totalCalculate_modal();
            }
        }
    };

    $scope.totalCalculate_modal = function() {
        $scope.total_modal = 0;
        for (var i = 0; i < $scope.order_insert.length; i++) {
            $scope.total_modal = $scope.total_modal + parseInt($scope.order_insert[i]['TOTAL']);
        }
    };

    $scope.delete = function(Group_id, ORDER_ID) {
        var showName;
        var msg;
        var countItem;
        var listArr = $scope.order_product_item_list;
        for (var i = 0; i < listArr.length; i++) {
            if (listArr[i].ORDER_ID === ORDER_ID) {
                if (listArr[i].CAMPAIGN_PROMO_ITEM_QTY > 1) {
                    showName = listArr[i].CAMPAIGN_NAME;
                    countItem = listArr[i].CAMPAIGN_PROMO_ITEM_QTY;
                    msg = 'TH: The item that you want to delete is ' + showName + ' (' + countItem + ' item)  selling type. \
							Please confirm if you want to delete all set.
\
							 EN: รายการที่ท่านต้องการลบเป็นการขายแบบ ' + showName + ' (' + countItem + ' รายการ) กรุณายืนยันการลบทั้งกลุ่ม';
                } else {
                    showName = listArr[i].PRODUCT_NAME;
                    msg = 'EN: Please confirm to delete this item ' + showName + '.
TH: ท่านต้องการลบ ' + showName + ' หรือไม่';
                }
            }
        }

        $message.confirm(msg, function(result) {
            if (result.status) {
                $scope.removeRow(Group_id);
            }
        });
    };

    $scope.cleartext = function() {
        $scope.promotionSearch = null;
        if ($scope.promotionType == null) {
            document.getElementById('textcode').disabled = true;
        } else {
            document.getElementById('textcode').disabled = false;
        }
        document.getElementById("textcode").focus();
    }

    $scope.cleartext();
    $scope.order_insert = [];
    $scope.searchlist = function(event) {

        if ($scope.promotionSearch != null && $scope.promotionType != null) {
            productService.getDiscountAndBooking($scope.promotionType, $scope.promotionSearch, function(result) {
                $scope.order_insert.push(result.data["response-data"]);
                $scope.totalCalculate_modal();
                $scope.promotionSearch = null;
            });
        }
    }

    $scope.addToListCard = function() {
        var discountList;
        for (var i = 0; i < $scope.order_insert.length; i++) {
            discountList = $scope.order_insert[i]
            if (discountList.DISCOUNT_4_PROD_ITEM < 0) {
                $scope.order_product_item_list.push(discountList);
            } else {
                $scope.order_product_item_list.splice(discountList.DISCOUNT_4_PROD_ITEM, 0, discountList);
            }
            $scope.btnDisabled = true;
        }
        $scope.promotionSearch = null;
        $scope.order_insert = [];
        $scope.totalCalculate_modal();
        $scope.totalCalculate();
    }

});<commit_msg>fix issue local satorage of oder item deslete<commit_after>angular.module('fec3App')

.controller('ordersummaryCtrl', function($scope, $loading, $localstorage, $routeParams, $linq, productService, $message) {

    $scope.id = $routeParams.id;
    $scope.name = $routeParams.name;
    $scope.dataType = 'D';

    var customerInfo = $localstorage.getObject("customerProfile");
    $scope.order_product_item_list = customerInfo.orderObj.order_product_item_list;

    $scope.order_add = [];
    $scope.removeRow = function(GROUP_ID) {
        var count = 0;
        //var indexlist = [];
        var indexlist = -1;
        var oldgroup;
        var listArr = $scope.order_product_item_list;
        for (var i = 0; i < listArr.length; i++) {
            if (listArr[i].GROUP_ID === GROUP_ID) {
                $scope.order_product_item_list.splice(i, 1);
                i--;
                indexlist = i;
                count = count + 1;
                //console.log($scope.order_product_item_list);
                $scope.totalCalculate();
            }
        }
        customerInfo.orderObj.order_product_item_list = $scope.order_product_item_list;
        $localstorage.setObject("customerProfile", customerInfo);
        //$localstorage.logObject("customerProfile");
    };

    $scope.totalCalculate = function() {
        $scope.total = 0;
        for (var i = 0; i < $scope.order_product_item_list.length; i++) {
            $scope.total = $scope.total + parseInt($scope.order_product_item_list[i]['TOTAL']);
        }
    };

    $scope.totalCalculate();

    $scope.removeRow_modal = function(PRODUCT_NAME) {
        var count = 0;
        //var indexlist = [];
        var indexlist = -1;
        var oldgroup;
        var listArr = eval($scope.order_list);

        for (var i = 0; i < listArr.length; i++) {
            if (listArr[i].PRODUCT_NAME === PRODUCT_NAME) {
                $scope.order_list.splice(i, 1);
                i--;
                indexlist = i;
                count = count + 1;
                $scope.totalCalculate_modal();
            }
        }
    };

    $scope.totalCalculate_modal = function() {
        $scope.total_modal = 0;
        for (var i = 0; i < $scope.order_insert.length; i++) {
            $scope.total_modal = $scope.total_modal + parseInt($scope.order_insert[i]['TOTAL']);
        }
    };

    $scope.delete = function(Group_id, ORDER_ID) {
        var showName;
        var msg;
        var countItem;
        var listArr = $scope.order_product_item_list;
        for (var i = 0; i < listArr.length; i++) {
            if (listArr[i].ORDER_ID === ORDER_ID) {
                if (listArr[i].CAMPAIGN_PROMO_ITEM_QTY > 1) {
                    showName = listArr[i].CAMPAIGN_NAME;
                    countItem = listArr[i].CAMPAIGN_PROMO_ITEM_QTY;
                    msg = 'TH: The item that you want to delete is ' + showName + ' (' + countItem + ' item)  selling type. \
							Please confirm if you want to delete all set.
\
							 EN: รายการที่ท่านต้องการลบเป็นการขายแบบ ' + showName + ' (' + countItem + ' รายการ) กรุณายืนยันการลบทั้งกลุ่ม';
                } else {
                    showName = listArr[i].PRODUCT_NAME;
                    msg = 'EN: Please confirm to delete this item ' + showName + '.
TH: ท่านต้องการลบ ' + showName + ' หรือไม่';
                }
            }
        }

        $message.confirm(msg, function(result) {
            if (result.status) {
                $scope.removeRow(Group_id);
            }
        });
    };

    $scope.cleartext = function() {
        $scope.promotionSearch = null;
        if ($scope.promotionType == null) {
            document.getElementById('textcode').disabled = true;
        } else {
            document.getElementById('textcode').disabled = false;
        }
        document.getElementById("textcode").focus();
    }

    $scope.cleartext();
    $scope.order_insert = [];
    $scope.searchlist = function(event) {

        if ($scope.promotionSearch != null && $scope.promotionType != null) {
            productService.getDiscountAndBooking($scope.promotionType, $scope.promotionSearch, function(result) {
                $scope.order_insert.push(result.data["response-data"]);
                $scope.totalCalculate_modal();
                $scope.promotionSearch = null;
            });
        }
    }

    $scope.addToListCard = function() {
        var discountList;
        for (var i = 0; i < $scope.order_insert.length; i++) {
            discountList = $scope.order_insert[i]
            if (discountList.DISCOUNT_4_PROD_ITEM < 0) {
                $scope.order_product_item_list.push(discountList);
            } else {
                $scope.order_product_item_list.splice(discountList.DISCOUNT_4_PROD_ITEM, 0, discountList);
            }
            $scope.btnDisabled = true;
        }
        $scope.promotionSearch = null;
        $scope.order_insert = [];
        $scope.totalCalculate_modal();
        $scope.totalCalculate();
    }

});<|endoftext|>


<commit_before>/*global angular */

/**
 * @ngdoc directive
 * @name vjsVideoApp.directive:vjs.directive.js
 * @description
 * # vjs.directive.js
 */
(function () {
    'use strict';

    var module = angular.module('vjs.video', []);

    module.controller('VjsVideoController', ['$scope', function ($scope) {
        var self = this;

        function getVidElement(element, isContainer) {
            var vid = null,
                videos;

            if (!window.videojs) {
                throw new Error('video.js was not found!');
            }

            if (isContainer) {
                videos = element[0].getElementsByTagName('video');
                if (videos.length === 0) {
                    throw new Error('video tag must be defined within container directive!');
                } else if (videos.length > 1) {
                    throw new Error('only one video can be defined within the container directive!');
                }

                vid = videos[0];
            } else {
                if (element[0].nodeName === 'VIDEO') {
                    vid = element[0];
                } else {
                    throw new Error('directive must be attached to a video tag!');
                }
            }

            return vid;
        }

        function applyRatio(el, ratioVal) {
            var ratio = ratioVal,
                style = document.createElement('style'),
                parseRatio = function (r) {
                    var tokens = r.split('/'),
                        tokenErrorMsg = 'the ratio must either be "wide", "standard" or ' +
                                        'decimal values in the format of w/h';

                    //if invalid ratio throw an error
                    if (tokens.length !== 2) {
                        throw new Error(tokenErrorMsg);
                    }

                    //confirm that both tokens are numbers
                    if (isNaN(tokens[0]) || isNaN(tokens[1])) {
                        throw new Error(tokenErrorMsg);
                    }

                    //confirm that the width or height is not zero
                    if (Number(tokens[0]) === 0 || Number(tokens[1]) === 0) {
                        throw new Error('neither the width or height ratio can be zero!');
                    }

                    return (Number(tokens[1]) / Number(tokens[0])) * 100;
                },
                genContainerId = function (element) {
                    var container = element[0].querySelector('.vjs-tech'),
                        vjsId;

                    if (container) {
                        vjsId = 'vjs-container-' + container.getAttribute('id');
                    } else {
                        //vjsId = 'vjs-container-default';
                        throw new Error('Failed to find instance of video-js class!');
                    }

                    //add generated id to container
                    element[0].setAttribute('id', vjsId);

                    return vjsId;
                },
                containerId,
                ratioPercentage,
                css;

            //if ratio isn't defined lets default to wide screen
            if (!ratio) {
                ratio = '16/9';
            }

            switch (ratio) {
            case 'wide':
                ratio = '16/9';
                break;
            case 'standard':
                ratio = '4/3';
                break;
            }

            containerId = genContainerId(el);

            ratioPercentage = parseRatio(ratio);

            css = ['#', containerId, ' ',
                   '.video-js {padding-top:', ratioPercentage,
                   '%;}\n', '.vjs-fullscreen {padding-top: 0px;}'].join('');

            style.type = 'text/css';
            style.rel = 'stylesheet';
            if (style.styleSheet) {
                style.styleSheet.cssText = css;
            } else {
                style.appendChild(document.createTextNode(css));
            }

            el[0].appendChild(style);
        }

        function generateMedia(ctrl, mediaChangedHandler) {
            var errMsgNoValid = 'a sources and/or tracks element must be ' +
                                'defined for the vjs-media attribute',
                errMsgNoSrcs  = 'sources must be an array of objects with at ' +
                                'least one item',
                errMsgNoTrks  = 'tracks must be an array of objects with at ' +
                                'least one item',
                div,
                curDiv;
            //check to see if vjsMedia is defined
            if (!ctrl.vjsMedia) {
                return;
            }

            //if sources and tracks aren't defined, throw an error
            if (!ctrl.vjsMedia.sources && !ctrl.vjsMedia.tracks) {
                throw new Error(errMsgNoValid);
            }

            //verify sources and tracks are arrays if they are defined
            if (ctrl.vjsMedia.sources && !(ctrl.vjsMedia.sources instanceof Array)) {
                throw new Error(errMsgNoSrcs);
            }
            if (ctrl.vjsMedia.tracks && !(ctrl.vjsMedia.tracks instanceof Array)) {
                throw new Error(errMsgNoTrks);
            }

            //build DOM elements for sources and tracks as children to a div
            div = document.createElement("div");

            if (ctrl.vjsMedia.sources) {
                ctrl.vjsMedia.sources.forEach(function (curObj) {
                    curDiv = document.createElement('source');
                    curDiv.setAttribute('src', curObj.src || "");
                    curDiv.setAttribute('type', curObj.type || "");
                    div.appendChild(curDiv);
                });
            }

            if (ctrl.vjsMedia.tracks) {
                ctrl.vjsMedia.tracks.forEach(function (curObj) {
                    curDiv = document.createElement('track');
                    curDiv.setAttribute('kind', curObj.kind || "");
                    curDiv.setAttribute('label', curObj.label || "");
                    curDiv.setAttribute('src', curObj.src || "");
                    curDiv.setAttribute('srclang', curObj.srclang || "");
                    div.appendChild(curDiv);
                });
            }

            //invoke callback
            mediaChangedHandler.call(undefined, {element: div});

        }

        function initVideoJs(vid, params, element, mediaChangedHandler) {
            var opts = params.vjsSetup || {},
                ratio = params.vjsRatio,
                isContainer = (element[0].nodeName !== 'VIDEO') ? true : false,
                elementClone = element.clone(),
                mediaWatcher;

            if (!window.videojs) {
                return null;
            }

            //override poster settings if defined in vjsMedia
            if (params.vjsMedia && params.vjsMedia.poster) {
                opts.poster = params.vjsMedia.poster;
            }

            //generate any defined sources or tracks
            generateMedia(params, mediaChangedHandler);

            //watch for changes to vjs-media
            mediaWatcher = $scope.$watch(
                function (s) {
                    return params.vjsMedia;
                },
                function (newVal, oldVal) {
                    var compiledEl,
                        newScope;

                    if (newVal && !angular.equals(newVal, oldVal)) {
                        //deregister watcher
                        mediaWatcher();

                        if (isContainer) {
                            window.videojs(vid).dispose();
                            $scope.$emit('vjsVideoMediaChanged');
                        } else {
                            $scope.$emit('vjsVideoMediaChanged');
                        }
                    }
                }
            );

            //bootstrap videojs
            window.videojs(vid, opts, function () {
                if (isContainer) {
                    applyRatio(element, ratio);
                }

                //emit ready event with reference to video
                $scope.$emit('vjsVideoReady', {
                    id: vid.getAttribute('id'),
                    vid: this
                });
            });

            //dispose of videojs before destroying directive
            $scope.$on('$destroy', function () {
                window.videojs(vid).dispose();
            });
        }

        //export public methods
        self.initVideoJs = initVideoJs;
        self.getVidElement = getVidElement;
    }]);

    module.directive('vjsVideo', ['$compile', '$timeout', function ($compile, $timeout) {

        return {
            restrict: 'A',
            transclude: true,
            scope: {
                vjsSetup: '=',
                vjsMedia: '='
            },
            controller: 'VjsVideoController',
            controllerAs: 'vjsCtrl',
            bindToController: true,
            link: function postLink(scope, element, attrs, ctrl, transclude) {
                var vid,
                    parentContainer,
                    origContent,
                    compiledEl,
                    mediaChangedHandler = function (e) {
                        //remove any inside contents
                        element.children().remove();
                        //add generated sources and tracks
                        element.append(e.element.childNodes);
                    },
                    init = function () {
                        vid = ctrl.getVidElement(element);

                        ctrl.initVideoJs(vid, ctrl, element, mediaChangedHandler);
                        //attach transcluded content
                        transclude(function (content) {
                            element.append(content);
                        });
                    };

                origContent = element.clone();

                //we need to wrap the video inside of a div
                //for easier DOM management
                if (!element.parent().hasClass('vjs-video-wrap')) {
                    element.wrap('

');
                }

                parentContainer = element.parent();

                scope.$on('vjsVideoMediaChanged', function (e) {
                    //remove current directive instance
                    //destroy will trigger a video.js dispose
                    $timeout(function () {
                        scope.$destroy();
                    });

                    //compile the new directive and add it to the DOM
                    compiledEl = origContent.clone();
                    parentContainer.append(compiledEl);
                    //it is key to pass in the parent scope to the directive
                    compiledEl = $compile(compiledEl)(scope.$parent);
                });

                init();
            }
        };
    }]);

    module.directive('vjsVideoContainer', function () {

        return {
            restrict: 'AE',
            transclude: true,
            templateUrl: 'scripts/directives/vjs.container.html',
            scope: {
                vjsSetup: '=',
                vjsRatio: '@',
                vjsMedia: '='
            },
            controller: 'VjsVideoController',
            controllerAs: 'vjsCtrl',
            bindToController: true,
            link: function postLink(scope, element, attrs, ctrl, transclude) {
                var vid,
                    origContent,
                    mediaChangedHandler = function (e) {
                        var vidEl = element[0].querySelector('video');

                        if (vidEl) {
                            //remove any inside contents
                            while (vidEl.firstChild) {
                                vidEl.removeChild(vidEl.firstChild);
                            }

                            //add generated sources and tracks
                            while (e.element.childNodes.length > 0) {
                                vidEl.appendChild(e.element.childNodes[0]);
                            }
                        }
                    },
                    init = function () {
                        vid = ctrl.getVidElement(element, true);

                        //set width and height of video to auto
                        vid.setAttribute('width', 'auto');
                        vid.setAttribute('height', 'auto');

                        //bootstrap video js
                        ctrl.initVideoJs(vid, ctrl, element, mediaChangedHandler);
                    };

                //save original content
                transclude(function (content) {
                    origContent = content.clone();
                });

                scope.$on('vjsVideoMediaChanged', function (e) {
                    //replace element children with orignal content
                    element.children().remove();
                    element.append(origContent.clone());
                    init();
                });

                init();
            }
        };
    });
}());
<commit_msg>Fixed issue where video.js instances weren't removed from the DOM in v 5.0<commit_after>/*global angular */

/**
 * @ngdoc directive
 * @name vjsVideoApp.directive:vjs.directive.js
 * @description
 * # vjs.directive.js
 */
(function () {
    'use strict';

    var module = angular.module('vjs.video', []);

    module.controller('VjsVideoController', ['$scope', function ($scope) {
        var self = this;

        function getVidElement(element, isContainer) {
            var vid = null,
                videos;

            if (!window.videojs) {
                throw new Error('video.js was not found!');
            }

            if (isContainer) {
                videos = element[0].getElementsByTagName('video');
                if (videos.length === 0) {
                    throw new Error('video tag must be defined within container directive!');
                } else if (videos.length > 1) {
                    throw new Error('only one video can be defined within the container directive!');
                }

                vid = videos[0];
            } else {
                if (element[0].nodeName === 'VIDEO') {
                    vid = element[0];
                } else {
                    throw new Error('directive must be attached to a video tag!');
                }
            }

            return vid;
        }

        function applyRatio(el, ratioVal) {
            var ratio = ratioVal,
                style = document.createElement('style'),
                parseRatio = function (r) {
                    var tokens = r.split('/'),
                        tokenErrorMsg = 'the ratio must either be "wide", "standard" or ' +
                                        'decimal values in the format of w/h';

                    //if invalid ratio throw an error
                    if (tokens.length !== 2) {
                        throw new Error(tokenErrorMsg);
                    }

                    //confirm that both tokens are numbers
                    if (isNaN(tokens[0]) || isNaN(tokens[1])) {
                        throw new Error(tokenErrorMsg);
                    }

                    //confirm that the width or height is not zero
                    if (Number(tokens[0]) === 0 || Number(tokens[1]) === 0) {
                        throw new Error('neither the width or height ratio can be zero!');
                    }

                    return (Number(tokens[1]) / Number(tokens[0])) * 100;
                },
                genContainerId = function (element) {
                    var container = element[0].querySelector('.vjs-tech'),
                        vjsId;

                    if (container) {
                        vjsId = 'vjs-container-' + container.getAttribute('id');
                    } else {
                        //vjsId = 'vjs-container-default';
                        throw new Error('Failed to find instance of video-js class!');
                    }

                    //add generated id to container
                    element[0].setAttribute('id', vjsId);

                    return vjsId;
                },
                containerId,
                ratioPercentage,
                css;

            //if ratio isn't defined lets default to wide screen
            if (!ratio) {
                ratio = '16/9';
            }

            switch (ratio) {
            case 'wide':
                ratio = '16/9';
                break;
            case 'standard':
                ratio = '4/3';
                break;
            }

            containerId = genContainerId(el);

            ratioPercentage = parseRatio(ratio);

            css = ['#', containerId, ' ',
                   '.video-js {padding-top:', ratioPercentage,
                   '%;}\n', '.vjs-fullscreen {padding-top: 0px;}'].join('');

            style.type = 'text/css';
            style.rel = 'stylesheet';
            if (style.styleSheet) {
                style.styleSheet.cssText = css;
            } else {
                style.appendChild(document.createTextNode(css));
            }

            el[0].appendChild(style);
        }

        function generateMedia(ctrl, mediaChangedHandler) {
            var errMsgNoValid = 'a sources and/or tracks element must be ' +
                                'defined for the vjs-media attribute',
                errMsgNoSrcs  = 'sources must be an array of objects with at ' +
                                'least one item',
                errMsgNoTrks  = 'tracks must be an array of objects with at ' +
                                'least one item',
                div,
                curDiv;
            //check to see if vjsMedia is defined
            if (!ctrl.vjsMedia) {
                return;
            }

            //if sources and tracks aren't defined, throw an error
            if (!ctrl.vjsMedia.sources && !ctrl.vjsMedia.tracks) {
                throw new Error(errMsgNoValid);
            }

            //verify sources and tracks are arrays if they are defined
            if (ctrl.vjsMedia.sources && !(ctrl.vjsMedia.sources instanceof Array)) {
                throw new Error(errMsgNoSrcs);
            }
            if (ctrl.vjsMedia.tracks && !(ctrl.vjsMedia.tracks instanceof Array)) {
                throw new Error(errMsgNoTrks);
            }

            //build DOM elements for sources and tracks as children to a div
            div = document.createElement("div");

            if (ctrl.vjsMedia.sources) {
                ctrl.vjsMedia.sources.forEach(function (curObj) {
                    curDiv = document.createElement('source');
                    curDiv.setAttribute('src', curObj.src || "");
                    curDiv.setAttribute('type', curObj.type || "");
                    div.appendChild(curDiv);
                });
            }

            if (ctrl.vjsMedia.tracks) {
                ctrl.vjsMedia.tracks.forEach(function (curObj) {
                    curDiv = document.createElement('track');
                    curDiv.setAttribute('kind', curObj.kind || "");
                    curDiv.setAttribute('label', curObj.label || "");
                    curDiv.setAttribute('src', curObj.src || "");
                    curDiv.setAttribute('srclang', curObj.srclang || "");
                    div.appendChild(curDiv);
                });
            }

            //invoke callback
            mediaChangedHandler.call(undefined, {element: div});

        }

        function initVideoJs(vid, params, element, mediaChangedHandler) {
            var opts = params.vjsSetup || {},
                ratio = params.vjsRatio,
                isContainer = (element[0].nodeName !== 'VIDEO') ? true : false,
                elementClone = element.clone(),
                mediaWatcher;

            if (!window.videojs) {
                return null;
            }

            //override poster settings if defined in vjsMedia
            if (params.vjsMedia && params.vjsMedia.poster) {
                opts.poster = params.vjsMedia.poster;
            }

            //generate any defined sources or tracks
            generateMedia(params, mediaChangedHandler);

            //watch for changes to vjs-media
            mediaWatcher = $scope.$watch(
                function (s) {
                    return params.vjsMedia;
                },
                function (newVal, oldVal) {
                    var compiledEl,
                        newScope;

                    if (newVal && !angular.equals(newVal, oldVal)) {
                        //deregister watcher
                        mediaWatcher();

                        if (isContainer) {
                            window.videojs(vid).dispose();
                            $scope.$emit('vjsVideoMediaChanged');
                        } else {
                            $scope.$emit('vjsVideoMediaChanged');
                        }
                    }
                }
            );

            //bootstrap videojs
            window.videojs(vid, opts, function () {
                if (isContainer) {
                    applyRatio(element, ratio);
                }

                //emit ready event with reference to video
                $scope.$emit('vjsVideoReady', {
                    id: vid.getAttribute('id'),
                    vid: this
                });
            });

            //dispose of videojs before destroying directive
            $scope.$on('$destroy', function () {
                window.videojs(vid).dispose();
            });
        }

        //export public methods
        self.initVideoJs = initVideoJs;
        self.getVidElement = getVidElement;
    }]);

    module.directive('vjsVideo', ['$compile', '$timeout', function ($compile, $timeout) {

        return {
            restrict: 'A',
            transclude: true,
            scope: {
                vjsSetup: '=',
                vjsMedia: '='
            },
            controller: 'VjsVideoController',
            controllerAs: 'vjsCtrl',
            bindToController: true,
            link: function postLink(scope, element, attrs, ctrl, transclude) {
                var vid,
                    parentContainer,
                    origContent,
                    compiledEl,
                    mediaChangedHandler = function (e) {
                        //remove any inside contents
                        element.children().remove();
                        //add generated sources and tracks
                        element.append(e.element.childNodes);
                    },
                    init = function () {
                        vid = ctrl.getVidElement(element);

                        ctrl.initVideoJs(vid, ctrl, element, mediaChangedHandler);
                        //attach transcluded content
                        transclude(function (content) {
                            element.append(content);
                        });
                    };

                origContent = element.clone();

                //we need to wrap the video inside of a div
                //for easier DOM management
                if (!element.parent().hasClass('vjs-video-wrap')) {
                    element.wrap('

');
                }

                parentContainer = element.parent();

                scope.$on('vjsVideoMediaChanged', function (e) {
                    //retreive base element that video.js creates
                    var staleChild = parentContainer.children()[0];

                    //remove current directive instance
                    //destroy will trigger a video.js dispose
                    $timeout(function () {
                        scope.$destroy();
                    });

                    //compile the new directive and add it to the DOM
                    compiledEl = origContent.clone();
                    parentContainer.append(compiledEl);
                    //it is key to pass in the parent scope to the directive
                    compiledEl = $compile(compiledEl)(scope.$parent);

                    //remove original element created by video.js
                    staleChild.remove();
                });

                init();
            }
        };
    }]);

    module.directive('vjsVideoContainer', function () {

        return {
            restrict: 'AE',
            transclude: true,
            templateUrl: 'scripts/directives/vjs.container.html',
            scope: {
                vjsSetup: '=',
                vjsRatio: '@',
                vjsMedia: '='
            },
            controller: 'VjsVideoController',
            controllerAs: 'vjsCtrl',
            bindToController: true,
            link: function postLink(scope, element, attrs, ctrl, transclude) {
                var vid,
                    origContent,
                    mediaChangedHandler = function (e) {
                        var vidEl = element[0].querySelector('video');

                        if (vidEl) {
                            //remove any inside contents
                            while (vidEl.firstChild) {
                                vidEl.removeChild(vidEl.firstChild);
                            }

                            //add generated sources and tracks
                            while (e.element.childNodes.length > 0) {
                                vidEl.appendChild(e.element.childNodes[0]);
                            }
                        }
                    },
                    init = function () {
                        vid = ctrl.getVidElement(element, true);

                        //set width and height of video to auto
                        vid.setAttribute('width', 'auto');
                        vid.setAttribute('height', 'auto');

                        //bootstrap video js
                        ctrl.initVideoJs(vid, ctrl, element, mediaChangedHandler);
                    };

                //save original content
                transclude(function (content) {
                    origContent = content.clone();
                });

                scope.$on('vjsVideoMediaChanged', function (e) {
                    //replace element children with orignal content
                    element.children().remove();
                    element.append(origContent.clone());
                    init();
                });

                init();
            }
        };
    });
}());
<|endoftext|>


<commit_before>class Connection {
  constructor(ws, { addHost, getDisplayList, onConnectRequest }) {
    this.ws = ws;
    this.addHost = addHost;
    this.getDisplayList = getDisplayList;
    this.onConnectRequest = onConnectRequest;
    this.type = this.VISITOR;
    this._handleMessage = this._onVisitorMessage;

    this.clients = new ClientList;
    this.nextClientID = 1; // not an array position

    this.hostID;
    this.hostName;

    this.host;
  }

  onMessage(str) {
    this._handleMessage(str);
  }

  _onVisitorMessage(str) {
    const req = JSON.parse(str);
    switch(req.type) {
      case 'CONNECT': //props: hostName AND/OR hostID AND/OR <anything>
        this.host = this.onConnectRequest(req);
        if(this.host==null) {
          this.ws.send(JSON.stringify({
            type: 'NO_SUCH_HOST',
            request: req,
          }));
        } else {
          this.host.newClient(this, req);

          this._handleMessage = this._onClientMessage;

          this.ws.send(JSON.stringify({
            type: 'CONNECTED',
            hostID: this.host.hostID,
            hostName: this.host.hostName,
            request: req,
          }));
        }
        break;
      case 'HOST': //props: hostName
        this.type = this.HOST;
        this._handleMessage = this._onHostMessage;
        this.hostID = this.addHost(this);
        this.hostName = req.hostName;
        this.ws.send(JSON.stringify({
          type: 'REGISTERED',
          hostID: this.hostID,
          hostName: this.hostName,
        }));
        break;
      case 'LIST':
        this.ws.send(JSON.stringify({
          type: 'LIST',
          payload: this.getDisplayList(),
        }));
        break;
      default:
        log('Unknown request type:', req.type, 'for:', req);
    }
  }

  _onHostMessage(str) {
    const req = JSON.parse(str);
    log('+ request from Host:', str);
    switch(req.type) {
      case 'SEND':
        let { clientIDs, message } = req;
        if(Array.isArray(clientIDs)) {
          clientIDs.forEach(id => {
            const client = this.clients.hash[id];
            if(client) {
              client.ws.send(message);
            }
          });
        } else {
          const client = this.clients.hash[clientIDs];
          if(client) {
            client.ws.send(message);
          }
        }
        break;
      default:
        log('! Unknown request from a host:', str);
    }
  }

  newClient(clientConnection, req) {
    const clientID = this.nextClientID++;
    clientConnection.setID(clientID);
    this.clients.addClient(clientConnection);

    this.ws.send(JSON.stringify({
      type: 'NEW_CLIENT',
      clientID,
      request: req,
    }));
  }

  _onClientMessage(str) {
    log('+ request from Client:', str);
    this.host.ws.send(JSON.stringify({
      type: 'FROM_CLIENT',
      clientID: this.clientID,
      message: str,
    }));
  }

  setID(clientID) {
    this.clientID = clientID;
  }
}

class ClientList {
  constructor() {
    this.hash = {};
    this.array = [];
  }

  addClient(client) {
    this.hash[client.clientID] = client;
    this.array.push(client);
  }

  removeClient(client) {
    delete this.hash[client.clientID];
    const index = this.array.indexOf(client);
    if(index >= 0) {
      this.array = this.array.slice().splice(index, 1);
    }
  }
}

Connection.VISITOR = null;
Connection.CLIENT = 1;
Connection.HOST = 2;

module.exports = Connection;
<commit_msg>Keep callbacks in same object for easier management<commit_after>class Connection {
  // options: { addHost, getDisplayList, onConnectRequest, onClose }
  constructor(ws, options) {
    this.ws = ws;
    this.options = options;
    this.type = this.VISITOR;
    this._handleMessage = this._onVisitorMessage;

    this.clients = new ClientList;
    this.nextClientID = 1; // not an array position

    this.hostID;
    this.hostName;

    this.host;
  }

  onMessage(str) {
    log('Received: %s', str);
    try {
      this._handleMessage(str);
    } catch (e) {
      log('ERROR onMessage: ',e,'\n- Trying to Process: `'+str+'`');
    }
  }

  onClose() {
    log('* Lost Connection');
    this.options.onClose();
  }

  _onVisitorMessage(str) {
    const req = JSON.parse(str);
    switch(req.type) {
      case 'CONNECT': //props: hostName AND/OR hostID AND/OR <anything>
        this.host = this.options.onConnectRequest(req);
        if(this.host==null) {
          this.ws.send(JSON.stringify({
            type: 'NO_SUCH_HOST',
            request: req,
          }));
        } else {
          this.host.newClient(this, req);

          this._handleMessage = this._onClientMessage;

          this.ws.send(JSON.stringify({
            type: 'CONNECTED',
            hostID: this.host.hostID,
            hostName: this.host.hostName,
            request: req,
          }));
        }
        break;
      case 'HOST': //props: hostName
        this.type = this.HOST;
        this._handleMessage = this._onHostMessage;
        this.hostID = this.options.addHost(this);
        this.hostName = req.hostName;
        this.ws.send(JSON.stringify({
          type: 'REGISTERED',
          hostID: this.hostID,
          hostName: this.hostName,
        }));
        break;
      case 'LIST':
        this.ws.send(JSON.stringify({
          type: 'LIST',
          payload: this.options.getDisplayList(),
        }));
        break;
      default:
        log('Unknown request type:', req.type, 'for:', req);
    }
  }

  _onHostMessage(str) {
    const req = JSON.parse(str);
    log('+ request from Host:', str);
    switch(req.type) {
      case 'SEND':
        let { clientIDs, message } = req;
        if(Array.isArray(clientIDs)) {
          clientIDs.forEach(id => {
            const client = this.clients.hash[id];
            if(client) {
              client.ws.send(message);
            }
          });
        } else {
          const client = this.clients.hash[clientIDs];
          if(client) {
            client.ws.send(message);
          }
        }
        break;
      default:
        log('! Unknown request from a host:', str);
    }
  }

  newClient(clientConnection, req) {
    const clientID = this.nextClientID++;
    clientConnection.setID(clientID);
    this.clients.addClient(clientConnection);

    this.ws.send(JSON.stringify({
      type: 'NEW_CLIENT',
      clientID,
      request: req,
    }));
  }

  _onClientMessage(str) {
    log('+ request from Client:', str);
    this.host.ws.send(JSON.stringify({
      type: 'FROM_CLIENT',
      clientID: this.clientID,
      message: str,
    }));
  }

  setID(clientID) {
    this.clientID = clientID;
  }
}

class ClientList {
  constructor() {
    this.hash = {};
    this.array = [];
  }

  addClient(client) {
    this.hash[client.clientID] = client;
    this.array.push(client);
  }

  removeClient(client) {
    delete this.hash[client.clientID];
    const index = this.array.indexOf(client);
    if(index >= 0) {
      this.array = this.array.slice().splice(index, 1);
    }
  }
}

Connection.VISITOR = null;
Connection.CLIENT = 1;
Connection.HOST = 2;

module.exports = Connection;
<|endoftext|>


<commit_before>'use strict';
var _ = require('lodash');
var config = require('./config');
var RippleAPI = require('../api');
var logger = require('./logger').logger;

var OPTIONS = {
  servers: config.get('rippled_servers'),
  max_fee: parseFloat(config.get('max_transaction_fee')),
  database_path: config.get('NODE_ENV') === 'test'
    ? ':memory:' : config.get('db_path'),
  logger: logger,
  mock: config.get('NODE_ENV') === 'test',
  trace: config.get('debug') || false
};

function init(options) {
  return new RippleAPI(_.assign({}, OPTIONS, options || {}));
}

module.exports = init;
<commit_msg>proxy support<commit_after>'use strict';
var _ = require('lodash');
var config = require('./config');
var RippleAPI = require('../api');
var logger = require('./logger').logger;

var OPTIONS = {
  servers: config.get('rippled_servers'),
  proxy: config.get('proxy'),
  max_fee: parseFloat(config.get('max_transaction_fee')),
  database_path: config.get('NODE_ENV') === 'test'
    ? ':memory:' : config.get('db_path'),
  logger: logger,
  mock: config.get('NODE_ENV') === 'test',
  trace: config.get('debug') || false
};

function init(options) {
  return new RippleAPI(_.assign({}, OPTIONS, options || {}));
}

module.exports = init;
<|endoftext|>


<commit_before>import React from 'react';
import { RouterContext } from 'react-router';
import debug from 'debug';
import { renderToString } from 'redux-epic';

import provideStore from '../../common/app/provide-store';
import createApp from '../../common/app';
import blockNameify from '../../common/app/utils/blockNameify';

const log = debug('fcc:react-server');

// add routes here as they slowly get reactified
// remove their individual controllers
const routes = [
  '/challenges',
  '/challenges/*',
  '/map',
  '/settings',
  '/settings/*'
];

const devRoutes = [];

export default function reactSubRouter(app) {
  var router = app.loopback.Router();

  router.get('/videos', (req, res) => res.redirect('/map'));
  router.get(
    '/videos/:dashedName',
    (req, res) => res.redirect(`/challenges/${req.params.dashedName}`)
  );

  // These routes are in production
  routes.forEach((route) => {
    router.get(route, serveReactApp);
  });

  if (process.env.NODE_ENV === 'development') {
    devRoutes.forEach(function(route) {
      router.get(route, serveReactApp);
    });
  }

  app.use('/:lang', router);

  function serveReactApp(req, res, next) {
    const { lang } = req;
    const serviceOptions = { req };
    createApp({
      serviceOptions,
      location: req.originalUrl,
      initialState: { app: { lang } }
    })
      // if react-router does not find a route send down the chain
      .filter(({ redirect, props }) => {
        if (!props && redirect) {
          log('react router found a redirect');
          return res.redirect(redirect.pathname + redirect.search);
        }
        if (!props) {
          log(`react tried to find ${req.path} but got 404`);
          return next();
        }
        return !!props;
      })
      .flatMap(({ props, store, epic }) => {
        log('render react markup and pre-fetch data');

        return renderToString(
          provideStore(React.createElement(RouterContext, props), store),
          epic
        )
          .map(({ markup }) => ({ markup, store, epic }));
      })
      .filter(({ store, epic }) => {
        const { delayedRedirect } = store.getState().app;
        if (delayedRedirect) {
          res.redirect(delayedRedirect);
          epic.dispose();
          return false;
        }
        return true;
      })
      .flatMap(function({ markup, store, epic }) {
        log('react markup rendered, data fetched');
        const state = store.getState();
        const { challenge } = state.entities;
        const challengeKey = Object.keys(challenge)[0];
        const blockName = blockNameify(challenge[challengeKey].block);
        const challengeTitle = challenge[challengeKey].title;
        const title = `${blockName}: ${challengeTitle}`;
        epic.dispose();
        res.expose(state, 'data');
        res.expose(req.flash(), 'flash');
        return res.render$(
          'layout-react',
          { markup, title }
        );
      })
      .doOnNext(markup => res.send(markup))
      .subscribe(
        () => log('html rendered and ready to send'),
        next
      );
  }
}
<commit_msg>fix: decouple rendering from pages<commit_after>import React from 'react';
import { RouterContext } from 'react-router';
import debug from 'debug';
import { renderToString } from 'redux-epic';

import createApp from '../../common/app';
import provideStore from '../../common/app/provide-store';

const log = debug('fcc:react-server');

// add routes here as they slowly get reactified
// remove their individual controllers
const routes = [
  '/challenges',
  '/challenges/*',
  '/map',
  '/settings',
  '/settings/*'
];

const devRoutes = [];

export default function reactSubRouter(app) {
  var router = app.loopback.Router();

  router.get('/videos', (req, res) => res.redirect('/map'));
  router.get(
    '/videos/:dashedName',
    (req, res) => res.redirect(`/challenges/${req.params.dashedName}`)
  );

  // These routes are in production
  routes.forEach((route) => {
    router.get(route, serveReactApp);
  });

  if (process.env.NODE_ENV === 'development') {
    devRoutes.forEach(function(route) {
      router.get(route, serveReactApp);
    });
  }

  app.use('/:lang', router);

  function serveReactApp(req, res, next) {
    const { lang } = req;
    const serviceOptions = { req };
    createApp({
      serviceOptions,
      location: req.originalUrl,
      initialState: { app: { lang } }
    })
      // if react-router does not find a route send down the chain
      .filter(({ redirect, props }) => {
        if (!props && redirect) {
          log('react router found a redirect');
          return res.redirect(redirect.pathname + redirect.search);
        }
        if (!props) {
          log(`react tried to find ${req.path} but got 404`);
          return next();
        }
        return !!props;
      })
      .flatMap(({ props, store, epic }) => {
        log('render react markup and pre-fetch data');

        return renderToString(
          provideStore(React.createElement(RouterContext, props), store),
          epic
        )
          .map(({ markup }) => ({ markup, store, epic }));
      })
      .filter(({ store, epic }) => {
        const { delayedRedirect } = store.getState().app;
        if (delayedRedirect) {
          res.redirect(delayedRedirect);
          epic.dispose();
          return false;
        }
        return true;
      })
      .flatMap(function({ markup, store, epic }) {
        log('react markup rendered, data fetched');
        const state = store.getState();
        const { title } = state.app;
        epic.dispose();
        res.expose(state, 'data');
        res.expose(req.flash(), 'flash');
        return res.render$(
          'layout-react',
          { markup, title }
        );
      })
      .doOnNext(markup => res.send(markup))
      .subscribe(
        () => log('html rendered and ready to send'),
        next
      );
  }
}
<|endoftext|>


<commit_before>#!/usr/bin/env node

const fs = require('fs');
const cp = require('child_process');
const path = require('path');
const uuid = require('uuid/v4');

const delimiter = '\n';

const users = {[path.resolve(__dirname, '..', 'jail')]: true};
const initialize = path.resolve(__dirname, 'initialize.sh');
const start = path.resolve(__dirname, 'start.sh');

// There are four states in the lifecycle of a Connection
// 1. Constructed (connected === false && open === false && pid === null)
// 2. Connected   (connected === true && open === false && pid === null)
// 3. Initialized (connected === true && open === true && pid === null)
// 4. Open        (connected === true && open === true && pid === ###)

class Connection {
    constructor(user) {
        this.user = user;
        this.uuid = uuid();
        this.args = [this.user, this.uuid];
        this.files = path.resolve(this.user, 'files');
        this.buffer = '';
        this.connected = false;
        this.open = false;
        this.pid = null;
    }
    message({source, content}) {
        if (source === 'eval') {
            this.scheme.stdin.write(content);
        }
        else if (source === 'open') {
            fs.readdir(this.files, (error, files) => this.send('open', files));
        }
        else if (source === 'save') {
            const {name, text} = content;
            const file = this.file(name);
            fs.writeFile(file, text, 'utf8', error => this.send('save', !error));
        }
        else if (source === 'load') {
            const {name} = content;
            const file = this.file(name);
            fs.readFile(file, 'utf8', (error, text) => this.send('load', text));
        }
        else if (source === 'kill') {
            if (this.pid) process.kill(this.pid, content);
        }
        else {
            console.error('invalid source', source);
        }
    }
    close() {
        // close() takes states 4, 3, and 2 to state 1

        if (this.pid) {
            process.kill(this.pid, 'SIGKILL');
        }
        this.pid = null;

        // clean up state 3
        if (this.open) {
            process.kill(this.scheme.pid, 'SIGKILL');
            this.scheme = null;
        }
        this.open = false;

        // clean up state 2
        if (this.connected && this.socket.readyState === 1) {
            this.socket.close();
            this.socket = null;
        }
        this.connected = false;
    }
    connect(socket) {
        this.connected = true;
        this.socket = socket;

        socket.on('message', data => this.open && this.message(JSON.parse(data)));
        socket.on('error', error => console.error('socket', error));
        socket.on('close', event => {
            this.connected = false;
            this.close();
        });

        cp.execFile(initialize, this.args, {}, error => error ? console.error('initialize', error) : this.initialize());
    }
    initialize() {

        const pipe_path = path.resolve(this.user, 'pipes', this.uuid);
        const pipe = fs.createReadStream(pipe_path);
        pipe.on('data', data => this.pipe(data));
        pipe.on('error', error => console.error('pipe', error));

        this.scheme = cp.spawn(start, this.args, {});
        this.open = true;

        this.scheme.on('error', error => console.error('scheme', error));
        this.scheme.on('exit', (code, signal) => {
            this.open = false;
            this.close();
        });

        this.scheme.stdout.on('error', error => console.error('scheme stdout', error));
        this.scheme.stdout.on('data', data => {
            if (this.pid) this.send('stdout', data.toString());
            else this.pid = +data.toString();
        });
    }
    send(source, content) {
        if (this.connected && this.socket.readyState === 1) {
            this.socket.send(JSON.stringify({source, content}));
        }
    }
    data(value) {
        try {
            this.send('data', JSON.parse(value));
        } catch (error) {
            console.error(error);
        }
    }
    pipe(data) {
        const values = (this.buffer + data).split(delimiter);
        this.buffer = values.pop();
        values.forEach(value => this.data(value));
    }
    file(name) {
        return path.resolve(this.user, 'files', name.split('/').join('-'));
    }
}

module.exports = Connection;<commit_msg>fixed state bug<commit_after>#!/usr/bin/env node

const fs = require('fs');
const cp = require('child_process');
const path = require('path');
const uuid = require('uuid/v4');

const delimiter = '\n';

const users = {[path.resolve(__dirname, '..', 'jail')]: true};
const initialize = path.resolve(__dirname, 'initialize.sh');
const start = path.resolve(__dirname, 'start.sh');

// There are four states in the lifecycle of a Connection
// 1. Constructed (connected === false && open === false && pid === null)
// 2. Connected   (connected === true && open === false && pid === null)
// 3. Initialized (connected === true && open === true && pid === null)
// 4. Open        (connected === true && open === true && pid === ###)
// Although states 3 and 4 are essentially identical and are only every briefly out of sync.

class Connection {
    constructor(user) {
        this.user = user;
        this.uuid = uuid();
        this.args = [this.user, this.uuid];
        this.files = path.resolve(this.user, 'files');
        this.buffer = '';
        this.connected = false;
        this.open = false;
        this.pid = null;
    }
    message({source, content}) {
        if (source === 'eval') {
            this.scheme.stdin.write(content);
        }
        else if (source === 'open') {
            fs.readdir(this.files, (error, files) => this.send('open', files));
        }
        else if (source === 'save') {
            const {name, text} = content;
            const file = this.file(name);
            fs.writeFile(file, text, 'utf8', error => this.send('save', !error));
        }
        else if (source === 'load') {
            const {name} = content;
            const file = this.file(name);
            fs.readFile(file, 'utf8', (error, text) => this.send('load', text));
        }
        else if (source === 'kill') {
            if (this.pid) process.kill(this.pid, content);
        }
        else {
            console.error('invalid source', source);
        }
    }
    close() {
        // close() takes states 4, 3, and 2 to state 1

        if (this.pid) {
            process.kill(this.pid, 'SIGKILL');
        }
        this.pid = null;

        // clean up state 3
        if (this.open) {
            process.kill(this.scheme.pid, 'SIGKILL');
            this.scheme = null;
        }
        this.open = false;

        // clean up state 2
        if (this.connected && this.socket.readyState === 1) {
            this.socket.close();
            this.socket = null;
        }
        this.connected = false;
    }
    connect(socket) {
        this.connected = true;
        this.socket = socket;

        socket.on('message', data => this.open && this.message(JSON.parse(data)));
        socket.on('error', error => console.error('socket', error));
        socket.on('close', event => {
            this.connected = false;
            this.close();
        });

        cp.execFile(initialize, this.args, {}, error => error ? console.error('initialize', error) : this.initialize());
    }
    initialize() {

        const pipe_path = path.resolve(this.user, 'pipes', this.uuid);
        const pipe = fs.createReadStream(pipe_path);
        pipe.on('data', data => this.pipe(data));
        pipe.on('error', error => console.error('pipe', error));

        this.scheme = cp.spawn(start, this.args, {});
        this.open = true;

        this.scheme.on('error', error => console.error('scheme', error));
        this.scheme.on('exit', (code, signal) => {
            this.pid = false;
            this.open = false;
            this.close();
        });

        this.scheme.stdout.on('error', error => console.error('scheme stdout', error));
        this.scheme.stdout.on('data', data => {
            if (this.pid) this.send('stdout', data.toString());
            else this.pid = +data.toString();
        });
    }
    send(source, content) {
        if (this.connected && this.socket.readyState === 1) {
            this.socket.send(JSON.stringify({source, content}));
        }
    }
    data(value) {
        try {
            this.send('data', JSON.parse(value));
        } catch (error) {
            console.error(error);
        }
    }
    pipe(data) {
        const values = (this.buffer + data).split(delimiter);
        this.buffer = values.pop();
        values.forEach(value => this.data(value));
    }
    file(name) {
        return path.resolve(this.user, 'files', name.split('/').join('-'));
    }
}

module.exports = Connection;<|endoftext|>


<commit_before>/**
 *    Copyright 2015-2017 ppy Pty. Ltd.
 *
 *    This file is part of osu!web. osu!web is distributed with the hope of
 *    attracting more community contributions to the core ecosystem of osu!.
 *
 *    osu!web is free software: you can redistribute it and/or modify
 *    it under the terms of the Affero GNU General Public License version 3
 *    as published by the Free Software Foundation.
 *
 *    osu!web is distributed WITHOUT ANY WARRANTY; without even the implied
 *    warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *    See the GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 */

const { mix } = require('laravel-mix');
const path = require('path');

// .js doesn't support globbing by itself, so we need to glob
// and spread the values in.
const glob = require('glob');
let min = '';
if (mix.inProduction()) {
  min = '.min';
}

// relative from root?
const node_root = 'node_modules';

mix
.webpackConfig({
  module: {
    rules: [
      {
        enforce: 'pre',
        test: /\.(js|coffee)$/,
        loader: 'import-glob-loader',
        exclude: /(node_modules)/,
      },
      {
        test: /\.coffee$/,
        use: ['imports-loader?this=>window', 'coffee-loader']
      }
    ]
  }
})
.js([
  'resources/assets/app.js'
], 'js/app.js')
.js([
  ...glob.sync('resources/assets/coffee/react/profile-page/*.coffee'),
  'resources/assets/coffee/react/profile-page.coffee',
], 'js/react/profile-page.js')
.js([
  ...glob.sync('resources/assets/coffee/react/beatmaps/*.coffee'),
  'resources/assets/coffee/react/beatmaps.coffee'
], 'js/react/beatmaps.js')
.js([
  ...glob.sync('resources/assets/coffee/react/status-page/*.coffee'),
  'resources/assets/coffee/react/status-page.coffee'
], 'js/react/status-page.js')
.js([
  ...glob.sync('resources/assets/coffee/react/beatmap-discussions/*.coffee'),
  'resources/assets/coffee/react/beatmap-discussions.coffee'
], 'js/react/beatmap-discussions.js')
.js([
  ...glob.sync('resources/assets/coffee/react/beatmapset-page/*.coffee'),
  'resources/assets/coffee/react/beatmapset-page.coffee'
], 'js/react/beatmapset-page.js')
.js([
  ...glob.sync('resources/assets/coffee/react/mp-history/*.coffee'),
  'resources/assets/coffee/react/mp-history.coffee'
], 'js/react/mp-history.js')
.js([
  'resources/assets/coffee/react/artist-page.coffee',
], 'js/react/artist-page.js')
.js([
  // 'resources/assets/coffee/react/contest/voting/_base-entry-list.coffee',
  ...glob.sync('resources/assets/coffee/react/contest/voting/*.coffee'),
  'resources/assets/coffee/react/contest-voting.coffee',
], 'js/react/contest-voting.js')
.js([
  ...glob.sync('resources/assets/coffee/react/contest/entry/*.coffee'),
  'resources/assets/coffee/react/contest-entry.coffee',
], 'js/react/contest-entry.js')
.copy('node_modules/font-awesome/fonts', 'public/vendor/fonts/font-awesome')
.copy('node_modules/photoswipe/dist/default-skin', 'public/vendor/_photoswipe-default-skin')
.copy('node_modules/timeago/locales', 'public/vendor/js/timeago-locales')
.less('resources/assets/less/app.less', 'public/build/css')
.scripts([
  'resources/assets/js/ga.js',
  'resources/assets/js/messages.js',
  'resources/assets/js/laroute.js'
], 'public/js/app-deps.js') // FIXME: less dumb name; this needs to be separated -
                            // compiling coffee and then concating together doesn't
                            // work so well when versioning is used with webpack.
.scripts([
  path.join(node_root, 'turbolinks/dist/turbolinks.js'),
  path.join(node_root, 'jquery/dist/jquery' + min + '.js'),
  path.join(node_root, 'jquery-ujs/src/rails.js'),
  path.join(node_root, 'qtip2/dist/jquery.qtip' + min + '.js'),
  path.join(node_root, 'jquery.scrollto/jquery.scrollTo.js'),
  path.join(node_root, 'jquery-ui/ui/data.js'),
  path.join(node_root, 'jquery-ui/ui/scroll-parent.js'),
  path.join(node_root, 'jquery-ui/ui/widget.js'),
  path.join(node_root, 'jquery-ui/ui/widgets/mouse.js'),
  path.join(node_root, 'jquery-ui/ui/widgets/slider.js'),
  path.join(node_root, 'jquery-ui/ui/widgets/sortable.js'),
  path.join(node_root, 'jquery-ui/ui/keycode.js'),
  path.join(node_root, 'timeago/jquery.timeago.js'),
  path.join(node_root, 'blueimp-file-upload/js/jquery.fileupload.js'),
  path.join(node_root, 'bootstrap/dist/js/bootstrap.js'),
  path.join(node_root, 'lodash/lodash.js'),
  path.join(node_root, 'layzr.js/dist/layzr.js'),
  path.join(node_root, 'react/dist/react-with-addons' + min + '.js'),
  path.join(node_root, 'react-dom/dist/react-dom' + min + '.js'),
  path.join(node_root, 'photoswipe/dist/photoswipe.js'),
  path.join(node_root, 'photoswipe/dist/photoswipe-ui-default.js'),
  path.join(node_root, 'd3/build/d3' + min + '.js'),
  path.join(node_root, 'moment/moment.js'),
  path.join(node_root, 'js-cookie/src/js.cookie.js'),

  path.join(node_root, 'react-height/build/react-height' + min + '.js'),
  path.join(node_root, 'react-motion/build/react-motion.js'),
  path.join(node_root, 'react-collapse/build/react-collapse' + min + '.js'),
  path.join(node_root, 'react-disqus-thread/dist/react-disqus-thread' + min + '.js'),
], 'public/js/vendor.js')
.version()
.sourceMaps(!mix.inProduction());
<commit_msg>only version in production<commit_after>/**
 *    Copyright 2015-2017 ppy Pty. Ltd.
 *
 *    This file is part of osu!web. osu!web is distributed with the hope of
 *    attracting more community contributions to the core ecosystem of osu!.
 *
 *    osu!web is free software: you can redistribute it and/or modify
 *    it under the terms of the Affero GNU General Public License version 3
 *    as published by the Free Software Foundation.
 *
 *    osu!web is distributed WITHOUT ANY WARRANTY; without even the implied
 *    warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *    See the GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 */

const { mix } = require('laravel-mix');
const path = require('path');

// .js doesn't support globbing by itself, so we need to glob
// and spread the values in.
const glob = require('glob');
let min = '';
if (mix.inProduction()) {
  min = '.min';
}

// relative from root?
const node_root = 'node_modules';

mix
.webpackConfig({
  module: {
    rules: [
      {
        enforce: 'pre',
        test: /\.(js|coffee)$/,
        loader: 'import-glob-loader',
        exclude: /(node_modules)/,
      },
      {
        test: /\.coffee$/,
        use: ['imports-loader?this=>window', 'coffee-loader']
      }
    ]
  }
})
.js([
  'resources/assets/app.js'
], 'js/app.js')
.js([
  ...glob.sync('resources/assets/coffee/react/profile-page/*.coffee'),
  'resources/assets/coffee/react/profile-page.coffee',
], 'js/react/profile-page.js')
.js([
  ...glob.sync('resources/assets/coffee/react/beatmaps/*.coffee'),
  'resources/assets/coffee/react/beatmaps.coffee'
], 'js/react/beatmaps.js')
.js([
  ...glob.sync('resources/assets/coffee/react/status-page/*.coffee'),
  'resources/assets/coffee/react/status-page.coffee'
], 'js/react/status-page.js')
.js([
  ...glob.sync('resources/assets/coffee/react/beatmap-discussions/*.coffee'),
  'resources/assets/coffee/react/beatmap-discussions.coffee'
], 'js/react/beatmap-discussions.js')
.js([
  ...glob.sync('resources/assets/coffee/react/beatmapset-page/*.coffee'),
  'resources/assets/coffee/react/beatmapset-page.coffee'
], 'js/react/beatmapset-page.js')
.js([
  ...glob.sync('resources/assets/coffee/react/mp-history/*.coffee'),
  'resources/assets/coffee/react/mp-history.coffee'
], 'js/react/mp-history.js')
.js([
  'resources/assets/coffee/react/artist-page.coffee',
], 'js/react/artist-page.js')
.js([
  // 'resources/assets/coffee/react/contest/voting/_base-entry-list.coffee',
  ...glob.sync('resources/assets/coffee/react/contest/voting/*.coffee'),
  'resources/assets/coffee/react/contest-voting.coffee',
], 'js/react/contest-voting.js')
.js([
  ...glob.sync('resources/assets/coffee/react/contest/entry/*.coffee'),
  'resources/assets/coffee/react/contest-entry.coffee',
], 'js/react/contest-entry.js')
.copy('node_modules/font-awesome/fonts', 'public/vendor/fonts/font-awesome')
.copy('node_modules/photoswipe/dist/default-skin', 'public/vendor/_photoswipe-default-skin')
.copy('node_modules/timeago/locales', 'public/vendor/js/timeago-locales')
.less('resources/assets/less/app.less', 'public/build/css')
.scripts([
  'resources/assets/js/ga.js',
  'resources/assets/js/messages.js',
  'resources/assets/js/laroute.js'
], 'public/js/app-deps.js') // FIXME: less dumb name; this needs to be separated -
                            // compiling coffee and then concating together doesn't
                            // work so well when versioning is used with webpack.
.scripts([
  path.join(node_root, 'turbolinks/dist/turbolinks.js'),
  path.join(node_root, 'jquery/dist/jquery' + min + '.js'),
  path.join(node_root, 'jquery-ujs/src/rails.js'),
  path.join(node_root, 'qtip2/dist/jquery.qtip' + min + '.js'),
  path.join(node_root, 'jquery.scrollto/jquery.scrollTo.js'),
  path.join(node_root, 'jquery-ui/ui/data.js'),
  path.join(node_root, 'jquery-ui/ui/scroll-parent.js'),
  path.join(node_root, 'jquery-ui/ui/widget.js'),
  path.join(node_root, 'jquery-ui/ui/widgets/mouse.js'),
  path.join(node_root, 'jquery-ui/ui/widgets/slider.js'),
  path.join(node_root, 'jquery-ui/ui/widgets/sortable.js'),
  path.join(node_root, 'jquery-ui/ui/keycode.js'),
  path.join(node_root, 'timeago/jquery.timeago.js'),
  path.join(node_root, 'blueimp-file-upload/js/jquery.fileupload.js'),
  path.join(node_root, 'bootstrap/dist/js/bootstrap.js'),
  path.join(node_root, 'lodash/lodash.js'),
  path.join(node_root, 'layzr.js/dist/layzr.js'),
  path.join(node_root, 'react/dist/react-with-addons' + min + '.js'),
  path.join(node_root, 'react-dom/dist/react-dom' + min + '.js'),
  path.join(node_root, 'photoswipe/dist/photoswipe.js'),
  path.join(node_root, 'photoswipe/dist/photoswipe-ui-default.js'),
  path.join(node_root, 'd3/build/d3' + min + '.js'),
  path.join(node_root, 'moment/moment.js'),
  path.join(node_root, 'js-cookie/src/js.cookie.js'),

  path.join(node_root, 'react-height/build/react-height' + min + '.js'),
  path.join(node_root, 'react-motion/build/react-motion.js'),
  path.join(node_root, 'react-collapse/build/react-collapse' + min + '.js'),
  path.join(node_root, 'react-disqus-thread/dist/react-disqus-thread' + min + '.js'),
], 'public/js/vendor.js')
.sourceMaps(!mix.inProduction());

if (mix.inProduction()) {
  mix.version();
}
<|endoftext|>


<commit_before>// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
// See the LICENCE file in the repository root for full licence text.

'use strict';

const fs = require('fs');
const path = require('path');
const { minify } = require('terser');
const webpack = require('webpack');

const Autoprefixer = require('autoprefixer');
const CopyPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const SentryPlugin = require('webpack-sentry-plugin');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

const inProduction = process.env.NODE_ENV === 'production' || process.argv.includes('-p');
const paymentSandbox = !(process.env.PAYMENT_SANDBOX === '0'
                         || process.env.PAYMENT_SANDBOX === 'false'
                         || !process.env.PAYMENT_SANDBOX);

// TODO: move to own file
class ConcatPlugin {
  constructor(options = {}) {
    this.patterns = options.patterns;
    this.options = options.options || {};
  }

  apply(compiler) {
    const { RawSource } = require('webpack-sources');
    const concatenate = require('concatenate');

    const plugin = { name: 'ConcatPlugin' };

    compiler.hooks.thisCompilation.tap(plugin, (compilation) => {
      const logger = compilation.getLogger('concat-webpack-plugin');
      compilation.hooks.additionalAssets.tapAsync('concat-webpack-plugin', async (callback) => {
        const assets = this.patterns.map((pattern) => {
          const webpackTo = path.resolve(compiler.options.output.path, pattern.to);

          let source = concatenate.sync(pattern.from);
          if (inProduction) {
            // TODO: source map
            source = minify(source, { sourceMap: true }).code;
          }

          return {
            source: new RawSource(source),
            targetPath: pattern.to,
            webpackTo: webpackTo,
          };
        });

        assets.forEach((asset) => {
          const {
            targetPath,
            source,
            webpackTo,
          } = asset;

          logger.log(`writing '${webpackTo}'`);
          compilation.emitAsset(targetPath, source);
        });

        callback();
      });
    });
  }
}

// declare entrypoints and output first.
const entry = {
  '/js/app': [
    './resources/assets/app.js',
    './resources/assets/less/app.less',
  ],
};

const coffeeReactComponents = [
  'artist-page',
  'beatmap-discussions',
  'beatmap-discussions-history',
  'beatmapset-page',
  'changelog-build',
  'changelog-index',
  'comments-index',
  'comments-show',
  'mp-history',
  'modding-profile',
  'profile-page',
  'admin/contest',
  'contest-entry',
  'contest-voting',
];

const tsReactComponents = [
  'account-edit',
  'beatmaps',
  'chat',
  'friends-index',
  'groups-show',
  'news-index',
  'news-show',
  'notifications-index',
  'scores-show',
  'store-bootstrap',
];

for (const name of coffeeReactComponents) {
  entry[`/js/react/${name}`] = [path.resolve(__dirname, `resources/assets/coffee/react/${name}.coffee`)];
}

for (const name of tsReactComponents) {
  entry[`/js/react/${name}`] = [path.resolve(__dirname, `resources/assets/lib/${name}.ts`)];
}

const output = {
  chunkFilename: '[name].[chunkhash:8].js',
  filename: '[name].[contenthash:8].js',
  path: path.resolve(__dirname, 'public'),
};

const plugins = (function() {
  const concatPlugin = (function() {
    let min = '';
    let reactMin = 'development';
    if (inProduction) {
      min = '.min';
      reactMin = 'production.min';
    }

    // vendor and locale files.
    const vendor = [
      'clipboard-polyfill/build/clipboard-polyfill.js',
      `url-polyfill/url-polyfill${min}.js`,
      'turbolinks/dist/turbolinks.js',
      `jquery/dist/jquery${min}.js`,
      'jquery-ujs/src/rails.js',
      `qtip2/dist/jquery.qtip${min}.js`,
      'jquery.scrollto/jquery.scrollTo.js',
      'jquery-ui/ui/data.js',
      'jquery-ui/ui/scroll-parent.js',
      'jquery-ui/ui/widget.js',
      'jquery-ui/ui/widgets/mouse.js',
      'jquery-ui/ui/widgets/slider.js',
      'jquery-ui/ui/widgets/sortable.js',
      'jquery-ui/ui/keycode.js',
      'timeago/jquery.timeago.js',
      'blueimp-file-upload/js/jquery.fileupload.js',
      'bootstrap/dist/js/bootstrap.js',
      'lodash/lodash.js',
      'layzr.js/dist/layzr.js',
      `react/umd/react.${reactMin}.js`,
      'react-dom-factories/index.js',
      `react-dom/umd/react-dom.${reactMin}.js`,
      `prop-types/prop-types${min}.js`,
      'photoswipe/dist/photoswipe.js',
      'photoswipe/dist/photoswipe-ui-default.js',
      `d3/dist/d3${min}.js`,
      'moment/moment.js',
      'js-cookie/src/js.cookie.js',
      `imagesloaded/imagesloaded.pkgd${min}.js`,
    ].map((name) => path.join(path.resolve(__dirname, 'node_modules'), name));

    vendor.forEach(function(script) {
      if (!fs.existsSync(script)) {
        throw new Error(`${script} doesn't exist`);
      }
    });

    if (!fs.readdirSync('resources/assets/build/locales').some((file) => file.endsWith('.js'))) {
      throw new Error('missing locale files.');
    }

    return new ConcatPlugin({
      patterns: [
        {
          from: [
            'resources/assets/js/ga.js',
            'resources/assets/build/lang.js',
            'resources/assets/js/bootstrap-lang.js',
          ],
          to: '/js/app-deps.js',
        },
        {
          from: vendor, to: '/js/vendor.js',
        },
      ],
    });
  }());

  const copyPlugin = new CopyPlugin({
    patterns: [
      { from: 'resources/assets/build/locales', to: 'js/locales' },
      { from: 'node_modules/@fortawesome/fontawesome-free/webfonts', to: 'vendor/fonts/font-awesome' },
      { from: 'node_modules/photoswipe/dist/default-skin', to: 'vendor/_photoswipe-default-skin' },
      { from: 'node_modules/moment/locale', to: 'vendor/js/moment-locales' },
    ],
  });

  return [
    new webpack.DefinePlugin({
      'process.env.DOCS_URL': JSON.stringify(process.env.DOCS_URL || 'https://docs.ppy.sh'),
      'process.env.PAYMENT_SANDBOX': JSON.stringify(paymentSandbox),
      'process.env.SHOPIFY_DOMAIN': JSON.stringify(process.env.SHOPIFY_DOMAIN),
      'process.env.SHOPIFY_STOREFRONT_TOKEN': JSON.stringify(process.env.SHOPIFY_STOREFRONT_TOKEN),
    }),
    new MiniCssExtractPlugin({
      chunkFilename: '/css/app.[hash:8].css',
      filename: '/css/app.[hash:8].css',
    }),
    concatPlugin,
    copyPlugin,
    new ManifestPlugin({
      fileName: 'mix-manifest.json',
    }),
  ];
}());

const webpackConfig = {
  devtool: 'source-map',
  entry,
  externals: {
    'd3': 'd3',
    'lodash': '_',
    'moment': 'moment',
    'prop-types': 'PropTypes',
    'react': 'React',
    'react-dom': 'ReactDOM',
    'react-dom-factories': 'ReactDOMFactories',
  },
  mode: inProduction ? 'production' : 'development',
  module: {
    rules: [
      {
        enforce: 'pre',
        exclude: /(node_modules)/,
        loader: 'import-glob-loader',
        test: /\.(js|coffee)$/,
      },
      {
        exclude: /(node_modules|bower_components)/,
        test: /\.jsx?$/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
              plugins: [
                '@babel/plugin-syntax-dynamic-import',
                '@babel/plugin-proposal-object-rest-spread',
                [
                  '@babel/plugin-transform-runtime',
                  {
                    helpers: false,
                  },
                ],
              ],
              presets: [
                [
                  '@babel/preset-env',
                  {
                    forceAllTransforms: true,
                    modules: false,
                  },
                ],
              ],
            },
          },
        ],
      },
      {
        exclude: /node_modules/,
        loader: 'ts-loader',
        test: /\.tsx?$/,
      },
      {
        // loader for preexisting global coffeescript
        exclude: [
          path.resolve(__dirname, 'resources/assets/coffee/react'),
        ],
        include: [
          path.resolve(__dirname, 'resources/assets/coffee'),
        ],
        test: /\.coffee$/,
        use: ['imports-loader?this=>window', 'coffee-loader'],
      },
      {
        // loader for import-based coffeescript
        include: [
          path.resolve(__dirname, 'resources/assets/coffee/react'),
          path.resolve(__dirname, 'resources/assets/lib'),
        ],
        test: /\.coffee$/,
        use: ['coffee-loader'],
      },
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              sourceMap: true,
              url: (url) => !url.startsWith('/'),
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: [Autoprefixer],
              sourceMap: true,
            },
          },
          { loader: 'less-loader', options: { sourceMap: true } },
        ],
      },
      {
        loaders: [
          {
            loader: 'file-loader',
            options: {
              name: (filepath) => {
                if (!/node_modules|bower_components/.test(filepath)) {
                  return '/images/[name].[ext]?[hash]';
                }

                const cleanPath = filepath
                  .replace(/\\/g, '/')
                  .replace(/((.*(node_modules|bower_components))|images|image|img|assets)\//g, '');

                return `/images/vendor/${cleanPath}?[hash]`;
              },
            },
          },
          {
            loader: 'img-loader',
          },
        ],
        test: /(\.(png|jpe?g|gif|webp)$|^((?!font).)*\.svg$)/,
      },
      {
        loader: 'file-loader',
        options: {
          name: (filepath) => {
            if (!/node_modules|bower_components/.test(filepath)) {
              return '/fonts/[name].[ext]?[hash]';
            }

            const cleanPath = filepath
              .replace(/\\/g, '/')
              .replace(/((.*(node_modules|bower_components))|fonts|font|assets)\//g, '');

            return `/fonts/vendor/${cleanPath}?[hash]`;
          },
        },
        test: /(\.(woff2?|ttf|eot|otf)$|font.*\.svg$)/,
      },
    ],
  },
  optimization: {
    runtimeChunk: {
      name: '/js/commons',
    },
    splitChunks: {
      cacheGroups: {
        commons: {
          chunks: 'initial',
          minChunks: 2,
          name: '/js/commons',
        },
      },
    },
  },
  output,
  plugins,
  resolve: {
    alias: {
      'ziggy': path.resolve(__dirname, 'resources/assets/js/ziggy.js'),
      'ziggy-route': path.resolve(__dirname, 'vendor/tightenco/ziggy/dist/js/route.js'),
    },
    extensions: ['*', '.js', '.coffee', '.ts', '.tsx'],
    modules: [
      path.resolve(__dirname, 'resources/assets/coffee'),
      path.resolve(__dirname, 'resources/assets/lib'),
      path.resolve(__dirname, 'resources/assets/coffee/react/_components'),
      path.resolve(__dirname, 'node_modules'),
    ],
    plugins: [new TsconfigPathsPlugin()],
  },
};

if (inProduction) {
  webpackConfig.optimization.minimizer = [
    new TerserPlugin({
      sourceMap: true,
      terserOptions: {
        safari10: true,
      },
    }),
    new CssMinimizerPlugin({
      sourceMap: true,
    }),
  ];
}

if (process.env.SENTRY_RELEASE === '1') {
  webpackConfig.plugins.push(
    new SentryPlugin({
      apiKey: process.env.SENTRY_API_KEY,
      organisation: process.env.SENTRY_ORG,
      project: process.env.SENTRY_PROJ,

      deleteAfterCompile: true,
      exclude: /\.css(\.map)?$/,
      filenameTransform: function(filename) {
        return '~' + filename;
      },
      release: function() {
        return process.env.GIT_SHA;
      },
    }),
  );
}

module.exports = webpackConfig;
<commit_msg>hash copied files<commit_after>// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
// See the LICENCE file in the repository root for full licence text.

'use strict';

const fs = require('fs');
const path = require('path');
const { minify } = require('terser');
const webpack = require('webpack');

const Autoprefixer = require('autoprefixer');
const CopyPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const SentryPlugin = require('webpack-sentry-plugin');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

const inProduction = process.env.NODE_ENV === 'production' || process.argv.includes('-p');
const paymentSandbox = !(process.env.PAYMENT_SANDBOX === '0'
                         || process.env.PAYMENT_SANDBOX === 'false'
                         || !process.env.PAYMENT_SANDBOX);

// TODO: move to own file
class ConcatPlugin {
  constructor(options = {}) {
    this.patterns = options.patterns;
    this.options = options.options || {};
  }

  apply(compiler) {
    const { RawSource } = require('webpack-sources');
    const concatenate = require('concatenate');

    const plugin = { name: 'ConcatPlugin' };

    compiler.hooks.thisCompilation.tap(plugin, (compilation) => {
      const logger = compilation.getLogger('concat-webpack-plugin');
      compilation.hooks.additionalAssets.tapAsync('concat-webpack-plugin', async (callback) => {
        const assets = this.patterns.map((pattern) => {
          const webpackTo = path.resolve(compiler.options.output.path, pattern.to);

          let source = concatenate.sync(pattern.from);
          if (inProduction) {
            // TODO: source map
            source = minify(source, { sourceMap: true }).code;
          }

          return {
            source: new RawSource(source),
            targetPath: pattern.to,
            webpackTo: webpackTo,
          };
        });

        assets.forEach((asset) => {
          const {
            targetPath,
            source,
            webpackTo,
          } = asset;

          logger.log(`writing '${webpackTo}'`);
          compilation.emitAsset(targetPath, source);
        });

        callback();
      });
    });
  }
}

// declare entrypoints and output first.
const entry = {
  '/js/app': [
    './resources/assets/app.js',
    './resources/assets/less/app.less',
  ],
};

const coffeeReactComponents = [
  'artist-page',
  'beatmap-discussions',
  'beatmap-discussions-history',
  'beatmapset-page',
  'changelog-build',
  'changelog-index',
  'comments-index',
  'comments-show',
  'mp-history',
  'modding-profile',
  'profile-page',
  'admin/contest',
  'contest-entry',
  'contest-voting',
];

const tsReactComponents = [
  'account-edit',
  'beatmaps',
  'chat',
  'friends-index',
  'groups-show',
  'news-index',
  'news-show',
  'notifications-index',
  'scores-show',
  'store-bootstrap',
];

for (const name of coffeeReactComponents) {
  entry[`/js/react/${name}`] = [path.resolve(__dirname, `resources/assets/coffee/react/${name}.coffee`)];
}

for (const name of tsReactComponents) {
  entry[`/js/react/${name}`] = [path.resolve(__dirname, `resources/assets/lib/${name}.ts`)];
}

const output = {
  chunkFilename: '[name].[chunkhash:8].js',
  filename: '[name].[contenthash:8].js',
  path: path.resolve(__dirname, 'public'),
};

const plugins = (function() {
  const concatPlugin = (function() {
    let min = '';
    let reactMin = 'development';
    if (inProduction) {
      min = '.min';
      reactMin = 'production.min';
    }

    // vendor and locale files.
    const vendor = [
      'clipboard-polyfill/build/clipboard-polyfill.js',
      `url-polyfill/url-polyfill${min}.js`,
      'turbolinks/dist/turbolinks.js',
      `jquery/dist/jquery${min}.js`,
      'jquery-ujs/src/rails.js',
      `qtip2/dist/jquery.qtip${min}.js`,
      'jquery.scrollto/jquery.scrollTo.js',
      'jquery-ui/ui/data.js',
      'jquery-ui/ui/scroll-parent.js',
      'jquery-ui/ui/widget.js',
      'jquery-ui/ui/widgets/mouse.js',
      'jquery-ui/ui/widgets/slider.js',
      'jquery-ui/ui/widgets/sortable.js',
      'jquery-ui/ui/keycode.js',
      'timeago/jquery.timeago.js',
      'blueimp-file-upload/js/jquery.fileupload.js',
      'bootstrap/dist/js/bootstrap.js',
      'lodash/lodash.js',
      'layzr.js/dist/layzr.js',
      `react/umd/react.${reactMin}.js`,
      'react-dom-factories/index.js',
      `react-dom/umd/react-dom.${reactMin}.js`,
      `prop-types/prop-types${min}.js`,
      'photoswipe/dist/photoswipe.js',
      'photoswipe/dist/photoswipe-ui-default.js',
      `d3/dist/d3${min}.js`,
      'moment/moment.js',
      'js-cookie/src/js.cookie.js',
      `imagesloaded/imagesloaded.pkgd${min}.js`,
    ].map((name) => path.join(path.resolve(__dirname, 'node_modules'), name));

    vendor.forEach(function(script) {
      if (!fs.existsSync(script)) {
        throw new Error(`${script} doesn't exist`);
      }
    });

    if (!fs.readdirSync('resources/assets/build/locales').some((file) => file.endsWith('.js'))) {
      throw new Error('missing locale files.');
    }

    return new ConcatPlugin({
      patterns: [
        {
          from: [
            'resources/assets/js/ga.js',
            'resources/assets/build/lang.js',
            'resources/assets/js/bootstrap-lang.js',
          ],
          to: '/js/app-deps.js',
        },
        {
          from: vendor, to: '/js/vendor.js',
        },
      ],
    });
  }());

  const copyPlugin = new CopyPlugin({
    patterns: [
      { from: 'resources/assets/build/locales/*', to: 'js/locales/[name].[contenthash:8].[ext]' },
      { from: 'node_modules/@fortawesome/fontawesome-free/webfonts/*', to: 'vendor/fonts/font-awesome/[name].[contenthash:8].[ext]' },
      { from: 'node_modules/photoswipe/dist/default-skin/*', to: 'vendor/_photoswipe-default-skin/[name].[contenthash:8].[ext]' },
      { from: 'node_modules/moment/locale/*', to: 'vendor/js/moment-locales/[name].[contenthash:8].[ext]' },
    ],
  });

  return [
    new webpack.DefinePlugin({
      'process.env.DOCS_URL': JSON.stringify(process.env.DOCS_URL || 'https://docs.ppy.sh'),
      'process.env.PAYMENT_SANDBOX': JSON.stringify(paymentSandbox),
      'process.env.SHOPIFY_DOMAIN': JSON.stringify(process.env.SHOPIFY_DOMAIN),
      'process.env.SHOPIFY_STOREFRONT_TOKEN': JSON.stringify(process.env.SHOPIFY_STOREFRONT_TOKEN),
    }),
    new MiniCssExtractPlugin({
      chunkFilename: '/css/app.[hash:8].css',
      filename: '/css/app.[hash:8].css',
    }),
    concatPlugin,
    copyPlugin,
    new ManifestPlugin({
      fileName: 'mix-manifest.json',
    }),
  ];
}());

const webpackConfig = {
  devtool: 'source-map',
  entry,
  externals: {
    'd3': 'd3',
    'lodash': '_',
    'moment': 'moment',
    'prop-types': 'PropTypes',
    'react': 'React',
    'react-dom': 'ReactDOM',
    'react-dom-factories': 'ReactDOMFactories',
  },
  mode: inProduction ? 'production' : 'development',
  module: {
    rules: [
      {
        enforce: 'pre',
        exclude: /(node_modules)/,
        loader: 'import-glob-loader',
        test: /\.(js|coffee)$/,
      },
      {
        exclude: /(node_modules|bower_components)/,
        test: /\.jsx?$/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: true,
              plugins: [
                '@babel/plugin-syntax-dynamic-import',
                '@babel/plugin-proposal-object-rest-spread',
                [
                  '@babel/plugin-transform-runtime',
                  {
                    helpers: false,
                  },
                ],
              ],
              presets: [
                [
                  '@babel/preset-env',
                  {
                    forceAllTransforms: true,
                    modules: false,
                  },
                ],
              ],
            },
          },
        ],
      },
      {
        exclude: /node_modules/,
        loader: 'ts-loader',
        test: /\.tsx?$/,
      },
      {
        // loader for preexisting global coffeescript
        exclude: [
          path.resolve(__dirname, 'resources/assets/coffee/react'),
        ],
        include: [
          path.resolve(__dirname, 'resources/assets/coffee'),
        ],
        test: /\.coffee$/,
        use: ['imports-loader?this=>window', 'coffee-loader'],
      },
      {
        // loader for import-based coffeescript
        include: [
          path.resolve(__dirname, 'resources/assets/coffee/react'),
          path.resolve(__dirname, 'resources/assets/lib'),
        ],
        test: /\.coffee$/,
        use: ['coffee-loader'],
      },
      {
        test: /\.less$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              sourceMap: true,
              url: (url) => !url.startsWith('/'),
            },
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: [Autoprefixer],
              sourceMap: true,
            },
          },
          { loader: 'less-loader', options: { sourceMap: true } },
        ],
      },
      {
        loaders: [
          {
            loader: 'file-loader',
            options: {
              name: (filepath) => {
                if (!/node_modules|bower_components/.test(filepath)) {
                  return '/images/[name].[ext]?[hash]';
                }

                const cleanPath = filepath
                  .replace(/\\/g, '/')
                  .replace(/((.*(node_modules|bower_components))|images|image|img|assets)\//g, '');

                return `/images/vendor/${cleanPath}?[hash]`;
              },
            },
          },
          {
            loader: 'img-loader',
          },
        ],
        test: /(\.(png|jpe?g|gif|webp)$|^((?!font).)*\.svg$)/,
      },
      {
        loader: 'file-loader',
        options: {
          name: (filepath) => {
            if (!/node_modules|bower_components/.test(filepath)) {
              return '/fonts/[name].[ext]?[hash]';
            }

            const cleanPath = filepath
              .replace(/\\/g, '/')
              .replace(/((.*(node_modules|bower_components))|fonts|font|assets)\//g, '');

            return `/fonts/vendor/${cleanPath}?[hash]`;
          },
        },
        test: /(\.(woff2?|ttf|eot|otf)$|font.*\.svg$)/,
      },
    ],
  },
  optimization: {
    runtimeChunk: {
      name: '/js/commons',
    },
    splitChunks: {
      cacheGroups: {
        commons: {
          chunks: 'initial',
          minChunks: 2,
          name: '/js/commons',
        },
      },
    },
  },
  output,
  plugins,
  resolve: {
    alias: {
      'ziggy': path.resolve(__dirname, 'resources/assets/js/ziggy.js'),
      'ziggy-route': path.resolve(__dirname, 'vendor/tightenco/ziggy/dist/js/route.js'),
    },
    extensions: ['*', '.js', '.coffee', '.ts', '.tsx'],
    modules: [
      path.resolve(__dirname, 'resources/assets/coffee'),
      path.resolve(__dirname, 'resources/assets/lib'),
      path.resolve(__dirname, 'resources/assets/coffee/react/_components'),
      path.resolve(__dirname, 'node_modules'),
    ],
    plugins: [new TsconfigPathsPlugin()],
  },
};

if (inProduction) {
  webpackConfig.optimization.minimizer = [
    new TerserPlugin({
      sourceMap: true,
      terserOptions: {
        safari10: true,
      },
    }),
    new CssMinimizerPlugin({
      sourceMap: true,
    }),
  ];
}

if (process.env.SENTRY_RELEASE === '1') {
  webpackConfig.plugins.push(
    new SentryPlugin({
      apiKey: process.env.SENTRY_API_KEY,
      organisation: process.env.SENTRY_ORG,
      project: process.env.SENTRY_PROJ,

      deleteAfterCompile: true,
      exclude: /\.css(\.map)?$/,
      filenameTransform: function(filename) {
        return '~' + filename;
      },
      release: function() {
        return process.env.GIT_SHA;
      },
    }),
  );
}

module.exports = webpackConfig;
<|endoftext|>