<commit_before>function epicDetailsController ($scope, $http, $q, $location) {
	var notStartedNames = ["To Do", "Open"];

    $scope.contextPath = jQuery('meta[name="ajs-context-path"]').attr('content');
    $scope.key = $location.search().epic;
    $scope.epicName = '';
    $scope.fullStories = []; // the full list of stories
    $scope.stories = []; // the list of stories in the current date range
    $scope.notStarted = 0;
    $scope.inProgress = 0;
    $scope.done = 0;

    $scope.workType = 1;

    $scope.$watch('workType', function() {
    	$scope.refresh();
    });

    $scope.points = [[]];

    //map from constant name to custom field name
    var fieldMap = {
        'Epic Name': null,
        'Story Points': null
    };

    var epicQuery = $scope.contextPath + '/rest/api/2/';
    var storiesQuery = $scope.contextPath + '/rest/api/2/search?jql=';

    if($scope.key.indexOf('-') != -1) {
        epicQuery += 'issue';
        storiesQuery += '"Epic Link"=' + $scope.key;
    }
    else {
        epicQuery += 'project';
        storiesQuery += 'project=' + $scope.key + ' and "Epic Link" is empty and issuetype not in (Epic, Sub-task)';
    }

    epicQuery += '/' + $scope.key;
    storiesQuery += ' order by resolutiondate desc';

    $q.all([
        $http.get(epicQuery),
        $http.get(storiesQuery),
        $http.get($scope.contextPath + '/rest/api/2/field')
    ]).then(function(results) {
        var epic = results[0].data;
        var stories = results[1].data;
        var fields = results[2].data;

        //get the rest of the stories if there are more
        var maxResults = stories.maxResults;
        var total = stories.total;

        if(maxResults < total) {
            var requests = [];
            for(var i = maxResults; i < total; i += maxResults) {
                requests.push($http.get(storiesQuery + '&startAt=' + i));
            }

            $q.all(requests).then(function(results) {
                //add the new stories to the list
                angular.forEach(results, function(e, i) {
                    $scope.fullStories = $scope.fullsStories.concat(e.data.issues);
                    $scope.stories = $scope.stories.concat(e.data.issues);
                });

                //refresh the display
                $scope.refresh();
            });
        }

        //setup field map
        angular.forEach(fieldMap, function(value, key) {
            angular.forEach(fields, function(elem, index) {
                if(key === elem.name) {
                    fieldMap[key] = elem.id;
                }
            });
        });

        if($scope.key.indexOf('-') != -1) {
            $scope.epicName = getField(epic.fields, 'Epic Name');
        }
        else {
            $scope.epicName = 'Other stories (' + epic.name + ')';
        }

        $scope.fullStories = stories.issues;
        $scope.stories = $scope.fullStories;

        $scope.refresh();

    });

    function getField(data, field) {
        return data[fieldMap[field]];
    }

    // count the number of not started, in progress, and done stories
    function countStories(stories) {
        $scope.notStarted = 0;
        $scope.inProgress = 0;
        $scope.done = 0;

    	angular.forEach(stories, function(story, index) {
    		var resolution = story.fields.resolutiondate;
    		if (resolution !== undefined && resolution !== null) {
    			$scope.done += getValue(story);
    		} else if (jQuery.inArray(story.fields.status.name, notStartedNames) != -1) {
    			$scope.notStarted += getValue(story);
    		} else {
    			$scope.inProgress += getValue(story);
    		}
    	});
    }

    // creates a list of (date, number) pairs
    function getProgressList(stories) {
        var list = [];

        angular.forEach(stories, function(story, index) {
            var value = getValue(story);
            list.push({
                date: Date.parse(story.fields.created),
                number: value
            });

            var resolution = story.fields.resolutiondate;
            if(resolution !== undefined && resolution !== null) {
                list.push({
                    date: Date.parse(story.fields.resolutiondate),
                    number: -value
                });
            }
        });

        list.push({
            date: new Date().getTime(),
            number: 0
        });

        return list;
    }

    // get the value of the story based on which work type is selected
    function getValue(story) {
        switch($scope.workType) {
        case 1:
            return 1;
        case 2:
            var points = getField(story.fields, 'Story Points');
            return (points !== undefined && points !== null) ? points : 0;
        case 3:
            var resolution = story.fields.resolutiondate;
            var time = story.fields.aggregateprogress.total;

            if(resolution !== undefined && resolution !== null) {
                time = story.fields.aggregateprogress.progress;
            }

            return (time / 3600).toFixed(2);
        }
    }

    // update the story counts and chart points
    $scope.refresh = function() {
    	countStories($scope.stories);

    	// update points using full story list, so graph doesn't shrink
        var points = getProgressList($scope.fullStories);
        points.sort(function(a, b) {
            return a.date - b.date;
        });
        $scope.points = [[]];
        var runningTotal = 0;
        angular.forEach(points, function(elem, index) {
            runningTotal += elem.number;
            $scope.points[0].push([elem.date, runningTotal]);
        });
    };

    //nicely format the date string
    $scope.doneDate = function(date) {
        if(date !== undefined && date !== null) {
            return new Date(Date.parse(date)).toLocaleString();
        }
        else {
            return 'unresolved';
        }
    };
}

// Directive for creating charts
// Creates a chart with an overview chart for selecting time ranges
function chartDirective() {
    return {
        restrict: 'E',
        template: "

" +
        		  "

",
        link: function(scope, elem, attrs) {
            var chart = null;
            var overview = null;

            var opts = {
                xaxis: {
                    ticks: function(axis) {
                        var min = axis.min;
                        var step = Math.floor((axis.max - axis.min) / 4);

                        var ticks = [];

                        for(var i = 0; i < 5; i++) {
                            ticks.push([min, new Date(min).toLocaleDateString()]);
                            min += step;
                        }
                        return ticks;
                    }
                },
                yaxis: {
                    minTickSize: 1,
                    tickFormatter: function(val, axis) {
                        return Math.floor(parseFloat(val));
                    }
                },
                selection: {
    				mode: "x"
    			},
                series: {
                    lines: {
                        steps: true
                    }
                }
            };

            var overviewOpts = {
            	series: {
        			lines: {
                        steps: true,
        				show: true,
        				lineWidth: 1
        			},
        			shadowSize: 0
       			},
       			xaxis: {
       				ticks: [],
       				mode: "time"
        		},
        		yaxis: {
        			ticks: [],
       				min: 0,
       				autoscaleMargin: 0.1
       			},
        		selection: {
        			mode: "x"
       			}
            };

            scope.$watch(attrs.ngModel, function(v) {
                if(!chart) {
                	var chartElem = jQuery('#chart');
                	var overviewElem = jQuery('#overview');

                    chart = jQuery.plot(chartElem, v, opts);
                    overview = jQuery.plot(overviewElem, v, overviewOpts);
                    chartElem.show();
                    overviewElem.show();
                    elem.show();
                }
                else {

                    chart.setData(v);
                    chart.setupGrid();
                    chart.draw();
                    overview.setData(v);
                    overview.setupGrid();
                    overview.draw();
                }
            });

            jQuery("#chart").bind("plotselected", function (event, ranges) {

    			// do the zooming
    			jQuery.each(chart.getXAxes(), function(_, axis) {
    				var opts = axis.options;
    				opts.min = ranges.xaxis.from;
    				opts.max = ranges.xaxis.to;
    				zoom(opts.min, opts.max);
    			});
    			chart.setupGrid();
    			chart.draw();
    			chart.clearSelection();

    			// don't fire event on the overview to prevent eternal loop

    			overview.setSelection(ranges, true);
    		});
    		jQuery("#overview").bind("plotselected", function (event, ranges) {
    			chart.setSelection(ranges);
    		});

    		// zoom to the given minimum and maximum millisecond values
    		// update the current data to be a subset between the values
    		function zoom(min, max) {
    			scope.stories = [];
    			angular.forEach(scope.fullStories, function(story, index) {
    				var created = Date.parse(story.fields.created);
    				var resolved = story.fields.resolutiondate !== null ? Date.parse(story.fields.resolutiondate) : null;
    				if (created <= max && (resolved === null || resolved >= min)) {
    					scope.stories.push(story);
    				}
    			});
    			// refresh the page, wrap in apply so that page updates
    			scope.$apply(function() {
    				scope.refresh();
    			});
    		}
        }
    };
}

<commit_msg>fixed work hours rounding<commit_after>function epicDetailsController ($scope, $http, $q, $location) {
	var notStartedNames = ["To Do", "Open"];

    $scope.contextPath = jQuery('meta[name="ajs-context-path"]').attr('content');
    $scope.key = $location.search().epic;
    $scope.epicName = '';
    $scope.fullStories = []; // the full list of stories
    $scope.stories = []; // the list of stories in the current date range
    $scope.notStarted = 0;
    $scope.inProgress = 0;
    $scope.done = 0;

    $scope.workType = 1;

    $scope.$watch('workType', function() {
    	$scope.refresh();
    });

    $scope.points = [[]];

    //map from constant name to custom field name
    var fieldMap = {
        'Epic Name': null,
        'Story Points': null
    };

    var epicQuery = $scope.contextPath + '/rest/api/2/';
    var storiesQuery = $scope.contextPath + '/rest/api/2/search?jql=';

    if($scope.key.indexOf('-') != -1) {
        epicQuery += 'issue';
        storiesQuery += '"Epic Link"=' + $scope.key;
    }
    else {
        epicQuery += 'project';
        storiesQuery += 'project=' + $scope.key + ' and "Epic Link" is empty and issuetype not in (Epic, Sub-task)';
    }

    epicQuery += '/' + $scope.key;
    storiesQuery += ' order by resolutiondate desc';

    $q.all([
        $http.get(epicQuery),
        $http.get(storiesQuery),
        $http.get($scope.contextPath + '/rest/api/2/field')
    ]).then(function(results) {
        var epic = results[0].data;
        var stories = results[1].data;
        var fields = results[2].data;

        //get the rest of the stories if there are more
        var maxResults = stories.maxResults;
        var total = stories.total;

        if(maxResults < total) {
            var requests = [];
            for(var i = maxResults; i < total; i += maxResults) {
                requests.push($http.get(storiesQuery + '&startAt=' + i));
            }

            $q.all(requests).then(function(results) {
                //add the new stories to the list
                angular.forEach(results, function(e, i) {
                    $scope.fullStories = $scope.fullsStories.concat(e.data.issues);
                    $scope.stories = $scope.stories.concat(e.data.issues);
                });

                //refresh the display
                $scope.refresh();
            });
        }

        //setup field map
        angular.forEach(fieldMap, function(value, key) {
            angular.forEach(fields, function(elem, index) {
                if(key === elem.name) {
                    fieldMap[key] = elem.id;
                }
            });
        });

        if($scope.key.indexOf('-') != -1) {
            $scope.epicName = getField(epic.fields, 'Epic Name');
        }
        else {
            $scope.epicName = 'Other stories (' + epic.name + ')';
        }

        $scope.fullStories = stories.issues;
        $scope.stories = $scope.fullStories;

        $scope.refresh();

    });

    function getField(data, field) {
        return data[fieldMap[field]];
    }

    // count the number of not started, in progress, and done stories
    function countStories(stories) {
        $scope.notStarted = 0;
        $scope.inProgress = 0;
        $scope.done = 0;

    	angular.forEach(stories, function(story, index) {
    		var resolution = story.fields.resolutiondate;
    		if (resolution !== undefined && resolution !== null) {
    			$scope.done += getValue(story);
    		} else if (jQuery.inArray(story.fields.status.name, notStartedNames) != -1) {
    			$scope.notStarted += getValue(story);
    		} else {
    			$scope.inProgress += getValue(story);
    		}
    	});

    	if ($scope.workType == 3) {
    		$scope.notStarted = $scope.notStarted.toFixed(2);
    		$scope.inProgress = $scope.inProgress.toFixed(2);
    		$scope.done = $scope.done.toFixed(2);
    	}
    }

    // creates a list of (date, number) pairs
    function getProgressList(stories) {
        var list = [];

        angular.forEach(stories, function(story, index) {
            var value = getValue(story);
            list.push({
                date: Date.parse(story.fields.created),
                number: value
            });

            var resolution = story.fields.resolutiondate;
            if(resolution !== undefined && resolution !== null) {
                list.push({
                    date: Date.parse(story.fields.resolutiondate),
                    number: -value
                });
            }
        });

        list.push({
            date: new Date().getTime(),
            number: 0
        });

        return list;
    }

    // get the value of the story based on which work type is selected
    function getValue(story) {
        switch($scope.workType) {
        case 1:
            return 1;
        case 2:
            var points = getField(story.fields, 'Story Points');
            return (points !== undefined && points !== null) ? points : 0;
        case 3:
            var resolution = story.fields.resolutiondate;
            var time = story.fields.aggregateprogress.total;

            if(resolution !== undefined && resolution !== null) {
                time = story.fields.aggregateprogress.progress;
            }

            return (time / 3600);
        }
    }

    // update the story counts and chart points
    $scope.refresh = function() {
    	countStories($scope.stories);

    	// update points using full story list, so graph doesn't shrink
        var points = getProgressList($scope.fullStories);
        points.sort(function(a, b) {
            return a.date - b.date;
        });
        $scope.points = [[]];
        var runningTotal = 0;
        angular.forEach(points, function(elem, index) {
            runningTotal += elem.number;
            $scope.points[0].push([elem.date, runningTotal]);
        });
    };

    //nicely format the date string
    $scope.doneDate = function(date) {
        if(date !== undefined && date !== null) {
            return new Date(Date.parse(date)).toLocaleString();
        }
        else {
            return 'unresolved';
        }
    };
}

// Directive for creating charts
// Creates a chart with an overview chart for selecting time ranges
function chartDirective() {
    return {
        restrict: 'E',
        template: "

" +
        		  "

",
        link: function(scope, elem, attrs) {
            var chart = null;
            var overview = null;

            var opts = {
                xaxis: {
                    ticks: function(axis) {
                        var min = axis.min;
                        var step = Math.floor((axis.max - axis.min) / 4);

                        var ticks = [];

                        for(var i = 0; i < 5; i++) {
                            ticks.push([min, new Date(min).toLocaleDateString()]);
                            min += step;
                        }
                        return ticks;
                    }
                },
                yaxis: {
                    minTickSize: 1,
                    tickFormatter: function(val, axis) {
                        return Math.floor(parseFloat(val));
                    }
                },
                selection: {
    				mode: "x"
    			},
                series: {
                    lines: {
                        steps: true
                    }
                }
            };

            var overviewOpts = {
            	series: {
        			lines: {
                        steps: true,
        				show: true,
        				lineWidth: 1
        			},
        			shadowSize: 0
       			},
       			xaxis: {
       				ticks: [],
       				mode: "time"
        		},
        		yaxis: {
        			ticks: [],
       				min: 0,
       				autoscaleMargin: 0.1
       			},
        		selection: {
        			mode: "x"
       			}
            };

            scope.$watch(attrs.ngModel, function(v) {
                if(!chart) {
                	var chartElem = jQuery('#chart');
                	var overviewElem = jQuery('#overview');

                    chart = jQuery.plot(chartElem, v, opts);
                    overview = jQuery.plot(overviewElem, v, overviewOpts);
                    chartElem.show();
                    overviewElem.show();
                    elem.show();
                }
                else {

                    chart.setData(v);
                    chart.setupGrid();
                    chart.draw();
                    overview.setData(v);
                    overview.setupGrid();
                    overview.draw();
                }
            });

            jQuery("#chart").bind("plotselected", function (event, ranges) {

    			// do the zooming
    			jQuery.each(chart.getXAxes(), function(_, axis) {
    				var opts = axis.options;
    				opts.min = ranges.xaxis.from;
    				opts.max = ranges.xaxis.to;
    				zoom(opts.min, opts.max);
    			});
    			chart.setupGrid();
    			chart.draw();
    			chart.clearSelection();

    			// don't fire event on the overview to prevent eternal loop

    			overview.setSelection(ranges, true);
    		});
    		jQuery("#overview").bind("plotselected", function (event, ranges) {
    			chart.setSelection(ranges);
    		});

    		// zoom to the given minimum and maximum millisecond values
    		// update the current data to be a subset between the values
    		function zoom(min, max) {
    			scope.stories = [];
    			angular.forEach(scope.fullStories, function(story, index) {
    				var created = Date.parse(story.fields.created);
    				var resolved = story.fields.resolutiondate !== null ? Date.parse(story.fields.resolutiondate) : null;
    				if (created <= max && (resolved === null || resolved >= min)) {
    					scope.stories.push(story);
    				}
    			});
    			// refresh the page, wrap in apply so that page updates
    			scope.$apply(function() {
    				scope.refresh();
    			});
    		}
        }
    };
}

<|endoftext|>


<commit_before>$(document).ready(function () {
    // https://datatables.net/plug-ins/sorting/date-euro
    $.extend(jQuery.fn.dataTableExt.oSort, {
        "date-euro-pre": function (a) {
            var x;

            if ($.trim(a) !== '') {
                var frDatea = $.trim(a).split(' ');
                var frTimea = (undefined != frDatea[1]) ? frDatea[1].split(':') : [00, 00, 00];
                var frDatea2 = frDatea[0].split('.');
                x = (frDatea2[2] + frDatea2[1] + frDatea2[0] + frTimea[0] + frTimea[1] + ((undefined != frTimea[2]) ? frTimea[2] : 0)) * 1;
            } else {
                x = Infinity;
            }
            return x;
        },
        "date-euro-asc": function (a, b) {
            return a - b;
        },
        "date-euro-desc": function (a, b) {
            return b - a;
        }
    });

    // Current rentals with euro date sorting
    $('#rentals').DataTable({columnDefs: [{type: 'date-euro', targets: [3]}]});
    // Rental history with euro date sorting
    $('#rentals_history').DataTable({columnDefs: [{type: 'date-euro', targets: [3, 4]}]});
    $('#usrs').DataTable(); // Users table
    $('#devs').DataTable(); // Device table

// Manage symbol "-". Each barcode of the tablet is length of 5
    $('#devices').bind('input propertychange', function (event) {
        // Remove "-" from input
        var text = event.target.value.replace(new RegExp("-", "g"), "");
        // Set input to ""
        $(event.target).val("");

        // Re-add "-"
        var finalText = "";
        var i = 0;
        for (var token in text) {
            if (i !== 0 && i % 5 === 0) {
                finalText += "-";
            }
            finalText += text[token];
            i++;
        }
        // Set text to input
        $(event.target).val(finalText.toUpperCase());
        // Update tablet counter
        $('#charNum').text(event.target.value.split("-").length);
    });

    // Async check that user exists.
    $("#personInformation").bind("input propertychange", function () {
        $.ajax({
            url: window.location.href + "exists",
            type: "POST",
            //data: {pin: $("#personInformation").val(), _csrf: $('meta[name="_csrf"]').attr('content')},
            data: {pin: $("#personInformation").val()},
            success: function (data) {
                if (typeof(data) === "string") {
                    $("#usr").text(data !== "" ? "Tere, " + data + "! ✔" : "Sisesta printeri kood ✘")
                        .removeClass("valid invalid").addClass(data !== "" ? "valid" : "invalid");
                }
            }
        });
    });

    // Add autofocus on modal elements
    $('.modal').on('shown.bs.modal', function () {
        $(this).find('[autofocus]').focus();
    });

    // Load the Visualization API and the corechart package.
    google.charts.load('current', {'packages': ['corechart']});

    var tabletHistory = [];

    function callDeviceUsagePieChart() {
        $.ajax({
            url: window.location.href + "_chart_device",
            type: 'GET',
            cache: false,
            success: function (data) {
                data.forEach(function (entry) {
                    tabletHistory.push([entry[0], parseInt(entry[1])]);
                });
                google.charts.setOnLoadCallback(pieChartDeviceUsage);
            }
        });
    }

    function pieChartDeviceUsage() {
        var data = new google.visualization.DataTable();
        data.addColumn('string', 'Seade');
        data.addColumn('number', 'Kasutusi');
        data.addRows(tabletHistory);
        var options = {
            title: "Aktiivsete seadmete kasutuste arv kokku",
            width: 500,
            height: 500,
            vAxis: {viewWindowMode: "explicit", viewWindow: {min: 0}}
        };
        new google.visualization.PieChart(document.getElementById("piechart_device")).draw(data, options);
    }

    if ($("#piechart_device").length) {
        // Set a callback to run when the Google Visualization API is loaded.
        google.charts.setOnLoadCallback(callDeviceUsagePieChart);
    }

    var userHistory = [];

    function callUserUsagePieChart() {
        $.ajax({
            url: window.location.href + "_chart_user",
            type: 'GET',
            cache: false,
            success: function (data) {
                data.forEach(function (entry) {
                    userHistory.push([entry[0], parseInt(entry[1])]);
                });
                google.charts.setOnLoadCallback(pieChartUserUsage);
            }
        });
    }

    function pieChartUserUsage() {
        var data = new google.visualization.DataTable();
        data.addColumn('string', 'PIN');
        data.addColumn('number', 'Kasutusi');
        data.addRows(userHistory);
        var options = {
            title: "Aktiveeritud kasutajate aktiivsus laenutuste arvu lõikes",
            width: 500,
            height: 500,
            vAxis: {viewWindowMode: "explicit", viewWindow: {min: 0}}
        };
        new google.visualization.PieChart(document.getElementById("piechart_user")).draw(data, options);
    }

    if ($("#piechart_user").length) {
        // Set a callback to run when the Google Visualization API is loaded.
        google.charts.setOnLoadCallback(callUserUsagePieChart);
    }
    var monthHistory = [];

    function callLineUsageMonthly() {
        $.ajax({
            url: window.location.href + "_chart_month",
            type: 'GET',
            cache: false,
            success: function (data) {
                data.forEach(function (entry) {
                    monthHistory.push([entry[0], entry[1]]);
                });
                google.charts.setOnLoadCallback(lineUsageMonthly);
            }
        });
    }

    function lineUsageMonthly() {
        var data = new google.visualization.DataTable();
        data.addColumn('number', 'Päev');
        data.addColumn('number', 'Kasutusi');
        data.addRows(monthHistory);
        var options = {
            title: "Seadmete kasutus käesoleva aasta lõikes",
            width: 800,
            height: 500,
            vAxis: {title: "Laenutusi", viewWindowMode: "explicit", viewWindow: {min: 0}},
            hAxis: {title: "Kuu"}
        };
        new google.visualization.LineChart(document.getElementById("line_month")).draw(data, options);
    }

    if ($("#line_month").length) {
        // Set a callback to run when the Google Visualization API is loaded.
        google.charts.setOnLoadCallback(callLineUsageMonthly);
    }

    var hist = [];

    function callUsageHist() {
        $.ajax({
            url: window.location.href + "_chart_day",
            type: 'GET',
            cache: false,
            success: function (data) {
                data.forEach(function (entry) {
                    hist.push([entry]);
                });
                google.charts.setOnLoadCallback(usageHist);
            }
        });
    }

    function usageHist() {
        var data = new google.visualization.DataTable();
        data.addColumn('number', 'Seadmeid');
        data.addRows(hist);
        var options = {
            title: "Laenutavate seadmete kogus",
            width: 800,
            height: 500,
            vAxis: {title: "Laenutuste arv", viewWindowMode: "explicit", viewWindow: {min: 0}},
            hAxis: {title: "Seadmete arv"},
            histogram: {bucketSize: 1}
        };
        new google.visualization.Histogram(document.getElementById("hist_day")).draw(data, options);
    }

    if ($("#hist_day").length) {
        // Set a callback to run when the Google Visualization API is loaded.
        google.charts.setOnLoadCallback(callUsageHist);
    }
});<commit_msg>Made charts more readable<commit_after>$(document).ready(function () {
    // https://datatables.net/plug-ins/sorting/date-euro
    $.extend(jQuery.fn.dataTableExt.oSort, {
        "date-euro-pre": function (a) {
            var x;

            if ($.trim(a) !== '') {
                var frDatea = $.trim(a).split(' ');
                var frTimea = (undefined != frDatea[1]) ? frDatea[1].split(':') : [00, 00, 00];
                var frDatea2 = frDatea[0].split('.');
                x = (frDatea2[2] + frDatea2[1] + frDatea2[0] + frTimea[0] + frTimea[1] + ((undefined != frTimea[2]) ? frTimea[2] : 0)) * 1;
            } else {
                x = Infinity;
            }
            return x;
        },
        "date-euro-asc": function (a, b) {
            return a - b;
        },
        "date-euro-desc": function (a, b) {
            return b - a;
        }
    });

    // Current rentals with euro date sorting
    $('#rentals').DataTable({columnDefs: [{type: 'date-euro', targets: [3]}]});
    // Rental history with euro date sorting
    $('#rentals_history').DataTable({columnDefs: [{type: 'date-euro', targets: [3, 4]}]});
    $('#usrs').DataTable(); // Users table
    $('#devs').DataTable(); // Device table

// Manage symbol "-". Each barcode of the tablet is length of 5
    $('#devices').bind('input propertychange', function (event) {
        // Remove "-" from input
        var text = event.target.value.replace(new RegExp("-", "g"), "");
        // Set input to ""
        $(event.target).val("");

        // Re-add "-"
        var finalText = "";
        var i = 0;
        for (var token in text) {
            if (i !== 0 && i % 5 === 0) {
                finalText += "-";
            }
            finalText += text[token];
            i++;
        }
        // Set text to input
        $(event.target).val(finalText.toUpperCase());
        // Update tablet counter
        $('#charNum').text(event.target.value.split("-").length);
    });

    // Async check that user exists.
    $("#personInformation").bind("input propertychange", function () {
        $.ajax({
            url: window.location.href + "exists",
            type: "POST",
            //data: {pin: $("#personInformation").val(), _csrf: $('meta[name="_csrf"]').attr('content')},
            data: {pin: $("#personInformation").val()},
            success: function (data) {
                if (typeof(data) === "string") {
                    $("#usr").text(data !== "" ? "Tere, " + data + "! ✔" : "Sisesta printeri kood ✘")
                        .removeClass("valid invalid").addClass(data !== "" ? "valid" : "invalid");
                }
            }
        });
    });

    // Add autofocus on modal elements
    $('.modal').on('shown.bs.modal', function () {
        $(this).find('[autofocus]').focus();
    });

    // Load the Visualization API and the corechart package.
    google.charts.load('current', {'packages': ['corechart']});

    var tabletHistory = [];

    function callDeviceUsagePieChart() {
        $.ajax({
            url: window.location.href + "_chart_device",
            type: 'GET',
            cache: false,
            success: function (data) {
                data.forEach(function (entry) {
                    tabletHistory.push([entry[0], parseInt(entry[1])]);
                });
                google.charts.setOnLoadCallback(pieChartDeviceUsage);
            }
        });
    }

    function pieChartDeviceUsage() {
        var data = new google.visualization.DataTable();
        data.addColumn('string', 'Seade');
        data.addColumn('number', 'Kasutusi');
        data.addRows(tabletHistory);
        var options = {
            title: "Aktiivsete seadmete kasutuste arv kokku",
            width: 500,
            height: 500,
            vAxis: {viewWindowMode: "explicit", viewWindow: {min: 0}}
        };
        new google.visualization.PieChart(document.getElementById("piechart_device")).draw(data, options);
    }

    if ($("#piechart_device").length) {
        // Set a callback to run when the Google Visualization API is loaded.
        google.charts.setOnLoadCallback(callDeviceUsagePieChart);
    }

    var userHistory = [];

    function callUserUsagePieChart() {
        $.ajax({
            url: window.location.href + "_chart_user",
            type: 'GET',
            cache: false,
            success: function (data) {
                data.forEach(function (entry) {
                    userHistory.push([entry[0], parseInt(entry[1])]);
                });
                google.charts.setOnLoadCallback(pieChartUserUsage);
            }
        });
    }

    function pieChartUserUsage() {
        var data = new google.visualization.DataTable();
        data.addColumn('string', 'PIN');
        data.addColumn('number', 'Kasutusi');
        data.addRows(userHistory);
        var options = {
            title: "Aktiveeritud kasutajate aktiivsus laenutuste arvu lõikes",
            width: 500,
            height: 500,
            vAxis: {viewWindowMode: "explicit", viewWindow: {min: 0}}
        };
        new google.visualization.PieChart(document.getElementById("piechart_user")).draw(data, options);
    }

    if ($("#piechart_user").length) {
        // Set a callback to run when the Google Visualization API is loaded.
        google.charts.setOnLoadCallback(callUserUsagePieChart);
    }
    var monthHistory = [];

    function callLineUsageMonthly() {
        $.ajax({
            url: window.location.href + "_chart_month",
            type: 'GET',
            cache: false,
            success: function (data) {
                data.forEach(function (entry) {
                    monthHistory.push([entry[0], entry[1]]);
                });
                google.charts.setOnLoadCallback(lineUsageMonthly);
            }
        });
    }

    function lineUsageMonthly() {
        var data = new google.visualization.DataTable();
        data.addColumn('number', 'Päev');
        data.addColumn('number', 'Kasutusi');
        data.addRows(monthHistory);
        var options = {
            title: "Seadmete kasutus käesoleva aasta lõikes",
            width: 800,
            height: 500,
            vAxis: {title: "Laenutusi", viewWindowMode: "explicit", viewWindow: {min: 0}},
            hAxis: {title: "Kuu"}
        };
        new google.visualization.LineChart(document.getElementById("line_month")).draw(data, options);
    }

    if ($("#line_month").length) {
        // Set a callback to run when the Google Visualization API is loaded.
        google.charts.setOnLoadCallback(callLineUsageMonthly);
    }

    var hist = [];

    function callUsageHist() {
        $.ajax({
            url: window.location.href + "_chart_day",
            type: 'GET',
            cache: false,
            success: function (data) {
                data.forEach(function (entry) {
                    hist.push([entry]);
                });
                google.charts.setOnLoadCallback(usageHist);
            }
        });
    }

    function usageHist() {
        var data = new google.visualization.DataTable();
        data.addColumn('number', 'Seadmeid');
        data.addRows(hist);
        var options = {
            title: "Laenutavate seadmete kogus käesoleval aastal",
            width: 800,
            height: 500,
            vAxis: {title: "Laenutuste arv", viewWindowMode: "explicit", viewWindow: {min: 0}},
            hAxis: {title: "Seadmete arv"},
            histogram: {bucketSize: 1}
        };
        new google.visualization.Histogram(document.getElementById("hist_day")).draw(data, options);
    }

    if ($("#hist_day").length) {
        // Set a callback to run when the Google Visualization API is loaded.
        google.charts.setOnLoadCallback(callUsageHist);
    }
});<|endoftext|>


<commit_before>/**
 * @file 柱形图
 * @author Marx
 */

import './index.less';
import * as d3 from 'd3';

// 柱形图的数据
const dataset = [50, 43, 120, 87, 99, 167, 142];

// svg区域大小
const width = 400;
const height = 400;

const xAxisWidth = 300;
const yAxisWidth = 300;

const padding = {
    top: 20,
    right: 20,
    bottom: 20,
    left: 40
};

// 矩形所占的宽度，包括空白
const rectStep = 35;
// 矩形所占的宽度，不包括空白
const rectwidth = 30;

// 画svg区域
const svg = d3.select('body')
    .append('svg')
    .attr('width', width)
    .attr('height', height);

const sortButton = document.getElementById('sort');
sortButton.addEventListener('click', () => {
    sortRect();
})

const xScale = d3.scaleBand()
    .domain(d3.range(dataset.length))
    .rangeRound([0, xAxisWidth])
    .padding(0.2);
const axis = d3.axisBottom(xScale);

const yScale = d3.scaleLinear()
    .domain([0, d3.max(dataset)])
    .range([yAxisWidth, 0])

const yAxis = d3.axisLeft(yScale).ticks(8);

/**
 * 画矩形
 *
 * @param {Object} rect 矩形选择集
 */
function drawRect(rect) {
    rect.attr('fill', 'steelblue')
        .attr('x', (d, i) => padding.left + xScale(i))
        .attr('y', d => height - padding.bottom - d)
        .attr('width', xScale.bandwidth())
        .attr('height', (d, i) => {
            console.log(xScale(i), xScale.bandwidth());
            console.log(xScale.padding());
            return d
        });
}

/**
 * 填写文字
 *
 * @param {Object} text 文字选择集
 */
function drawText(text) {
    text.attr('fill', '#fff')
        .attr('font-size', '14px')
        .attr('text-anchor', 'middle')
        .attr('x', (d, i) => padding.left + xScale(i))
        .attr('y', d => height - d - padding.top)
        .attr('dx', rectwidth / 2)
        .attr('dy', '1em')
        .text(d => d);
}

/**
 * 更新矩形
 */
function updateRect() {
    const update = svg.selectAll('rect')
        .data(dataset);
    const enter = update.enter()
        .append('rect');
    drawRect(update);
    drawRect(enter);
}

/**
 * 更新文字
 */
function updateText() {
    const update = svg.selectAll('text')
        .data(dataset);
    const enter = update.enter()
        .append('text');
    drawText(update);
    drawText(enter);
}

/**
 * 排序
 */
function sortRect() {
    dataset.sort(d3.ascending);
    // 画矩形
    updateRect();

    // 填写文字
    updateText();
}

// 画矩形
updateRect();

// 填写文字
updateText();

const gAxis = svg.append('g')
    .attr('transform', 'translate(' + padding.left + ', ' + (height - padding.bottom) + ')')
    .call(axis);
const gyAxis = svg.append('g')
    .attr('transform', 'translate(' + padding.left + ', ' + (height - yAxisWidth - padding.bottom) + ')')
    .call(yAxis);<commit_msg>坐标轴对齐<commit_after>/**
 * @file 柱形图
 * @author Marx
 */

import './index.less';
import * as d3 from 'd3';

// 柱形图的数据
const dataset = [50, 43, 120, 87, 99, 167, 142];

// svg区域大小
const width = 400;
const height = 400;

const xAxisWidth = 300;
const yAxisWidth = 300;

const padding = {
    top: 20,
    right: 20,
    bottom: 20,
    left: 40
};

// 矩形所占的宽度，包括空白
const rectStep = 35;
// 矩形所占的宽度，不包括空白
const rectwidth = 30;

// 画svg区域
const svg = d3.select('body')
    .append('svg')
    .attr('width', width)
    .attr('height', height);

const sortButton = document.getElementById('sort');
sortButton.addEventListener('click', () => {
    sortRect();
})

const xScale = d3.scaleBand()
    .domain(d3.range(dataset.length))
    .rangeRound([0, xAxisWidth])
    .padding(0.2);
const axis = d3.axisBottom(xScale);

const yScale = d3.scaleLinear()
    .domain([d3.max(dataset), 0])
    .range([0, yAxisWidth])

console.log(yScale(142));

const yAxis = d3.axisLeft(yScale).ticks(8);

/**
 * 画矩形
 *
 * @param {Object} rect 矩形选择集
 */
function drawRect(rect) {
    rect.attr('fill', 'steelblue')
        .attr('x', (d, i) => padding.left + xScale(i))
        .attr('y', d => height - padding.bottom - yScale(167 - d))
        .attr('width', xScale.bandwidth())
        .attr('height', (d, i) => {
            console.log(xScale(i), xScale.bandwidth());
            console.log(xScale.padding());
            return yScale(167 - d)
        });
}

/**
 * 填写文字
 *
 * @param {Object} text 文字选择集
 */
function drawText(text) {
    text.attr('fill', '#fff')
        .attr('font-size', '14px')
        .attr('text-anchor', 'middle')
        .attr('x', (d, i) => padding.left + xScale(i))
        .attr('y', d => height - yScale(167 - d) - padding.top)
        .attr('dx', rectwidth / 2)
        .attr('dy', '1em')
        .text(d => d);
}

/**
 * 更新矩形
 */
function updateRect() {
    const update = svg.selectAll('rect')
        .data(dataset);
    const enter = update.enter()
        .append('rect');
    drawRect(update);
    drawRect(enter);
}

/**
 * 更新文字
 */
function updateText() {
    const update = svg.selectAll('text')
        .data(dataset);
    const enter = update.enter()
        .append('text');
    drawText(update);
    drawText(enter);
}

/**
 * 排序
 */
function sortRect() {
    dataset.sort(d3.ascending);
    // 画矩形
    updateRect();

    // 填写文字
    updateText();
}

// 画矩形
updateRect();

// 填写文字
updateText();

const gAxis = svg.append('g')
    .attr('transform', 'translate(' + padding.left + ', ' + (height - padding.bottom) + ')')
    .call(axis);
const gyAxis = svg.append('g')
    .attr('transform', 'translate(' + padding.left + ', ' + (height - yAxisWidth - padding.bottom) + ')')
    .call(yAxis);<|endoftext|>


<commit_before>/** ******************************************************************************************************************
 * @file All obejct definitionas.
 * @author julian <jjdanois@gmail.com>
 * @since 1.0.0
 * @date 19-Jun-2017
 *********************************************************************************************************************/
"use strict";

// @formatter:off

const str = `
/**
 * @typedef {Object} Challenge
 * @property {String} 	question
 * @property {String} 	answer
 */

/**
 * @typedef {Object} Note
 * @property {String} title
 * @property {String} body
 */

/**
 * @typedef {Object} Annotation
 * @property {String} type
 * @property {String} subType
 * @property {String} value
 */

/**
 * @typedef {Object} Attachment
 * @property {String} type
 * @property {String} name
 * @property {String} path
 */

/**
 * @typedef {String} CronString
 */

/**
 * @typedef {Object} Cron
 */

/**
 * @typedef {Object} Shared
 * @property {ObjectId}          _id
 * @property {Array<Note>}       notes
 * @property {Array<Annotation>} annotations
 * @property {Array<Attachment>} attachments
 */

/**
 * @typedef {Object} Duration
 * @property {Number} years
 * @property {Number} months
 * @property {Number} days
 * @property {Number} hours
 * @property {Number} minutes
 */

/**
 * @typedef {Object} DateTimeRange
 * @property {Date} 		start
 * @property {?Date} 		[end]		- If missing, event is instant
 * @property {?Duration} 	[duration]
 * @property {?String} 		[all]		- "day", "week", "month", "year"
 */

/**
 * @typedef {Object} ScheduleItem
 * @property {ObjectId} 		_id
 * @property {String} 			type
 * @property {?ObjectId} 		[generator]     - The event that created this or missing if enetered directly by the user somehow
 * @property {DateTimeRange}	[timeDate]      - If no generator then we should have this
 */

/**
 * A medication is anything the person can take. It can be either a prescription medication or
 * just some form of OTC medication or supplement.
 *
 * @typedef {Object} Medication
 * @property {ObjectId} [prescription]
 * @property {ObjectId} drug
 * @property {Array<ScheduleItem>} taken
 * @property {Number} count
 * @property {ObjectId} dosage
 * @property {ObjectId} [event]
 */

/**
 * @typedef {Object} Dosage
 * @property {String} name
 * @property {Array<Number>} values
 * @property {Array<String>} units
 * @property {String} format
 */

/**
 * @typedef {Object} Refill
 * @property {ObjectId} filledBy        - Who filled it
 * @property {Date} filledDate          - When was it filled
 * @property {ObjectId} [alternative]   - For generics and the like
 */

/**
 *
 * @typedef {Object} Prescription
 * @property {ObjectId} medication      - What is the prescription for
 * @property {ObjectId} [event]         - If no event then prescription is "as needed" otherwise when to take
 * @property {ObjectId} dosage          - What does it says about dosage
 * @property {ObjectId} issuedBy        - Who wrote the prescription
 * @property {Date} validDate           - What's the date on the prescription
 * @property {Array<Refill>} refills    - Every time it gets refilled, we add one of these
 * @property {Number} totalRefills      - How many refils on the prescription, to get remaining refills: "totalRefills - refills.length"
 */

/**
 * An actual real medication. This is a reference collection only. We will add more things to it in case of supplements
 * or other OTC drugs.
 *
 * @typedef {Object} Drug
 * @property {String} name
 */

/**
 * @typedef {Object} Event
 * @property {ObjectId} 		_id
 * @property {String} 			type            - "medication", "note", "user", "other"
 * @property {Cron}             [sequence]
 * @property {DateTimeRange} 	next            - Hard date generate from the sequence
 * @property {DBRef}            target          - What is it that happens
 */

 /**
 * @typedef {Object} User
 * @extends Shared
 * @property {String} 	        type
 * @property {String}	        firstName
 * @property {String}	        lastName
 * @property {String}	        fullName
 * @property {String}	        preferred
 * @property {Date} 	        dob
 * @property {String} 	        password
 * @property {?String}          [emailAuthDate]
 * @property {?String}          [deferAuthCode]
 * @property {Boolean}          [validated=false]
 * @property {Array<Challenge>} challenges
 * @property {Array<Medication>}  _medications      - Inactive medications for history purposes
 * @property {Array<Prescription>}  medications   - Current active medications
 * @property {Array<Event>}     events              - All events from medications and those added by the user
 */
`;

/**
 * @typedef {object} Parameters
 * @property {Request|request} [req]
 * @property {Response|response} [res]
 * @property {User} user
 * @property {*} [data]
 * @property {?Credentials} creds
 * @property {?function} respond
 * @property {?function} error
 * @property {?Array<string>} wildcard
 * @property {?function(string):?string} get
 * @property {?Object<string,string>} [params]
 * @property {?Object<string,string|boolean|Array<string|boolean>>} [query]
 * @property {?Object<string,string>} [headers]
 * @property {?Object<string,string>} [header]
 * @property {?QueryOptions} options
 * @property {function} [kill]
 */

/**
 * @typedef {object} QueryOptions
 * @property {?(String|Array<String>)} [materialize]
 * @property {Number} [recurse=1]
 * @property {object} [where]
 * @property {Number} [skip=0]
 * @property {Number} [limit=20]
 * @property {?(String|Array<String>)} project    - From project=field1,field2,-field3
 * @property {Object} [projections]
 * @property {?Object<string,number>} [sort]        - From sort=field1,-field2,field3
 * @property {Boolean} [upsert=false]
 * @property {Boolean} [lenient=false]
 * @property {Number} flags
 * @property {User} user
 * @property {Number} DEFER_PREP
 * @property {Number} UPSERT
 */

const { random, lorem } = require( 'faker' );

function getRandomIntInclusive( min, max ) {
    min = Math.ceil( min );
    max = Math.floor( max );
    return Math.floor( Math.random() * ( max - min + 1 ) ) + min;
}

const typeDef = {
    String: lorem.words,
    Object: () => {},
    Number: () => getRandomIntInclusive( 1, 10 ),
    Date: () => new Date(),
    Boolean: () => !!getRandomIntInclusive( 0, 1 ),
    ObjectId: random.uuid,
    DBRef: () => 'DBRef'
};

function createType ( type, line ) {
    const isArray = type.includes( 'Array' );

    if ( isArray ) type = type.match( /<(.*)>/ )[ 1 ];

    const val = typeDef[ type ];
    if ( val ) return isArray ? [ val() ] : val();
    throw new Error( `
        Type: ${type},
        Got: ${typeDef[ type ]}
        Line: ${line}
    ` );
}

const defObj = str.split( '\n' ).reduce( ( def, line ) => {
    if ( line.includes( '* @typedef' ) ) {
        typeDef[ def.target ] = () => def[ def.target ]; // Save the last Object

        const target = line.match( /\s(\w+)$/ )[ 1 ];
        def.target = target;
        def[ target ] = {};
    }
    else if ( line.includes( '* @property' ) ) {
        const [ , type, name ] = line.match( /{??([a-zA-Z<>]*)}\s+\[?(\w+)\]?/ );
        def[ def.target ][ name ] = createType( type, line );
    }
    return def;
}, { target: '' } );

console.log( defObj );<commit_msg>remove file<commit_after><|endoftext|>


<commit_before>$(document).ready(function () {
    check();
});

function check() {
    $.get('check', function (hosts) {
        var html = '
' + hosts.name + '
';
        hosts.group.forEach(function (group) {
            html += '
' + group.name + '
';
            group.host.forEach(function (host) {
                var color = host.status > 200 ? 'error' : 'success';
                html += '

';
                html += '' + host.name + '	';
                html += '' + host.url + '	';
                html += '' + host.status + '	';
                html += '' + host.time + ' ms	';
                html += '

';
            });
        });
        html += "
";

        html += '
Last refresh: ' + $.format.date(new Date(), "dd.MM.yyyy HH:mm:ss.SSSk") + '
';

        $('#content').html(html);
    });
}

var interval;
var autorefresh = false;

function switchAutoRefresh() {
    autorefresh = !autorefresh;

    var text = autorefresh ? 'Toggle auto refresh off' : 'Toggle auto refresh on';
    $('#autorefresh').text(text);

    if (autorefresh) {
        interval = setInterval(function () {
            check();
        }, 10000);
    } else {
        clearInterval(interval);
    }
}
<commit_msg>removed jQuery<commit_after>window.onload = function () {
    check();
}

function check() {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", 'check', true);
    xhr.onload = function () {
        var hosts = JSON.parse(xhr.responseText);

        var html = '
' + hosts.name + '
';
        hosts.group.forEach(function (group) {
            html += '
' + group.name + '
';
            group.host.forEach(function (host) {
                var color = host.status > 200 ? 'error' : 'success';
                html += '

';
                html += '' + host.name + '	';
                html += '' + host.url + '	';
                html += '' + host.status + '	';
                html += '' + host.time + ' ms	';
                html += '

';
            });
        });
        html += "
";

        html += '
Last refresh: ' + formatTimestamp() + '
';

        document.getElementById('content').innerHTML = html;
    };
    xhr.send();
}

var interval;
var autorefresh = false;

function switchAutoRefresh() {
    autorefresh = !autorefresh;

    var text = autorefresh ? 'Toggle auto refresh off' : 'Toggle auto refresh on';
    document.getElementById('autorefresh').value = text;

    if (autorefresh) {
        interval = setInterval(function () {
            check();
        }, 10000);
    } else {
        clearInterval(interval);
    }
}

function formatTimestamp() {
    var date = new Date();
    return pad(date.getDate()) + '.' + pad((date.getMonth() + 1)) + '.' + date.getFullYear() + ' '
            + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
}

function pad(value) {
    if (value < 10) {
        return '0' + value;
    } else {
        return value;
    }
}<|endoftext|>


<commit_before>import chalk from 'chalk';
import URI from 'urijs';
import config from '../config';
import * as errors from '../errors';

import { toolNameFromEndpoint } from '../libs/consume-openapi';
import { existFile, readFile, writeFile } from '../libs/file';
import { quietParse, minifyJSON } from '../libs/json';
import * as specActions from '../actions/spec-actions';

export default async function({getState, dispatch}, path, oprn, toolData, paramData) {
	console.log(chalk.gray('Adding tool...'));

	// validate parameter
	const operation = normalizeOperation(oprn);
	const security = normalizeSecurity(toolData.security);
	paramData = normalizeParams(paramData);

	await ensureSpecFileExists();

	// load spec
	const fileStr = await readFile(config.specFileName);
	const json = JSON.parse(minifyJSON(fileStr));

	ensureToolNotExists(json, path, operation);

	// add auth
	if (security == 'basic') {
		json.securityDefinitions = json.securityDefinitions || {};
		json.securityDefinitions.basic_auth = json.securityDefinitions.basic_auth
			|| {'type': 'basic'};
	}

	// add tool
	json.paths = json.paths || {};
	json.paths[path] = json.paths[path] || {};
	json.paths[path][operation] = {
		"x-tb-name": toolNameFromEndpoint({path:path, operation:operation}),
		"operationId": generateOperationId(),
		"security": security == 'basic' ? [{'basic_auth': []}] : [],
		"parameters": [],
		"responses": {
			"200": {
				"description": "Results"
			}
		},
		"x-tb-actionLabel": operation == 'get' ? 'Get' : 'Submit',
		"x-tb-color": generateColor(),
		"x-tb-needsConfirm": false,
		"x-tb-needsNotificationPermission": false
	};
	paramData.forEach(perData => {
		let param = {
			"name": perData.name,
			"in": perData.in,
			"required": true,
			"type": "string",
			"x-tb-fieldLabel": perData.name,
			"x-tb-fieldPlaceholder": "",
			"x-tb-fieldType": perData.field
		};
		if (perData.field == 'select') {
			param = {...param,
				"enum": ["Value1", "Value2"],
				"x-tb-fieldEnumLabel": ["Option1", "Option2"],
			}
		}
		json.paths[path][operation].parameters.push(param);
	});

	// save spec
	dispatch(specActions.save(json));

	console.log(chalk.green(`Tool added for ${path}`));
}

function normalizeOperation(operation) {
	operation = operation || 'get';
	operation = operation.toLowerCase();
	return operation;
}

function normalizeSecurity(operation) {
	operation = operation || 'none';
	operation = operation.toLowerCase();
	return operation;
}

function normalizeFieldType(param) {
	param = param || 'text';
	operation = operation.toLowerCase();
	return operation;
}

function normalizeParams(paramData) {
	paramData.forEach(perData => {
		perData.field = normalizeFieldType(perData.field);
		if ( ! perData.field) {
			throw {message: errors.ERR_ADD_MISSING_PARAM_FIELD};
		}
		else if ( ! perData.name) {
			throw {message: errors.ERR_ADD_MISSING_PARAM_NAME};
		}
		else if ( ! perData.in) {
			throw {message: errors.ERR_ADD_MISSING_PARAM_IN};
		}
	});
	return paramData;
}

async function ensureSpecFileExists() {
	const exists = await existFile(config.specFileName);
	if ( ! exists) {
		throw {message: errors.ERR_ADD_SPEC_NOT_EXISTS};
	}
}

function ensureToolNotExists(json, path, operation) {
	if (json.paths && json.paths[path] && json.paths[path][operation]) {
		throw {message: errors.ERR_ADD_TOOL_EXISTS};
	}
}

function generateOperationId() {
	var text = "";
	var possible = "abcdefghijklmnopqrstuvwxyz";

	for( var i=0; i < 8; i++ ) {
		text += possible.charAt(Math.floor(Math.random() * possible.length));
	}

	return text;
}

function generateColor() {
	const colorNum = Math.floor(Math.random() * 5);
	switch (colorNum) {
		case 0: return "red";
		case 1: return "blue";
		case 2: return "purple";
		case 3: return "green";
		case 4:
		default:
			return "orange";
	}
}
<commit_msg>Add: fixed normalize FieldType<commit_after>import chalk from 'chalk';
import URI from 'urijs';
import config from '../config';
import * as errors from '../errors';

import { toolNameFromEndpoint } from '../libs/consume-openapi';
import { existFile, readFile, writeFile } from '../libs/file';
import { quietParse, minifyJSON } from '../libs/json';
import * as specActions from '../actions/spec-actions';

export default async function({getState, dispatch}, path, oprn, toolData, paramData) {
	console.log(chalk.gray('Adding tool...'));

	// validate parameter
	const operation = normalizeOperation(oprn);
	const security = normalizeSecurity(toolData.security);
	paramData = normalizeParams(paramData);

	await ensureSpecFileExists();

	// load spec
	const fileStr = await readFile(config.specFileName);
	const json = JSON.parse(minifyJSON(fileStr));

	ensureToolNotExists(json, path, operation);

	// add auth
	if (security == 'basic') {
		json.securityDefinitions = json.securityDefinitions || {};
		json.securityDefinitions.basic_auth = json.securityDefinitions.basic_auth
			|| {'type': 'basic'};
	}

	// add tool
	json.paths = json.paths || {};
	json.paths[path] = json.paths[path] || {};
	json.paths[path][operation] = {
		"x-tb-name": toolNameFromEndpoint({path:path, operation:operation}),
		"operationId": generateOperationId(),
		"security": security == 'basic' ? [{'basic_auth': []}] : [],
		"parameters": [],
		"responses": {
			"200": {
				"description": "Results"
			}
		},
		"x-tb-actionLabel": operation == 'get' ? 'Get' : 'Submit',
		"x-tb-color": generateColor(),
		"x-tb-needsConfirm": false,
		"x-tb-needsNotificationPermission": false
	};
	paramData.forEach(perData => {
		let param = {
			"name": perData.name,
			"in": perData.in,
			"required": true,
			"type": "string",
			"x-tb-fieldLabel": perData.name,
			"x-tb-fieldPlaceholder": "",
			"x-tb-fieldType": perData.field
		};
		if (perData.field == 'select') {
			param = {...param,
				"enum": ["Value1", "Value2"],
				"x-tb-fieldEnumLabel": ["Option1", "Option2"],
			}
		}
		json.paths[path][operation].parameters.push(param);
	});

	// save spec
	dispatch(specActions.save(json));

	console.log(chalk.green(`Tool added for ${path}`));
}

function normalizeOperation(operation) {
	operation = operation || 'get';
	operation = operation.toLowerCase();
	return operation;
}

function normalizeSecurity(security) {
	security = security || 'none';
	security = security.toLowerCase();
	return security;
}

function normalizeFieldType(param) {
	param = param || 'text';
	param = param.toLowerCase();
	return param;
}

function normalizeParams(paramData) {
	paramData.forEach(perData => {
		perData.field = normalizeFieldType(perData.field);
		if ( ! perData.field) {
			throw {message: errors.ERR_ADD_MISSING_PARAM_FIELD};
		}
		else if ( ! perData.name) {
			throw {message: errors.ERR_ADD_MISSING_PARAM_NAME};
		}
		else if ( ! perData.in) {
			throw {message: errors.ERR_ADD_MISSING_PARAM_IN};
		}
	});
	return paramData;
}

async function ensureSpecFileExists() {
	const exists = await existFile(config.specFileName);
	if ( ! exists) {
		throw {message: errors.ERR_ADD_SPEC_NOT_EXISTS};
	}
}

function ensureToolNotExists(json, path, operation) {
	if (json.paths && json.paths[path] && json.paths[path][operation]) {
		throw {message: errors.ERR_ADD_TOOL_EXISTS};
	}
}

function generateOperationId() {
	var text = "";
	var possible = "abcdefghijklmnopqrstuvwxyz";

	for( var i=0; i < 8; i++ ) {
		text += possible.charAt(Math.floor(Math.random() * possible.length));
	}

	return text;
}

function generateColor() {
	const colorNum = Math.floor(Math.random() * 5);
	switch (colorNum) {
		case 0: return "red";
		case 1: return "blue";
		case 2: return "purple";
		case 3: return "green";
		case 4:
		default:
			return "orange";
	}
}
<|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Norwegian Defence Research Establishment / NTNU
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * Created by Fredrik Tørnvall and Håkon Ødegård Løvdal on 26/02/15.
 */
var Main = (function($) {

    // Global variable for holding the interval used to update the panes
    var clickInterval;

    /*
        Private method for setting up the AJAX with the correct CSRF-token
        (must do this to be able to do POST-requests)
    */
    var setupAjax = function() {
        var token = $("input[name='_csrf']").val();
        var header = "X-CSRF-TOKEN";
        $.ajaxSetup({
            beforeSend: function(xhr) {
                xhr.setRequestHeader(header, token);
            }
        });
        console.log("[Debug][Main] Successfully set up AJAX")
    }

    /*
        Global, generic AJAX-function for all AJAX-requests across the complete page.
        Takes in five arguments, defining the request. All urls are appended to '/api/'
     */
    var ajax = function(url, error, success, httpMethod, dataType) {
        $.ajax({
            url: "/api/" + url,
            dataType: dataType,
            error: error,
            success: success,
            type: httpMethod
        })
    };

    // TODO: For now, it only updates when in main-pane. Needs to change in topic pane later on
    // Updates all the subscriber counters on the page (both the main-pane and the topics-pane
    var updateSubscribers = function(subscribers) {
        $('.total-subscribers').each(function() {
            if ($(this).val() != subscribers) {
                $(this).text(subscribers)
            }
        });
    }

    var error = function(xhr, status, error) {
        console.error("[Error][Main] in Ajax with the following callback [status: " + xhr.status +  " readyState: " + xhr.readyState + " responseText: " + xhr.responseText + "]")
    }

    var refresh = function(response) {
        updateSubscribers(response.subscribers)
        console.log("[Debug][Main]" + JSON.stringify(response))
    }

    return {
        ajax: ajax,
        init: function() {
            setupAjax()
            $(".nav-tabs").on("click", "a", function(e){
                clearInterval(clickInterval)
                var clickedElement = $(this).attr("href")
                var updateInterval = $('#settings-update-interval').val() * 1000

                if (clickedElement === "#main") {
                    ajax(clickedElement.substring(1), error, refresh, "GET", "json")
                    clickInterval = setInterval( function() {
                        ajax(clickedElement.substring(1), error, refresh, "GET", "json")
                    }, updateInterval);
                } else if(clickedElement === "#topics") {
                    ajax(clickedElement.substring(1), Topics.error, Topics.refresh, "GET", "json")
                    clickInterval = setInterval( function() {
                        ajax(clickedElement.substring(1), Topics.error, Topics.refresh, "GET", "json")
                    }, updateInterval);
                } else if (clickedElement === "#stats") {
                    ajax(clickedElement.substring(1), Stats.error, Stats.refresh, "GET", "json")
                    clickInterval = setInterval( function() {
                        ajax(clickedElement.substring(1), Stats.error, Stats.refresh, "GET", "json")
                    }, updateInterval);
                } else if (clickedElement === "#config") {
                    ajax(clickedElement.substring(1), Config.error, Config.refresh, "GET")
                    clickInterval = setInterval( function() {
                        ajax(clickedElement.substring(1), Config.error, Config.refresh, "GET")
                    }, updateInterval);
                } else if (clickedElement === "#log") {
                    ajax(clickedElement.substring(1), Logs.error, Logs.refresh, "GET")
                    clickInterval = setInterval( function() {
                        ajax(clickedElement.substring(1), Logs.error, Logs.refresh, "GET")
                    }, updateInterval);
                } else {
                    console.error("[Error][Main] Unknown nav-tab clicked, this should not happen!")
                }
            });

            if ($('#main').length) {
                clickInterval = setInterval( function() {
                    ajax("main", error, refresh, "GET", "json")
                }, 2000);
            }
        }
    }

})(jQuery)

$(document).ready(function(){
    Main.init()
    Topics.init()
    Config.init()
});

<commit_msg>added the log tab<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Norwegian Defence Research Establishment / NTNU
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * Created by Fredrik Tørnvall and Håkon Ødegård Løvdal on 26/02/15.
 */
var Main = (function($) {

    // Global variable for holding the interval used to update the panes
    var clickInterval;

    /*
        Private method for setting up the AJAX with the correct CSRF-token
        (must do this to be able to do POST-requests)
    */
    var setupAjax = function() {
        var token = $("input[name='_csrf']").val();
        var header = "X-CSRF-TOKEN";
        $.ajaxSetup({
            beforeSend: function(xhr) {
                xhr.setRequestHeader(header, token);
            }
        });
        console.log("[Debug][Main] Successfully set up AJAX")
    }

    /*
        Global, generic AJAX-function for all AJAX-requests across the complete page.
        Takes in five arguments, defining the request. All urls are appended to '/api/'
     */
    var ajax = function(url, error, success, httpMethod, dataType) {
        $.ajax({
            url: "/api/" + url,
            dataType: dataType,
            error: error,
            success: success,
            type: httpMethod
        })
    };

    // TODO: For now, it only updates when in main-pane. Needs to change in topic pane later on
    // Updates all the subscriber counters on the page (both the main-pane and the topics-pane
    var updateSubscribers = function(subscribers) {
        $('.total-subscribers').each(function() {
            if ($(this).val() != subscribers) {
                $(this).text(subscribers)
            }
        });
    }

    var error = function(xhr, status, error) {
        console.error("[Error][Main] in Ajax with the following callback [status: " + xhr.status +  " readyState: " + xhr.readyState + " responseText: " + xhr.responseText + "]")
    }

    var refresh = function(response) {
        updateSubscribers(response.subscribers)
        console.log("[Debug][Main]" + JSON.stringify(response))
    }

    return {
        ajax: ajax,
        init: function() {
            setupAjax()
            $(".nav-tabs").on("click", "a", function(e){
                clearInterval(clickInterval)
                var clickedElement = $(this).attr("href")
                var updateInterval = $('#settings-update-interval').val() * 1000

                if (clickedElement === "#main") {
                    ajax(clickedElement.substring(1), error, refresh, "GET", "json")
                    clickInterval = setInterval( function() {
                        ajax(clickedElement.substring(1), error, refresh, "GET", "json")
                    }, updateInterval);
                } else if(clickedElement === "#topics") {
                    ajax(clickedElement.substring(1), Topics.error, Topics.refresh, "GET", "json")
                    clickInterval = setInterval( function() {
                        ajax(clickedElement.substring(1), Topics.error, Topics.refresh, "GET", "json")
                    }, updateInterval);
                } else if (clickedElement === "#stats") {
                    ajax(clickedElement.substring(1), Stats.error, Stats.refresh, "GET", "json")
                    clickInterval = setInterval( function() {
                        ajax(clickedElement.substring(1), Stats.error, Stats.refresh, "GET", "json")
                    }, updateInterval);
                } else if (clickedElement === "#config") {
                    ajax(clickedElement.substring(1), Config.error, Config.refresh, "GET")
                    clickInterval = setInterval( function() {
                        ajax(clickedElement.substring(1), Config.error, Config.refresh, "GET")
                    }, updateInterval);
                } else if (clickedElement === "#log") {
                    ajax(Logs.url(), Logs.error, Logs.refresh, "GET")
                    clickInterval = setInterval( function() {
                        ajax(Logs.url(), Logs.error, Logs.refresh, "GET")
                    }, updateInterval);
                } else {
                    console.error("[Error][Main] Unknown nav-tab clicked, this should not happen!")
                }
            });

            if ($('#main').length) {
                clickInterval = setInterval( function() {
                    ajax("main", error, refresh, "GET", "json")
                }, 2000);
            }
        },
        clearIntervalForTab: function() {
            clearInterval(clickInterval)
        }
    }

})(jQuery)

$(document).ready(function(){
    Main.init()
    Topics.init()
    Config.init()
    Logs.init()
});

<|endoftext|>


<commit_before>
export function getLevelConfiguration() {

  return {
    numberOfLevels: 20,
    breakEveryXLevels: 5,
    breakDuration: 5 * 60, // 5 minutes in seconds
    levelDuration: 15 * 60, // 15 minutes in seconds
    firstBigBlind: 200
  };
}
<commit_msg>prevent the last level from being a break<commit_after>
// Get the total number of levels including breaks
function getTotalNumberOfLevels(numberOfPlayLevels, breakAfterXPlayLevels) {
  const lastLevelIsABreak = numberOfPlayLevels % breakAfterXPlayLevels === 0;
  let numberOfLevels = Math.floor(numberOfPlayLevels + (numberOfPlayLevels / breakAfterXPlayLevels));

  // We should never end on a break
  if (lastLevelIsABreak) {
    numberOfLevels -= 1;
  }

  return numberOfLevels;
}

export function getLevelConfiguration() {
  const numberOfPlayLevels = 28;
  const breakAfterXPlayLevels = 4;

  return {
    numberOfLevels: getTotalNumberOfLevels(numberOfPlayLevels, breakAfterXPlayLevels),
    breakEveryXLevels: breakAfterXPlayLevels + 1,
    breakDuration: 5 * 60, // 5 minutes in seconds
    levelDuration: 15 * 60, // 15 minutes in seconds
    firstBigBlind: 200
  };
}
<|endoftext|>


<commit_before>const ExtractTextPlugin = require('extract-text-webpack-plugin')
const autoprefixer = require('autoprefixer')

const createInlineableResourcesRule = options => {
  return {
    test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
    loader: require.resolve('url-loader'),
    options: {
      limit: 10000,
      name: options.outputPath + '[name].[hash:8].[ext]',
    },
  }
}

const createJavascriptRule = isDevelopment => {
  if (isDevelopment) {
    return {
      test: /\.js$/,
      exclude: /(node_modules)/,
      use: {
        loader: 'babel-loader',
        options: {
          babelrc: false,
          presets: [require.resolve('babel-preset-react-app'), 'react-hmre'],
          compact: true,
        },
      },
    }
  }

  return {
    test: /\.js$/,
    exclude: /(node_modules)/,
    use: {
      loader: 'babel-loader',
      options: {
        babelrc: false,
        presets: [require.resolve('babel-preset-react-app')],
        cacheDirectory: true,
      },
    },
  }
}

const createStyleLoader = isDevelopment => {
  return {
    loader: require.resolve('style-loader'),
    options: {
      hmr: isDevelopment,
    },
  }
}

const createCssLoader = options => {
  return {
    loader: require.resolve('css-loader'),
    options: {
      importLoaders: 1,
      minimize: true,
      sourceMap: options.createSourceMap,
    },
  }
}

const createPostCssLoader = options => {
  return {
    loader: require.resolve('postcss-loader'),
    options: {
      // Necessary for external CSS imports to work
      // https://github.com/facebookincubator/create-react-app/issues/2677
      ident: 'postcss',
      plugins: () => [
        // require('postcss-flexbugs-fixes'),
        autoprefixer({
          browsers: options.autoprefixerBrowser,
          flexbox: 'no-2009',
        }),
      ],
      sourceMap: options.createSourceMap,
    },
  }
}

const createCssRule = (options, isDevelopment) => {
  return {
    test: /\.css$/,
    loader: ExtractTextPlugin.extract({
      fallback: createStyleLoader(isDevelopment),
      use: [createCssLoader(options), createPostCssLoader(options)],
    }),
  }
}

const createStylusRule = (options, isDevelopment) => {
  return {
    test: /\.styl$/,
    loader: ExtractTextPlugin.extract({
      fallback: createStyleLoader(isDevelopment),
      use: [
        createCssLoader(options),
        createPostCssLoader(options),
        {
          loader: require.resolve('stylus-loader'),
        },
      ],
    }),
  }
}

const createFallbackRule = options => {
  return {
    loader: require.resolve('file-loader'),
    exclude: [/\.js$/, /\.html$/, /\.json$/],
    options: {
      name: options.outputPath + '[name].[hash:8].[ext]',
    },
  }
}

const createRules = (options, isDevelopment) => {
  return [
    {
      oneOf: [
        createInlineableResourcesRule(options),
        createJavascriptRule(isDevelopment),
        createCssRule(options),
        createStylusRule(options),
        createFallbackRule(options),
        // ** STOP ** Are you adding a new loader?
        // Make sure to add the new loader(s) before the "file" loader.
      ],
    },
  ]
}

module.exports = exports = createRules
<commit_msg>change resource path to 'res'<commit_after>const ExtractTextPlugin = require('extract-text-webpack-plugin')
const autoprefixer = require('autoprefixer')

const createInlineableResourcesRule = options => {
  return {
    test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
    loader: require.resolve('url-loader'),
    options: {
      limit: 10000,
      name: 'res/' + '[name].[hash:8].[ext]',
    },
  }
}

const createJavascriptRule = isDevelopment => {
  if (isDevelopment) {
    return {
      test: /\.js$/,
      exclude: /(node_modules)/,
      use: {
        loader: 'babel-loader',
        options: {
          babelrc: false,
          presets: [require.resolve('babel-preset-react-app'), 'react-hmre'],
          compact: true,
        },
      },
    }
  }

  return {
    test: /\.js$/,
    exclude: /(node_modules)/,
    use: {
      loader: 'babel-loader',
      options: {
        babelrc: false,
        presets: [require.resolve('babel-preset-react-app')],
        cacheDirectory: true,
      },
    },
  }
}

const createStyleLoader = isDevelopment => {
  return {
    loader: require.resolve('style-loader'),
    options: {
      hmr: isDevelopment,
    },
  }
}

const createCssLoader = options => {
  return {
    loader: require.resolve('css-loader'),
    options: {
      importLoaders: 1,
      minimize: true,
      sourceMap: options.createSourceMap,
    },
  }
}

const createPostCssLoader = options => {
  return {
    loader: require.resolve('postcss-loader'),
    options: {
      // Necessary for external CSS imports to work
      // https://github.com/facebookincubator/create-react-app/issues/2677
      ident: 'postcss',
      plugins: () => [
        // require('postcss-flexbugs-fixes'),
        autoprefixer({
          browsers: options.autoprefixerBrowser,
          flexbox: 'no-2009',
        }),
      ],
      sourceMap: options.createSourceMap,
    },
  }
}

const createCssRule = (options, isDevelopment) => {
  return {
    test: /\.css$/,
    loader: ExtractTextPlugin.extract({
      fallback: createStyleLoader(isDevelopment),
      use: [createCssLoader(options), createPostCssLoader(options)],
    }),
  }
}

const createStylusRule = (options, isDevelopment) => {
  return {
    test: /\.styl$/,
    loader: ExtractTextPlugin.extract({
      fallback: createStyleLoader(isDevelopment),
      use: [
        createCssLoader(options),
        createPostCssLoader(options),
        {
          loader: require.resolve('stylus-loader'),
        },
      ],
    }),
  }
}

const createFallbackRule = options => {
  return {
    loader: require.resolve('file-loader'),
    exclude: [/\.js$/, /\.html$/, /\.json$/],
    options: {
      name: 'res/' + '[name].[hash:8].[ext]',
    },
  }
}

const createRules = (options, isDevelopment) => {
  return [
    {
      oneOf: [
        createInlineableResourcesRule(options),
        createJavascriptRule(isDevelopment),
        createCssRule(options),
        createStylusRule(options),
        createFallbackRule(options),
        // ** STOP ** Are you adding a new loader?
        // Make sure to add the new loader(s) before the "file" loader.
      ],
    },
  ]
}

module.exports = exports = createRules
<|endoftext|>


<commit_before>module.config.specs = def(
  [
  ],

  function () {
    var modulator = function (type, namespace, path, mapper) {
      return {
        type: type,
        namespace: namespace,
        modulator: namespace + '.Modulator',
        compiler: namespace + '.Compiler',
        path: path,
        mapper: mapper
      }
    };

    var source = function (type /*, args */) {
      return {
        type: type,
        args: Array.prototype.slice.call(arguments, 1)
      };
    };

    return {
      modulator: modulator,
      source: source
    };
  }
);
<commit_msg>Error handling.<commit_after>module.config.specs = def(
  [
  ],

  function () {
    var modulator = function (type, namespace, path, mapper) {
      return {
        type: type,
        namespace: namespace,
        modulator: namespace + '.Modulator',
        compiler: namespace + '.Compiler',
        path: path,
        mapper: mapper
      };
    };

    var source = function (type /*, args */) {
      return {
        type: type,
        args: Array.prototype.slice.call(arguments, 1)
      };
    };

    return {
      modulator: modulator,
      source: source
    };
  }
);
<|endoftext|>


<commit_before>/*
Copyright © 2015 by Coursera
Licensed under the Apache License 2.0, seen https://github.com/coursera/react-imgix/blob/main/LICENSE

Minor syntax modifications have been made
*/

var Base64 = require("js-base64").Base64;
const PACKAGE_VERSION = require("../package.json").version;
import extractQueryParams from "./extractQueryParams";

// @see https://www.imgix.com/docs/reference
var PARAM_EXPANSION = Object.freeze({
  // Adjustment
  brightness: "bri",
  contrast: "con",
  exposure: "exp",
  gamma: "gam",
  highlights: "high",
  hue: "hue",
  invert: "invert",
  saturation: "sat",
  shaddows: "shad",
  sharpness: "sharp",
  "unsharp-mask": "usm",
  "unsharp-radius": "usmrad",
  vibrance: "vib",

  // Automatic
  "auto-features": "auto",

  // Background
  "background-color": "bg",

  // Blend
  blend: "blend",
  "blend-mode": "bm",
  "blend-align": "ba",
  "blend-alpha": "balph",
  "blend-padding": "bp",
  "blend-width": "bw",
  "blend-height": "bh",
  "blend-fit": "bf",
  "blend-crop": "bc",
  "blend-size": "bs",
  "blend-x": "bx",
  "blend-y": "by",

  // Border & Padding
  border: "border",
  padding: "pad",

  // Face Detection
  "face-index": "faceindex",
  "face-padding": "facepad",
  faces: "faces",

  // Format
  "chroma-subsampling": "chromasub",
  "color-quantization": "colorquant",
  download: "dl",
  DPI: "dpi",
  format: "fm",
  "lossless-compression": "lossless",
  quality: "q",

  // Mask
  "mask-image": "mask",

  // Mask
  "noise-blur": "nr",
  "noise-sharpen": "nrs",

  // Palette n/a
  // PDF n/a
  // Pixel Density n/a

  // Rotation
  "flip-direction": "flip",
  orientation: "or",
  "rotation-angle": "rot",

  // Size
  "crop-mode": "crop",
  "fit-mode": "fit",
  "image-height": "h",
  "image-width": "w",

  // Stylize
  blurring: "blur",
  halftone: "htn",
  monotone: "mono",
  pixelate: "px",
  "sepia-tone": "sepia",

  // Trim TODO
  // Watermark TODO

  // Extra
  height: "h",
  width: "w",
});

var DEFAULT_OPTIONS = Object.freeze({
  auto: "format", // http://www.imgix.com/docs/reference/automatic#param-auto
});

/**
 * Construct a URL for an image with an Imgix proxy, expanding image options
 * per the [API reference docs](https://www.imgix.com/docs/reference).
 * @param  {String} src         src of raw image
 * @param  {Object} longOptions map of image API options, in long or short form per expansion rules
 * @return {String}             URL of image src transformed by Imgix
 */
function constructUrl(src, longOptions) {
  if (!src) {
    return "";
  }

  const keys = Object.keys(longOptions);
  const keysLength = keys.length;
  let url = src + "?";
  for (let i = 0; i < keysLength; i++) {
    let key = keys[i];
    let val = longOptions[key];

    if (PARAM_EXPANSION[key]) {
      key = PARAM_EXPANSION[key];
    } else {
      key = encodeURIComponent(key);
    }

    if (key.substr(-2) === "64") {
      val = Base64.encodeURI(val);
    }

    url += key + "=" + encodeURIComponent(val) + "&";
  }

  return url.slice(0, -1);
}

function buildURLPublic(src, imgixParams = {}, options = {}) {
  const { disableLibraryParam } = options;

  const [rawSrc, params] = extractQueryParams(src);

  return constructUrl(
    rawSrc,
    Object.assign(
      {},
      params,
      imgixParams,
      disableLibraryParam ? {} : { ixlib: `react-${PACKAGE_VERSION}` }
    )
  );
}

export default constructUrl;

export { buildURLPublic };
<commit_msg>chore: fix license link to coursera<commit_after>/*
Copyright © 2015 by Coursera
Licensed under the Apache License 2.0, seen https://github.com/coursera/react-imgix/blob/master/LICENSE

Minor syntax modifications have been made
*/

var Base64 = require("js-base64").Base64;
const PACKAGE_VERSION = require("../package.json").version;
import extractQueryParams from "./extractQueryParams";

// @see https://www.imgix.com/docs/reference
var PARAM_EXPANSION = Object.freeze({
  // Adjustment
  brightness: "bri",
  contrast: "con",
  exposure: "exp",
  gamma: "gam",
  highlights: "high",
  hue: "hue",
  invert: "invert",
  saturation: "sat",
  shaddows: "shad",
  sharpness: "sharp",
  "unsharp-mask": "usm",
  "unsharp-radius": "usmrad",
  vibrance: "vib",

  // Automatic
  "auto-features": "auto",

  // Background
  "background-color": "bg",

  // Blend
  blend: "blend",
  "blend-mode": "bm",
  "blend-align": "ba",
  "blend-alpha": "balph",
  "blend-padding": "bp",
  "blend-width": "bw",
  "blend-height": "bh",
  "blend-fit": "bf",
  "blend-crop": "bc",
  "blend-size": "bs",
  "blend-x": "bx",
  "blend-y": "by",

  // Border & Padding
  border: "border",
  padding: "pad",

  // Face Detection
  "face-index": "faceindex",
  "face-padding": "facepad",
  faces: "faces",

  // Format
  "chroma-subsampling": "chromasub",
  "color-quantization": "colorquant",
  download: "dl",
  DPI: "dpi",
  format: "fm",
  "lossless-compression": "lossless",
  quality: "q",

  // Mask
  "mask-image": "mask",

  // Mask
  "noise-blur": "nr",
  "noise-sharpen": "nrs",

  // Palette n/a
  // PDF n/a
  // Pixel Density n/a

  // Rotation
  "flip-direction": "flip",
  orientation: "or",
  "rotation-angle": "rot",

  // Size
  "crop-mode": "crop",
  "fit-mode": "fit",
  "image-height": "h",
  "image-width": "w",

  // Stylize
  blurring: "blur",
  halftone: "htn",
  monotone: "mono",
  pixelate: "px",
  "sepia-tone": "sepia",

  // Trim TODO
  // Watermark TODO

  // Extra
  height: "h",
  width: "w",
});

var DEFAULT_OPTIONS = Object.freeze({
  auto: "format", // http://www.imgix.com/docs/reference/automatic#param-auto
});

/**
 * Construct a URL for an image with an Imgix proxy, expanding image options
 * per the [API reference docs](https://www.imgix.com/docs/reference).
 * @param  {String} src         src of raw image
 * @param  {Object} longOptions map of image API options, in long or short form per expansion rules
 * @return {String}             URL of image src transformed by Imgix
 */
function constructUrl(src, longOptions) {
  if (!src) {
    return "";
  }

  const keys = Object.keys(longOptions);
  const keysLength = keys.length;
  let url = src + "?";
  for (let i = 0; i < keysLength; i++) {
    let key = keys[i];
    let val = longOptions[key];

    if (PARAM_EXPANSION[key]) {
      key = PARAM_EXPANSION[key];
    } else {
      key = encodeURIComponent(key);
    }

    if (key.substr(-2) === "64") {
      val = Base64.encodeURI(val);
    }

    url += key + "=" + encodeURIComponent(val) + "&";
  }

  return url.slice(0, -1);
}

function buildURLPublic(src, imgixParams = {}, options = {}) {
  const { disableLibraryParam } = options;

  const [rawSrc, params] = extractQueryParams(src);

  return constructUrl(
    rawSrc,
    Object.assign(
      {},
      params,
      imgixParams,
      disableLibraryParam ? {} : { ixlib: `react-${PACKAGE_VERSION}` }
    )
  );
}

export default constructUrl;

export { buildURLPublic };
<|endoftext|>


<commit_before>import { UICorePlugin, Events, Styler, template, version } from 'clappr'

import pluginStyle from './public/context_menu.scss'
import templateHtml from './public/context_menu.html'

export default class ContextMenuPlugin extends UICorePlugin {
  get name() { return 'context_menu' }

  get attributes() { return { class: 'context-menu' } }

  get template() { return template(templateHtml) }

  get loopEnable() { return this.core.activePlayback.el.loop }

  get playerVersion() {
    return {
      label: `Clappr Player v${version}`,
      name: 'playerVersion',
      noAction: true,
    }
  }

  get copyURL() {
    return {
      label: 'Copy URL',
      name: 'copyURL',
    }
  }

  get copyURLCurrentTime() {
    return {
      label: 'Copy URL on current time',
      name: 'copyURLCurrentTime',
    }
  }

  get loop() {
    return {
      label: 'Loop: ',
      name: 'loop',
      class: this.core.options.loop ? 'on' : 'off',
    }
  }

  get events() {
    const events = {
      'click [data-copyURL]': 'onCopyURL',
      'click [data-copyURLCurrentTime]': 'onCopyURLCurrentTime',
      'click [data-loop]': 'onToggleLoop',
    }
    this.extraOptions && this.extraOptions.forEach(item => {
      if (typeof item.callback === 'function') {
        const callbackName = `${item.name}Callback`
        this[callbackName] = item.callback
        events[`click [data-${item.name}]`] = callbackName
      }
    })
    return events
  }

  constructor(core) {
    super(core)
    this.extraOptions = this.options.contextMenu && this.options.contextMenu.extraOptions || []
    this.delegateEvents(this.events)
    this.bindEvents()
  }

  bindEvents() {
    const coreEventListenerData = [
      { object: this.core, event: Events.CORE_ACTIVE_CONTAINER_CHANGED, callback: this.containerChanged },
      { object: this.core, event: Events.CORE_RESIZE, callback: this.registerPlayerResize },
    ]

    coreEventListenerData.forEach(item => this.stopListening(item.object, item.event, item.callback))
    coreEventListenerData.forEach(item => this.listenTo(item.object, item.event, item.callback))

    this.bindCustomEvents()
  }

  bindContainerEvents() {
    const containerEventListenerData = [
      { object: this.container, event: Events.CONTAINER_CONTEXTMENU, callback: this.toggleContextMenu },
      { object: this.container, event: Events.CONTAINER_CLICK, callback: this.hide },
    ]

    if (this.container) {
      containerEventListenerData.forEach(item => this.stopListening(item.object, item.event, item.callback))
      containerEventListenerData.forEach(item => this.listenTo(item.object, item.event, item.callback))
    }
  }

  bindCustomEvents() {
    $('body').off('click', this.hide.bind(this))
    $('body').on('click', this.hide.bind(this))
  }

  destroy() {
    $('body').off('click', this.hide.bind(this))
    this.stopListening()
    super.destroy()
  }

  registerPlayerResize(size) {
    if (!size.width || typeof size.width !== 'number') return
    this.playerSize = size
  }

  containerChanged() {
    this.container = this.core.activeContainer
    this.bindContainerEvents()
  }

  toggleContextMenu(event) {
    event.preventDefault()
    this.show(event.offsetY, event.offsetX)
  }

  show(top, left) {
    !this.playerElement && this.calculateContextMenuLimit()
    const finalTop = top > this.maxHeight ? this.maxHeight : top
    const finalLeft = left > this.maxWidth ? this.maxWidth : left
    this.hide()
    this.$el.css({ top: finalTop, left: finalLeft })
    this.$el.show()
  }

  calculateContextMenuLimit() {
    this.maxWidth = this.playerSize && this.playerSize.width - 160
    this.maxHeight = this.playerSize && this.playerSize.height - 200
  }

  hide() {
    this.$el.hide()
  }

  copyToClipboard(value, $el) {
    if (!$el) return

    const $copyTextarea = $('<textarea class="copytextarea"/>')
    $copyTextarea.text(value)
    $el.append($copyTextarea[0])

    const copyTextarea = this.el.querySelector('.context-menu .copytextarea')
    copyTextarea.select()

    try {
      document.execCommand('copy')
    } catch (err) {
      throw Error(err)
    }

    $copyTextarea.remove()
  }

  onCopyURL() {
    this.copyToClipboard(window.location.href, this.$el)
  }

  onCopyURLCurrentTime() {
    let url = window.location.href
    const currentTime = Math.floor(this.container.getCurrentTime())

    /* eslint-disable no-useless-escape */
    if (window.location.search === '') { // if dont exist any query string
      url += `?t=${currentTime}`
    } else if (window.location.search.split(/[\?=&]/g).indexOf('t') === -1) { // if exist query string but not the resume at
      url += `&t=${currentTime}`
    } else if (window.location.search.split(/[\?=&]/g).indexOf('t') !== -1) { // if exist resume query string
      const search = window.location.search.split(/[\?&]/g)
      const resumeAtQueryString = search.find(item => item.includes('t='))
      const newQueryString = window.location.search.replace(resumeAtQueryString, `t=${currentTime}`)
      url = `${url.replace(window.location.search, '')}${newQueryString}`
    }
    /* eslint-enable no-useless-escape */

    this.copyToClipboard(url, this.$el)
  }

  onToggleLoop() {
    this.core.options.loop = !this.loopEnable
    this.core.activePlayback.el.loop = !this.loopEnable
    this.$el.find('[data-loop]').toggleClass('on', this.loopEnable)
    this.$el.find('[data-loop]').toggleClass('off', !this.loopEnable)
  }

  appendExtraOptions(item) {
    if (!item.callback || typeof item.callback !== 'function') item.noAction = true
    this.menuOptions.unshift(item)
  }

  addCustomStyle() {
    const styles = this.options.contextMenu && this.options.contextMenu.customStyle
    if (styles) {
      this.$el.css(styles.container)
      this.$el.find('.context-menu-list').css(styles.list)
      this.$el.find('.context-menu-list-item').css(styles.items)
    }
  }

  sanitizeCustomizedItems() {
    const customMenuItems = []
    this.options.contextMenu.menuItems.forEach(item => {
      typeof this[item] !== 'undefined' && customMenuItems.push(this[item])
    })
    return customMenuItems
  }

  render() {
    this.customMenuItems = this.options.contextMenu && this.options.contextMenu.menuItems && this.sanitizeCustomizedItems()
    this.menuOptions = this.customMenuItems && this.customMenuItems.length > 0
      ? this.customMenuItems
      : [this.copyURL, this.copyURLCurrentTime, this.loop, this.playerVersion]
    const extraOptions = this.options.contextMenu && this.options.contextMenu.extraOptions
    extraOptions && extraOptions.forEach(item => this.appendExtraOptions(item))
    this.$el.html(this.template({ options: this.menuOptions }))
    this.$el.append(Styler.getStyleFor(pluginStyle))
    this.core.$el[0].append(this.$el[0])
    this.hide()
    this.disable()
    this.addCustomStyle()
    return this
  }
}
<commit_msg>refactor(src): reorganize logic called on constructor<commit_after>import { UICorePlugin, Events, Styler, template, version } from 'clappr'

import pluginStyle from './public/context_menu.scss'
import templateHtml from './public/context_menu.html'

export default class ContextMenuPlugin extends UICorePlugin {
  get name() { return 'context_menu' }

  get attributes() { return { class: 'context-menu' } }

  get template() { return template(templateHtml) }

  get loopEnable() { return this.core.activePlayback.el.loop }

  get playerVersion() {
    return {
      label: `Clappr Player v${version}`,
      name: 'playerVersion',
      noAction: true,
    }
  }

  get copyURL() {
    return {
      label: 'Copy URL',
      name: 'copyURL',
    }
  }

  get copyURLCurrentTime() {
    return {
      label: 'Copy URL on current time',
      name: 'copyURLCurrentTime',
    }
  }

  get loop() {
    return {
      label: 'Loop: ',
      name: 'loop',
      class: this.core.options.loop ? 'on' : 'off',
    }
  }

  get events() {
    const events = {
      'click [data-copyURL]': 'onCopyURL',
      'click [data-copyURLCurrentTime]': 'onCopyURLCurrentTime',
      'click [data-loop]': 'onToggleLoop',
    }
    this.extraOptions && this.extraOptions.forEach(item => {
      if (typeof item.callback === 'function') {
        const callbackName = `${item.name}Callback`
        this[callbackName] = item.callback
        events[`click [data-${item.name}]`] = callbackName
      }
    })
    return events
  }

  constructor(core) {
    super(core)
    this.init()
  }

  init() {
    this.extraOptions = this.options.contextMenu && this.options.contextMenu.extraOptions || []
    this.delegateEvents(this.events)
    this.bindEvents()
  }

  bindEvents() {
    const coreEventListenerData = [
      { object: this.core, event: Events.CORE_ACTIVE_CONTAINER_CHANGED, callback: this.containerChanged },
      { object: this.core, event: Events.CORE_RESIZE, callback: this.registerPlayerResize },
    ]

    coreEventListenerData.forEach(item => this.stopListening(item.object, item.event, item.callback))
    coreEventListenerData.forEach(item => this.listenTo(item.object, item.event, item.callback))

    this.bindCustomEvents()
  }

  bindContainerEvents() {
    const containerEventListenerData = [
      { object: this.container, event: Events.CONTAINER_CONTEXTMENU, callback: this.toggleContextMenu },
      { object: this.container, event: Events.CONTAINER_CLICK, callback: this.hide },
    ]

    if (this.container) {
      containerEventListenerData.forEach(item => this.stopListening(item.object, item.event, item.callback))
      containerEventListenerData.forEach(item => this.listenTo(item.object, item.event, item.callback))
    }
  }

  bindCustomEvents() {
    $('body').off('click', this.hide.bind(this))
    $('body').on('click', this.hide.bind(this))
  }

  destroy() {
    $('body').off('click', this.hide.bind(this))
    this.stopListening()
    super.destroy()
  }

  registerPlayerResize(size) {
    if (!size.width || typeof size.width !== 'number') return
    this.playerSize = size
  }

  containerChanged() {
    this.container = this.core.activeContainer
    this.bindContainerEvents()
  }

  toggleContextMenu(event) {
    event.preventDefault()
    this.show(event.offsetY, event.offsetX)
  }

  show(top, left) {
    !this.playerElement && this.calculateContextMenuLimit()
    const finalTop = top > this.maxHeight ? this.maxHeight : top
    const finalLeft = left > this.maxWidth ? this.maxWidth : left
    this.hide()
    this.$el.css({ top: finalTop, left: finalLeft })
    this.$el.show()
  }

  calculateContextMenuLimit() {
    this.maxWidth = this.playerSize && this.playerSize.width - 160
    this.maxHeight = this.playerSize && this.playerSize.height - 200
  }

  hide() {
    this.$el.hide()
  }

  copyToClipboard(value, $el) {
    if (!$el) return

    const $copyTextarea = $('<textarea class="copytextarea"/>')
    $copyTextarea.text(value)
    $el.append($copyTextarea[0])

    const copyTextarea = this.el.querySelector('.context-menu .copytextarea')
    copyTextarea.select()

    try {
      document.execCommand('copy')
    } catch (err) {
      throw Error(err)
    }

    $copyTextarea.remove()
  }

  onCopyURL() {
    this.copyToClipboard(window.location.href, this.$el)
  }

  onCopyURLCurrentTime() {
    let url = window.location.href
    const currentTime = Math.floor(this.container.getCurrentTime())

    /* eslint-disable no-useless-escape */
    if (window.location.search === '') { // if dont exist any query string
      url += `?t=${currentTime}`
    } else if (window.location.search.split(/[\?=&]/g).indexOf('t') === -1) { // if exist query string but not the resume at
      url += `&t=${currentTime}`
    } else if (window.location.search.split(/[\?=&]/g).indexOf('t') !== -1) { // if exist resume query string
      const search = window.location.search.split(/[\?&]/g)
      const resumeAtQueryString = search.find(item => item.includes('t='))
      const newQueryString = window.location.search.replace(resumeAtQueryString, `t=${currentTime}`)
      url = `${url.replace(window.location.search, '')}${newQueryString}`
    }
    /* eslint-enable no-useless-escape */

    this.copyToClipboard(url, this.$el)
  }

  onToggleLoop() {
    this.core.options.loop = !this.loopEnable
    this.core.activePlayback.el.loop = !this.loopEnable
    this.$el.find('[data-loop]').toggleClass('on', this.loopEnable)
    this.$el.find('[data-loop]').toggleClass('off', !this.loopEnable)
  }

  appendExtraOptions(item) {
    if (!item.callback || typeof item.callback !== 'function') item.noAction = true
    this.menuOptions.unshift(item)
  }

  addCustomStyle() {
    const styles = this.options.contextMenu && this.options.contextMenu.customStyle
    if (styles) {
      this.$el.css(styles.container)
      this.$el.find('.context-menu-list').css(styles.list)
      this.$el.find('.context-menu-list-item').css(styles.items)
    }
  }

  sanitizeCustomizedItems() {
    const customMenuItems = []
    this.options.contextMenu.menuItems.forEach(item => {
      typeof this[item] !== 'undefined' && customMenuItems.push(this[item])
    })
    return customMenuItems
  }

  render() {
    this.customMenuItems = this.options.contextMenu && this.options.contextMenu.menuItems && this.sanitizeCustomizedItems()
    this.menuOptions = this.customMenuItems && this.customMenuItems.length > 0
      ? this.customMenuItems
      : [this.copyURL, this.copyURLCurrentTime, this.loop, this.playerVersion]
    const extraOptions = this.options.contextMenu && this.options.contextMenu.extraOptions
    extraOptions && extraOptions.forEach(item => this.appendExtraOptions(item))
    this.$el.html(this.template({ options: this.menuOptions }))
    this.$el.append(Styler.getStyleFor(pluginStyle))
    this.core.$el[0].append(this.$el[0])
    this.hide()
    this.disable()
    this.addCustomStyle()
    return this
  }
}
<|endoftext|>