<commit_before>angular.module('concertoPanel').directive('wizardParamSetter', ["$compile", "$templateCache", "$uibModal", "$filter", "uiGridConstants", "GridService", "RDocumentation", "$http", "DataTableCollectionService", "TestCollectionService", "ViewTemplateCollectionService", "TestWizardParam", function ($compile, $templateCache, $uibModal, $filter, uiGridConstants, GridService, RDocumentation, $http, DataTableCollectionService, TestCollectionService, ViewTemplateCollectionService, TestWizardParam) {
        return {
            restrict: 'E',
            scope: {
                param: "=",
                output: "=",
                values: "=",
                parent: "=",
                wizardObject: "=",
                underList: "="
            },
            link: function (scope, element, attrs, controllers) {
                scope.RDocumentation = RDocumentation;
                scope.testWizardParamService = TestWizardParam;
                scope.gridService = GridService;
                scope.dataTableCollectionService = DataTableCollectionService;
                scope.testCollectionService = TestCollectionService;
                scope.viewTemplateCollectionService = ViewTemplateCollectionService;
                scope.htmlEditorOptions = Defaults.ckeditorTestContentOptions;

                scope.codeEditorOptions = {
                    lineWrapping: true,
                    lineNumbers: true,
                    mode: 'r',
                    viewportMargin: Infinity,
                    hintOptions: {
                        completeSingle: false,
                        wizardService: RDocumentation
                    },
                    extraKeys: {
                        "F11": function (cm) {
                            cm.setOption("fullScreen", !cm.getOption("fullScreen"));
                        },
                        "Esc": function (cm) {
                            if (cm.getOption("fullScreen"))
                                cm.setOption("fullScreen", false);
                        },
                        "Ctrl-Space": "autocomplete"
                    }
                };
                if (RDocumentation.functionIndex === null) {
                    $http.get(RDocumentation.rCacheDirectory + 'functionIndex.json').success(function (data) {
                        if (data !== null) {
                            RDocumentation.functionIndex = data;
                            scope.codeEditorOptions.hintOptions.functionIndex = data;
                        }
                    });
                } else {
                    scope.codeEditorOptions.hintOptions.functionIndex = RDocumentation.functionIndex;
                }

                scope.mode = "dialog";
                scope.wizardMode = "prod";
                scope.complexSetters = [1, 2, 7, 9, 10, 11];
                scope.isSetterComplex = false;
                scope.title = "";
                scope.summary = "";
                if ("mode" in attrs) {
                    scope.mode = attrs.mode;
                }
                if ("wizardMode" in attrs) {
                    scope.wizardMode = attrs.wizardMode;
                }

                scope.onPrimitiveValueChange = function (value) {
                    scope.output = value;
                    if (scope.wizardMode == "dev" && value != null && !scope.underList) {
                        scope.param.definition.defvalue = value;
                    }
                    if (scope.parent === null)
                        scope.values[scope.param.name] = value;
                };

                scope.updateSeterComplexity = function () {
                    scope.isSetterComplex = scope.complexSetters.indexOf(parseInt(scope.param.type)) !== -1;
                };
                scope.updateTitle = function () {
                    scope.title = scope.testWizardParamService.getSetterTitle(scope.param);
                };
                scope.updateSummary = function () {
                    scope.summary = scope.testWizardParamService.getSetterSummary(scope.param, scope.output);
                };
                scope.listOptions = {
                    //grid virtialization <-> setter directive workaround
                    virtualizationThreshold: 64000,
                    enableFiltering: true,
                    enableGridMenu: true,
                    exporterMenuCsv: false,
                    exporterMenuPdf: false,
                    importerShowMenu: false,
                    data: "output",
                    exporterCsvFilename: 'export.csv',
                    exporterHeaderFilterUseName: true,
                    exporterHeaderFilter: function (name) {
                        return name;
                    },
                    showGridFooter: true,
                    columnDefs: [],
                    onRegisterApi: function (gridApi) {
                        scope.listGridApi = gridApi;
                    },
                    importerDataAddCallback: function (grid, newObjects) {
                        for (var i = 0; i < newObjects.length; i++) {
                            for (var key in newObjects[i]) {
                                for (var j = 0; j < scope.listOptions.columnDefs.length; j++) {
                                    var col = scope.listOptions.columnDefs[j];
                                    if (col.name === key) {
                                        if (col.type == 4) {
                                            newObjects[i][key] = newObjects[i][key].toString();
                                        }
                                        if (col.type == 9 || col.type == 10) {
                                            newObjects[i][key] = angular.fromJson(newObjects[i][key]);
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                        scope.output = scope.output.concat(newObjects);
                    },
                    exporterFieldCallback: function (grid, row, col, value) {
                        if (value !== undefined && value.constructor == Array) {
                            value = angular.toJson(value);
                        }
                        return value;
                    },
                    enableCellEditOnFocus: false
                };
                scope.$watch("output.length", function (newValue) {
                    scope.listOptions.enableFiltering = newValue > 0;
                    if (scope.listGridApi && uiGridConstants.dataChange) {
                        scope.listGridApi.core.notifyDataChange(uiGridConstants.dataChange.COLUMN);
                    }
                });
                scope.getColumnDefs = function (obj, param, parent, output, isGroupField) {
                    if (!obj)
                        return [];
                    if (!isGroupField && obj.type == 9) {
                        var fields = [];
                        for (var i = 0; i < obj.definition.fields.length; i++) {
                            var field = obj.definition.fields[i];
                            var param = "grid.appScope.param.definition.element.definition.fields[" + i + "]";
                            var parent = "grid.appScope.output[grid.appScope.output.indexOf(row.entity)]";
                            var output = "grid.appScope.output[grid.appScope.output.indexOf(row.entity)]." + field.name;
                            var add = scope.getColumnDefs(field, param, parent, output, true);
                            for (var j = 0; j < add.length; j++) {
                                fields.push(add[j]);
                            }
                        }
                        return fields;
                    }

                    return [{
                            type: obj.type,
                            displayName: isGroupField ? obj.label : Trans.TEST_WIZARD_PARAM_LIST_COLUMN_ELEMENT,
                            name: isGroupField ? obj.name : "element",
                            cellTemplate:
                                    "
" +
                                    scope.getParamSetterCellTemplate(param, parent, output) +
                                    "
"
                        }];
                };
                scope.getParamSetterCellTemplate = function (param, parent, output) {
                    var cell = '<wizard-param-setter param="' + param + '" parent="' + parent + '" output="' + output + '" mode="grid" wizard-mode="' + scope.wizardMode + '" under-list="true" values="grid.appScope.values" wizard-object="grid.appScope.wizardObject"></wizard-param-setter>';
                    return cell;
                };
                scope.initializeListColumnDefs = function () {
                    var defs = [];
                    var param = "grid.appScope.param.definition.element";
                    var parent = "grid.appScope.output";
                    var output = "grid.appScope.output[grid.appScope.output.indexOf(row.entity)]";
                    var cd = scope.getColumnDefs(scope.param.definition.element, param, parent, output, false);
                    for (var i = 0; i < cd.length; i++) {
                        defs.push(cd[i]);
                    }

                    defs.push({
                        displayName: "",
                        name: "_action",
                        enableSorting: false,
                        enableFiltering: false,
                        exporterSuppressExport: true,
                        enableCellEdit: false,
                        cellTemplate:
                                "
" +
                                '<button class="btn btn-danger btn-xs" ng-click="grid.appScope.removeElement(grid.appScope.output.indexOf(row.entity));">' + Trans.TEST_WIZARD_PARAM_LIST_ELEMENT_DELETE + '</button>' +
                                "
",
                        width: 100
                    });
                    scope.listOptions.columnDefs = defs;
                };
                scope.launchSetterDialog = function (param, output, parent, values, wizardObject) {
                    var modalInstance = $uibModal.open({
                        templateUrl: Paths.DIALOG_TEMPLATE_ROOT + "param_setter_dialog.html",
                        scope: scope,
                        controller: TestWizardParamSetterController,
                        resolve: {
                            param: function () {
                                return param;
                            },
                            output: function () {
                                return output;
                            },
                            parent: function () {
                                return parent;
                            },
                            values: function () {
                                return values;
                            },
                            wizardObject: function () {
                                return wizardObject;
                            },
                            wizardMode: function () {
                                return scope.wizardMode;
                            }
                        },
                        size: "prc-lg"
                    });
                    modalInstance.result.then(function (result) {
                        scope.output = result;
                    }, function () {
                    });
                };
                scope.moveElementUp = function (index) {
                    scope.output.splice(index + 1, 0, scope.output.splice(index, 1)[0]);
                };
                scope.moveElementDown = function (index) {
                    scope.output.splice(index - 1, 0, scope.output.splice(index, 1)[0]);
                };
                scope.moveFieldUp = function (index) {
                    var params = $filter('orderBy')(scope.param.definition.fields, "order");
                    var paramFound = false;
                    for (var i = 0; i < params.length; i++) {
                        var param = params[i];
                        if (param.order !== i) {
                            param.order = i;
                        }
                        if (paramFound) {
                            param.order--;
                            params[i - 1].order++;
                            paramFound = false;
                        }
                        if (index === i) {
                            paramFound = true;
                        }
                    }
                };
                scope.moveFieldDown = function (index) {
                    var params = $filter('orderBy')(scope.param.definition.fields, "order");
                    for (var i = 0; i < params.length; i++) {
                        var param = params[i];
                        if (param.order !== i) {
                            param.order = i;
                        }
                        if (index === i) {
                            params[i - 1].order++;
                            param.order--;
                        }
                    }
                };
                scope.addElement = function () {
                    if (scope.param.definition.element.type == 4) {
                        scope.output.push({value: null});
                    } else if (scope.param.definition.element.type == 7 || scope.param.definition.element.type == 9) {
                        scope.output.push({});
                    } else if (scope.param.definition.element.type == 10) {
                        scope.output.push([]);
                    } else {
                        scope.output.push({value: null});
                    }
                };
                scope.removeElement = function (index) {
                    scope.output.splice(index, 1);
                };
                scope.removeSelectedElements = function () {
                    var selectedRows = scope.listGridApi.selection.getSelectedRows();
                    for (var i = 0; i < selectedRows.length; i++) {
                        for (var j = 0; j < scope.output.length; j++) {
                            if (scope.output[j] == selectedRows[i]) {
                                scope.removeElement(j);
                                break;
                            }
                        }
                    }
                };
                scope.removeAllElements = function () {
                    scope.output = [];
                };
                scope.$watch('param.type', function (newValue, oldValue) {
                    if (!scope.param)
                        return;
                    if (newValue === null || newValue === undefined)
                        return;

                    switch (parseInt(newValue)) {
                        case 7:
                        case 9:
                            if (scope.output === null || scope.output === undefined || typeof scope.output !== 'object' || scope.output.constructor === Array || newValue != oldValue) {
                                scope.output = {};
                            }
                            break;
                        case 10:
                            if (scope.output === null || scope.output === undefined || scope.output.constructor !== Array || newValue != oldValue) {
                                scope.output = [];
                            }
                            break;
                        default:
                            if (scope.output === undefined || typeof scope.output === 'object' || newValue != oldValue) {
                                scope.output = null;
                            }
                            break;
                    }

                    if (newValue == 10) {
                        scope.initializeListColumnDefs();
                    }
                    scope.updateSeterComplexity();
                    scope.updateTitle();
                    scope.updateSummary();
                    element.html($templateCache.get("type_" + newValue + "_setter.html"));
                    $compile(element.contents())(scope);
                });

                scope.$watch('param.definition.element.type', function (newValue, oldValue) {
                    if (newValue === null || newValue === undefined)
                        return;
                    if (newValue != oldValue) {
                        if (scope.param.type == 10) {
                            if (scope.output === null || scope.output === undefined || scope.output.constructor !== Array || newValue !== oldValue) {
                                scope.output = [];
                            }
                        }
                    }
                });
                scope.$watchCollection('output', function (newValue) {
                    scope.updateSummary();
                });
                scope.$watch("param.definition.defvalue", function (newValue, oldValue) {
                    if (scope.output === null || (scope.wizardMode == "dev" && newValue != null && newValue != undefined && !scope.underList)) {
                        scope.output = newValue;
                    }
                });
            }
        };
    }]);<commit_msg>initial value of wizard params setters of primitive type<commit_after>angular.module('concertoPanel').directive('wizardParamSetter', ["$compile", "$templateCache", "$uibModal", "$filter", "uiGridConstants", "GridService", "RDocumentation", "$http", "DataTableCollectionService", "TestCollectionService", "ViewTemplateCollectionService", "TestWizardParam", function ($compile, $templateCache, $uibModal, $filter, uiGridConstants, GridService, RDocumentation, $http, DataTableCollectionService, TestCollectionService, ViewTemplateCollectionService, TestWizardParam) {
        return {
            restrict: 'E',
            scope: {
                param: "=",
                output: "=",
                values: "=",
                parent: "=",
                wizardObject: "=",
                underList: "="
            },
            link: function (scope, element, attrs, controllers) {
                scope.RDocumentation = RDocumentation;
                scope.testWizardParamService = TestWizardParam;
                scope.gridService = GridService;
                scope.dataTableCollectionService = DataTableCollectionService;
                scope.testCollectionService = TestCollectionService;
                scope.viewTemplateCollectionService = ViewTemplateCollectionService;
                scope.htmlEditorOptions = Defaults.ckeditorTestContentOptions;

                scope.codeEditorOptions = {
                    lineWrapping: true,
                    lineNumbers: true,
                    mode: 'r',
                    viewportMargin: Infinity,
                    hintOptions: {
                        completeSingle: false,
                        wizardService: RDocumentation
                    },
                    extraKeys: {
                        "F11": function (cm) {
                            cm.setOption("fullScreen", !cm.getOption("fullScreen"));
                        },
                        "Esc": function (cm) {
                            if (cm.getOption("fullScreen"))
                                cm.setOption("fullScreen", false);
                        },
                        "Ctrl-Space": "autocomplete"
                    }
                };
                if (RDocumentation.functionIndex === null) {
                    $http.get(RDocumentation.rCacheDirectory + 'functionIndex.json').success(function (data) {
                        if (data !== null) {
                            RDocumentation.functionIndex = data;
                            scope.codeEditorOptions.hintOptions.functionIndex = data;
                        }
                    });
                } else {
                    scope.codeEditorOptions.hintOptions.functionIndex = RDocumentation.functionIndex;
                }

                scope.mode = "dialog";
                scope.wizardMode = "prod";
                scope.complexSetters = [1, 2, 7, 9, 10, 11];
                scope.isSetterComplex = false;
                scope.title = "";
                scope.summary = "";
                if ("mode" in attrs) {
                    scope.mode = attrs.mode;
                }
                if ("wizardMode" in attrs) {
                    scope.wizardMode = attrs.wizardMode;
                }

                scope.onPrimitiveValueChange = function (value) {
                    scope.output = value;
                    if (scope.wizardMode == "dev" && value != null && !scope.underList) {
                        scope.param.definition.defvalue = value;
                    }
                    if (scope.parent === null)
                        scope.values[scope.param.name] = value;
                };

                scope.updateSeterComplexity = function () {
                    scope.isSetterComplex = scope.complexSetters.indexOf(parseInt(scope.param.type)) !== -1;
                };
                scope.updateTitle = function () {
                    scope.title = scope.testWizardParamService.getSetterTitle(scope.param);
                };
                scope.updateSummary = function () {
                    scope.summary = scope.testWizardParamService.getSetterSummary(scope.param, scope.output);
                };
                scope.listOptions = {
                    //grid virtialization <-> setter directive workaround
                    virtualizationThreshold: 64000,
                    enableFiltering: true,
                    enableGridMenu: true,
                    exporterMenuCsv: false,
                    exporterMenuPdf: false,
                    importerShowMenu: false,
                    data: "output",
                    exporterCsvFilename: 'export.csv',
                    exporterHeaderFilterUseName: true,
                    exporterHeaderFilter: function (name) {
                        return name;
                    },
                    showGridFooter: true,
                    columnDefs: [],
                    onRegisterApi: function (gridApi) {
                        scope.listGridApi = gridApi;
                    },
                    importerDataAddCallback: function (grid, newObjects) {
                        for (var i = 0; i < newObjects.length; i++) {
                            for (var key in newObjects[i]) {
                                for (var j = 0; j < scope.listOptions.columnDefs.length; j++) {
                                    var col = scope.listOptions.columnDefs[j];
                                    if (col.name === key) {
                                        if (col.type == 4) {
                                            newObjects[i][key] = newObjects[i][key].toString();
                                        }
                                        if (col.type == 9 || col.type == 10) {
                                            newObjects[i][key] = angular.fromJson(newObjects[i][key]);
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                        scope.output = scope.output.concat(newObjects);
                    },
                    exporterFieldCallback: function (grid, row, col, value) {
                        if (value !== undefined && value.constructor == Array) {
                            value = angular.toJson(value);
                        }
                        return value;
                    },
                    enableCellEditOnFocus: false
                };
                scope.$watch("output.length", function (newValue) {
                    scope.listOptions.enableFiltering = newValue > 0;
                    if (scope.listGridApi && uiGridConstants.dataChange) {
                        scope.listGridApi.core.notifyDataChange(uiGridConstants.dataChange.COLUMN);
                    }
                });
                scope.getColumnDefs = function (obj, param, parent, output, isGroupField) {
                    if (!obj)
                        return [];
                    if (!isGroupField && obj.type == 9) {
                        var fields = [];
                        for (var i = 0; i < obj.definition.fields.length; i++) {
                            var field = obj.definition.fields[i];
                            var param = "grid.appScope.param.definition.element.definition.fields[" + i + "]";
                            var parent = "grid.appScope.output[grid.appScope.output.indexOf(row.entity)]";
                            var output = "grid.appScope.output[grid.appScope.output.indexOf(row.entity)]." + field.name;
                            var add = scope.getColumnDefs(field, param, parent, output, true);
                            for (var j = 0; j < add.length; j++) {
                                fields.push(add[j]);
                            }
                        }
                        return fields;
                    }

                    return [{
                            type: obj.type,
                            displayName: isGroupField ? obj.label : Trans.TEST_WIZARD_PARAM_LIST_COLUMN_ELEMENT,
                            name: isGroupField ? obj.name : "element",
                            cellTemplate:
                                    "
" +
                                    scope.getParamSetterCellTemplate(param, parent, output) +
                                    "
"
                        }];
                };
                scope.getParamSetterCellTemplate = function (param, parent, output) {
                    var cell = '<wizard-param-setter param="' + param + '" parent="' + parent + '" output="' + output + '" mode="grid" wizard-mode="' + scope.wizardMode + '" under-list="true" values="grid.appScope.values" wizard-object="grid.appScope.wizardObject"></wizard-param-setter>';
                    return cell;
                };
                scope.initializeListColumnDefs = function () {
                    var defs = [];
                    var param = "grid.appScope.param.definition.element";
                    var parent = "grid.appScope.output";
                    var output = "grid.appScope.output[grid.appScope.output.indexOf(row.entity)]";
                    var cd = scope.getColumnDefs(scope.param.definition.element, param, parent, output, false);
                    for (var i = 0; i < cd.length; i++) {
                        defs.push(cd[i]);
                    }

                    defs.push({
                        displayName: "",
                        name: "_action",
                        enableSorting: false,
                        enableFiltering: false,
                        exporterSuppressExport: true,
                        enableCellEdit: false,
                        cellTemplate:
                                "
" +
                                '<button class="btn btn-danger btn-xs" ng-click="grid.appScope.removeElement(grid.appScope.output.indexOf(row.entity));">' + Trans.TEST_WIZARD_PARAM_LIST_ELEMENT_DELETE + '</button>' +
                                "
",
                        width: 100
                    });
                    scope.listOptions.columnDefs = defs;
                };
                scope.launchSetterDialog = function (param, output, parent, values, wizardObject) {
                    var modalInstance = $uibModal.open({
                        templateUrl: Paths.DIALOG_TEMPLATE_ROOT + "param_setter_dialog.html",
                        scope: scope,
                        controller: TestWizardParamSetterController,
                        resolve: {
                            param: function () {
                                return param;
                            },
                            output: function () {
                                return output;
                            },
                            parent: function () {
                                return parent;
                            },
                            values: function () {
                                return values;
                            },
                            wizardObject: function () {
                                return wizardObject;
                            },
                            wizardMode: function () {
                                return scope.wizardMode;
                            }
                        },
                        size: "prc-lg"
                    });
                    modalInstance.result.then(function (result) {
                        scope.output = result;
                    }, function () {
                    });
                };
                scope.moveElementUp = function (index) {
                    scope.output.splice(index + 1, 0, scope.output.splice(index, 1)[0]);
                };
                scope.moveElementDown = function (index) {
                    scope.output.splice(index - 1, 0, scope.output.splice(index, 1)[0]);
                };
                scope.moveFieldUp = function (index) {
                    var params = $filter('orderBy')(scope.param.definition.fields, "order");
                    var paramFound = false;
                    for (var i = 0; i < params.length; i++) {
                        var param = params[i];
                        if (param.order !== i) {
                            param.order = i;
                        }
                        if (paramFound) {
                            param.order--;
                            params[i - 1].order++;
                            paramFound = false;
                        }
                        if (index === i) {
                            paramFound = true;
                        }
                    }
                };
                scope.moveFieldDown = function (index) {
                    var params = $filter('orderBy')(scope.param.definition.fields, "order");
                    for (var i = 0; i < params.length; i++) {
                        var param = params[i];
                        if (param.order !== i) {
                            param.order = i;
                        }
                        if (index === i) {
                            params[i - 1].order++;
                            param.order--;
                        }
                    }
                };
                scope.addElement = function () {
                    if (scope.param.definition.element.type == 4) {
                        scope.output.push({value: null});
                    } else if (scope.param.definition.element.type == 7 || scope.param.definition.element.type == 9) {
                        scope.output.push({});
                    } else if (scope.param.definition.element.type == 10) {
                        scope.output.push([]);
                    } else {
                        scope.output.push({value: null});
                    }
                };
                scope.removeElement = function (index) {
                    scope.output.splice(index, 1);
                };
                scope.removeSelectedElements = function () {
                    var selectedRows = scope.listGridApi.selection.getSelectedRows();
                    for (var i = 0; i < selectedRows.length; i++) {
                        for (var j = 0; j < scope.output.length; j++) {
                            if (scope.output[j] == selectedRows[i]) {
                                scope.removeElement(j);
                                break;
                            }
                        }
                    }
                };
                scope.removeAllElements = function () {
                    scope.output = [];
                };
                scope.$watch('param.type', function (newValue, oldValue) {
                    if (!scope.param)
                        return;
                    if (newValue === null || newValue === undefined)
                        return;

                    switch (parseInt(newValue)) {
                        case 7:
                        case 9:
                            if (scope.output === null || scope.output === undefined || typeof scope.output !== 'object' || scope.output.constructor === Array || newValue != oldValue) {
                                scope.output = {};
                            }
                            break;
                        case 10:
                            if (scope.output === null || scope.output === undefined || scope.output.constructor !== Array || newValue != oldValue) {
                                scope.output = [];
                            }
                            break;
                        default:
                            if (scope.output === undefined || typeof scope.output === 'object' || newValue != oldValue) {
                                scope.output = null;
                            }
                            break;
                    }

                    if (newValue == 10) {
                        scope.initializeListColumnDefs();
                    }
                    scope.updateSeterComplexity();
                    scope.updateTitle();
                    scope.updateSummary();
                    element.html($templateCache.get("type_" + newValue + "_setter.html"));
                    $compile(element.contents())(scope);
                });

                scope.$watch('param.definition.element.type', function (newValue, oldValue) {
                    if (newValue === null || newValue === undefined)
                        return;
                    if (newValue != oldValue) {
                        if (scope.param.type == 10) {
                            if (scope.output === null || scope.output === undefined || scope.output.constructor !== Array || newValue !== oldValue) {
                                scope.output = [];
                            }
                        }
                    }
                });
                scope.$watchCollection('output', function (newValue) {
                    scope.updateSummary();
                });
                scope.$watch("param.definition.defvalue", function (newValue, oldValue) {
                    if (scope.output === null || (scope.wizardMode == "dev" && newValue != null && newValue != undefined && !scope.underList)) {
                        scope.output = newValue;
                    }
                });

                if ((scope.output === undefined || scope.output == null) && scope.param.definition != undefined) {
                    switch (parseInt(scope.param.type)) {
                        case 0:
                        case 1:
                        case 2:
                        case 3:
                        case 4:
                        case 5:
                        case 6:
                        case 8:
                        case 11:
                            scope.output = scope.param.definition.defvalue;
                            break;
                    }
                }
                if (scope.output === undefined || scope.output === null) {
                    switch (parseInt(scope.param.type)) {
                        case 0:
                        case 1:
                        case 2:
                        case 3:
                        case 5:
                        case 6:
                        case 8:
                        case 11:
                            scope.output = "";
                            break;
                        case 4:
                            scope.output = "0";
                            break;
                    }
                }
            }
        };
    }]);<|endoftext|>


<commit_before>import SPELLS from 'common/SPELLS/index';
import { formatMilliseconds, formatPercentage } from 'common/format';
import Analyzer from 'parser/core/Analyzer';
import EventEmitter from 'parser/core/modules/EventEmitter';
import StatTracker from 'parser/shared/modules/StatTracker';
import BLOODLUST_BUFFS from 'game/BLOODLUST_BUFFS';
import EventFilter from 'parser/core/EventFilter';
import { EventType } from 'parser/core/Events';
import { STEADY_FOCUS_HASTE_PERCENT } from 'parser/hunter/marksmanship/constants';
import { DIRE_BEAST_HASTE_PERCENT } from 'parser/hunter/shared/constants';

const debug = false;

class Haste extends Analyzer {
  static dependencies = {
    eventEmitter: EventEmitter,
    statTracker: StatTracker,
  };

  /* eslint-disable no-useless-computed-key */
  static HASTE_BUFFS = {

    // HASTE RATING BUFFS ARE HANDLED BY THE STATTRACKER MODULE

    ...BLOODLUST_BUFFS,
    [SPELLS.HOLY_AVENGER_TALENT.id]: 0.3,
    [SPELLS.BERSERKING.id]: 0.1,
    [SPELLS.ICY_VEINS.id]: 0.3,
    [SPELLS.IN_FOR_THE_KILL_TALENT_BUFF.id]: 0.1,
    [SPELLS.BONE_SHIELD.id]: 0.1, // Blood BK haste buff from maintaining boneshield
    [SPELLS.METAMORPHOSIS_HAVOC_BUFF.id]: 0.25,
    [SPELLS.HAVOC_T21_4PC_BUFF.id]: 0.25,
    [SPELLS.DARK_SOUL_MISERY_TALENT.id]: 0.3,
    [SPELLS.REVERSE_ENTROPY_BUFF.id]: 0.15,
    [SPELLS.ENRAGE.id]: 0.25, // Fury Warrior
    [SPELLS.FROTHING_BERSERKER.id]: 0.05, // Fury Warrior
    [SPELLS.QUICK_THINKER_BUFF.id]: 0.15,
    [SPELLS.EMPOWER_RUNE_WEAPON.id]: 0.15, // Frost DK
    [SPELLS.EVER_RISING_TIDE_CHARGING_BUFF.id]: 0.1, // Essence
    [SPELLS.GUARDIAN_OF_AZEROTH_HASTE_BUFF.id]: {
      hastePerStack: 0.02,
    },

    //region Hunter Haste Buffs
    [SPELLS.DIRE_BEAST_BUFF.id]: DIRE_BEAST_HASTE_PERCENT,
    [SPELLS.STEADY_FOCUS_BUFF]: STEADY_FOCUS_HASTE_PERCENT,
    //endregion

    // Boss abilities:
    [SPELLS.OPULENCE_AMETHYST_OF_THE_SHADOW_KING.id]: 0.5, // Amethyst of the Shadow King by Opulence (BoD - BFA)
  };

  get changehaste() {
    return new EventFilter(EventType.ChangeHaste);
  }

  current = null;
  constructor(...args) {
    super(...args);
    this.current = this.statTracker.currentHastePercentage;
    debug && console.log(`Haste: Starting haste: ${formatPercentage(this.current)}%`);
    this._triggerChangeHaste(null, null, this.current);
  }
  on_toPlayer_applybuff(event) {
    this._applyActiveBuff(event);
  }
  on_toPlayer_changebuffstack(event) {
    this._changeBuffStack(event);
  }
  on_toPlayer_removebuff(event) {
    this._removeActiveBuff(event);
  }
  on_toPlayer_applydebuff(event) {
    this._applyActiveBuff(event);
  }
  on_toPlayer_changedebuffstack(event) {
    this._changeBuffStack(event);
  }
  on_toPlayer_removedebuff(event) {
    this._removeActiveBuff(event);
  }

  on_toPlayer_changestats(event) { // fabbed event from StatTracker
    if (!event.delta.haste) {
      return;
    }

    // Calculating the Haste percentage difference form a rating change is hard because all rating (from gear + buffs) is additive while Haste percentage buffs are both multiplicative and additive (see the applyHaste function).
    // 1. Calculate the total Haste percentage without any rating (since the total percentage from the total rating multiplies like any other Haste buff)
    const remainingHasteBuffs = this.constructor.removeHaste(this.current, this.statTracker.hastePercentage(event.before.haste, true));
    // 2. Calculate the new total Haste percentage with the new rating and the old total buff percentage
    const newHastePercentage = this.constructor.addHaste(this.statTracker.hastePercentage(event.after.haste, true), remainingHasteBuffs);

    this._setHaste(event, newHastePercentage);

    if (debug) {
      const spellName = event.trigger.ability ? event.trigger.ability.name : 'unknown';
      console.log(`Haste: Current haste: ${formatPercentage(this.current)}% (haste RATING changed by ${event.delta.haste} from ${spellName})`);
    }
  }

  _applyActiveBuff(event) {
    const spellId = event.ability.guid;
    const hasteGain = this._getBaseHasteGain(spellId);

    if (hasteGain) {
      this._applyHasteGain(event, hasteGain);

      debug && console.log(formatMilliseconds(this.owner.fightDuration), 'Haste:', 'Current haste:', `${formatPercentage(this.current)}%`, `(gained ${formatPercentage(hasteGain)}% from ${event.ability.name})`);
    } else {
      debug && console.warn(formatMilliseconds(this.owner.fightDuration), 'Haste: Applied not recognized buff:', event.ability.name);
    }
  }
  _removeActiveBuff(event) {
    const spellId = event.ability.guid;
    const haste = this._getBaseHasteGain(spellId);

    if (haste) {
      this._applyHasteLoss(event, haste);

      debug && console.log(`Haste: Current haste: ${formatPercentage(this.current)}% (lost ${formatPercentage(haste)}% from ${SPELLS[spellId] ? SPELLS[spellId].name : spellId})`);
    } else {
      debug && console.warn(formatMilliseconds(this.owner.fightDuration), 'Haste: Removed not recognized buff:', event.ability.name);
    }
  }
  /**
   * Gets the base Haste gain for the provided spell.
   */
  _getBaseHasteGain(spellId) {
    const hasteBuff = this.constructor.HASTE_BUFFS[spellId] || undefined;

    if (typeof hasteBuff === 'number') {
      // A regular number is a static Haste percentage
      return hasteBuff;
    } else if (typeof hasteBuff === 'object') {
      // An object can provide more info
      if (hasteBuff.haste) {
        return this._getHasteValue(hasteBuff.haste, hasteBuff);
      }
    }
    return null;
  }

  _changeBuffStack(event) {
    const spellId = event.ability.guid;
    const haste = this._getHastePerStackGain(spellId);

    if (haste) {
      // Haste stacks are additive, so at 5 stacks with 3% per you'd be at 15%, 6 stacks = 18%. This means the only right way to add a Haste stack is to reset to Haste without the old total and then add the new total Haste again.
      // 1. Calculate the total Haste percentage without the buff
      const baseHaste = this.constructor.removeHaste(this.current, event.oldStacks * haste);
      // 2. Calculate the new total Haste percentage with the Haste from the new amount of stacks
      const newHastePercentage = this.constructor.addHaste(baseHaste, event.newStacks * haste);

      this._setHaste(event, newHastePercentage);

      debug && console.log(`Haste: Current haste: ${formatPercentage(this.current)}% (gained ${formatPercentage(haste * event.stacksGained)}% from ${SPELLS[spellId] ? SPELLS[spellId].name : spellId})`);
    }
  }
  _getHastePerStackGain(spellId) {
    const hasteBuff = this.constructor.HASTE_BUFFS[spellId] || undefined;

    if (typeof hasteBuff === 'number') {
      // hasteBuff being a number is shorthand for static haste only
    } else if (typeof hasteBuff === 'object') {
      if (hasteBuff.hastePerStack) {
        return this._getHasteValue(hasteBuff.hastePerStack, hasteBuff);
      }
    }
    return null;
  }
  /**
   * Get the actual Haste value from a prop allowing various formats.
   */
  _getHasteValue(value, hasteBuff) {
    const { itemId } = hasteBuff;
    if (typeof value === 'function') {
      const selectedCombatant = this.selectedCombatant;
      let itemDetails;
      if (itemId) {
        itemDetails = selectedCombatant.getItem(itemId);
        if (!itemDetails) {
          console.error('Failed to retrieve item information for item with ID:', itemId);
        }
      }
      return value(selectedCombatant, itemDetails);
    }
    return value;
  }

  _applyHasteGain(event, haste) {
    this._setHaste(event, this.constructor.addHaste(this.current, haste));
  }
  _applyHasteLoss(event, haste) {
    this._setHaste(event, this.constructor.removeHaste(this.current, haste));
  }
  _setHaste(event, haste) {
    if (isNaN(haste)) {
      throw new Error('Attempted to set an invalid Haste value. Something broke.');
    }
    const oldHaste = this.current;
    this.current = haste;

    this._triggerChangeHaste(event, oldHaste, this.current);
  }
  _triggerChangeHaste(event, oldHaste, newHaste) {
    const fabricatedEvent = {
      type: EventType.ChangeHaste,
      sourceID: event ? event.sourceID : this.owner.playerId,
      targetID: this.owner.playerId,
      oldHaste,
      newHaste,
    };
    debug && console.log(EventType.ChangeHaste, fabricatedEvent);
    this.eventEmitter.fabricateEvent(fabricatedEvent, event);
  }

  static addHaste(baseHaste, hasteGain) {
    return baseHaste * (1 + hasteGain) + hasteGain;
  }
  static removeHaste(baseHaste, hasteLoss) {
    return (baseHaste - hasteLoss) / (1 + hasteLoss);
  }
}

export default Haste;
<commit_msg>[Core] Add a missing .id to Haste.js<commit_after>import SPELLS from 'common/SPELLS/index';
import { formatMilliseconds, formatPercentage } from 'common/format';
import Analyzer from 'parser/core/Analyzer';
import EventEmitter from 'parser/core/modules/EventEmitter';
import StatTracker from 'parser/shared/modules/StatTracker';
import BLOODLUST_BUFFS from 'game/BLOODLUST_BUFFS';
import EventFilter from 'parser/core/EventFilter';
import { EventType } from 'parser/core/Events';
import { STEADY_FOCUS_HASTE_PERCENT } from 'parser/hunter/marksmanship/constants';
import { DIRE_BEAST_HASTE_PERCENT } from 'parser/hunter/shared/constants';

const debug = false;

class Haste extends Analyzer {
  static dependencies = {
    eventEmitter: EventEmitter,
    statTracker: StatTracker,
  };

  /* eslint-disable no-useless-computed-key */
  static HASTE_BUFFS = {

    // HASTE RATING BUFFS ARE HANDLED BY THE STATTRACKER MODULE

    ...BLOODLUST_BUFFS,
    [SPELLS.HOLY_AVENGER_TALENT.id]: 0.3,
    [SPELLS.BERSERKING.id]: 0.1,
    [SPELLS.ICY_VEINS.id]: 0.3,
    [SPELLS.IN_FOR_THE_KILL_TALENT_BUFF.id]: 0.1,
    [SPELLS.BONE_SHIELD.id]: 0.1, // Blood BK haste buff from maintaining boneshield
    [SPELLS.METAMORPHOSIS_HAVOC_BUFF.id]: 0.25,
    [SPELLS.HAVOC_T21_4PC_BUFF.id]: 0.25,
    [SPELLS.DARK_SOUL_MISERY_TALENT.id]: 0.3,
    [SPELLS.REVERSE_ENTROPY_BUFF.id]: 0.15,
    [SPELLS.ENRAGE.id]: 0.25, // Fury Warrior
    [SPELLS.FROTHING_BERSERKER.id]: 0.05, // Fury Warrior
    [SPELLS.QUICK_THINKER_BUFF.id]: 0.15,
    [SPELLS.EMPOWER_RUNE_WEAPON.id]: 0.15, // Frost DK
    [SPELLS.EVER_RISING_TIDE_CHARGING_BUFF.id]: 0.1, // Essence
    [SPELLS.GUARDIAN_OF_AZEROTH_HASTE_BUFF.id]: {
      hastePerStack: 0.02,
    },

    //region Hunter Haste Buffs
    [SPELLS.DIRE_BEAST_BUFF.id]: DIRE_BEAST_HASTE_PERCENT,
    [SPELLS.STEADY_FOCUS_BUFF.id]: STEADY_FOCUS_HASTE_PERCENT,
    //endregion

    // Boss abilities:
    [SPELLS.OPULENCE_AMETHYST_OF_THE_SHADOW_KING.id]: 0.5, // Amethyst of the Shadow King by Opulence (BoD - BFA)
  };

  get changehaste() {
    return new EventFilter(EventType.ChangeHaste);
  }

  current = null;
  constructor(...args) {
    super(...args);
    this.current = this.statTracker.currentHastePercentage;
    debug && console.log(`Haste: Starting haste: ${formatPercentage(this.current)}%`);
    this._triggerChangeHaste(null, null, this.current);
  }
  on_toPlayer_applybuff(event) {
    this._applyActiveBuff(event);
  }
  on_toPlayer_changebuffstack(event) {
    this._changeBuffStack(event);
  }
  on_toPlayer_removebuff(event) {
    this._removeActiveBuff(event);
  }
  on_toPlayer_applydebuff(event) {
    this._applyActiveBuff(event);
  }
  on_toPlayer_changedebuffstack(event) {
    this._changeBuffStack(event);
  }
  on_toPlayer_removedebuff(event) {
    this._removeActiveBuff(event);
  }

  on_toPlayer_changestats(event) { // fabbed event from StatTracker
    if (!event.delta.haste) {
      return;
    }

    // Calculating the Haste percentage difference form a rating change is hard because all rating (from gear + buffs) is additive while Haste percentage buffs are both multiplicative and additive (see the applyHaste function).
    // 1. Calculate the total Haste percentage without any rating (since the total percentage from the total rating multiplies like any other Haste buff)
    const remainingHasteBuffs = this.constructor.removeHaste(this.current, this.statTracker.hastePercentage(event.before.haste, true));
    // 2. Calculate the new total Haste percentage with the new rating and the old total buff percentage
    const newHastePercentage = this.constructor.addHaste(this.statTracker.hastePercentage(event.after.haste, true), remainingHasteBuffs);

    this._setHaste(event, newHastePercentage);

    if (debug) {
      const spellName = event.trigger.ability ? event.trigger.ability.name : 'unknown';
      console.log(`Haste: Current haste: ${formatPercentage(this.current)}% (haste RATING changed by ${event.delta.haste} from ${spellName})`);
    }
  }

  _applyActiveBuff(event) {
    const spellId = event.ability.guid;
    const hasteGain = this._getBaseHasteGain(spellId);

    if (hasteGain) {
      this._applyHasteGain(event, hasteGain);

      debug && console.log(formatMilliseconds(this.owner.fightDuration), 'Haste:', 'Current haste:', `${formatPercentage(this.current)}%`, `(gained ${formatPercentage(hasteGain)}% from ${event.ability.name})`);
    } else {
      debug && console.warn(formatMilliseconds(this.owner.fightDuration), 'Haste: Applied not recognized buff:', event.ability.name);
    }
  }
  _removeActiveBuff(event) {
    const spellId = event.ability.guid;
    const haste = this._getBaseHasteGain(spellId);

    if (haste) {
      this._applyHasteLoss(event, haste);

      debug && console.log(`Haste: Current haste: ${formatPercentage(this.current)}% (lost ${formatPercentage(haste)}% from ${SPELLS[spellId] ? SPELLS[spellId].name : spellId})`);
    } else {
      debug && console.warn(formatMilliseconds(this.owner.fightDuration), 'Haste: Removed not recognized buff:', event.ability.name);
    }
  }
  /**
   * Gets the base Haste gain for the provided spell.
   */
  _getBaseHasteGain(spellId) {
    const hasteBuff = this.constructor.HASTE_BUFFS[spellId] || undefined;

    if (typeof hasteBuff === 'number') {
      // A regular number is a static Haste percentage
      return hasteBuff;
    } else if (typeof hasteBuff === 'object') {
      // An object can provide more info
      if (hasteBuff.haste) {
        return this._getHasteValue(hasteBuff.haste, hasteBuff);
      }
    }
    return null;
  }

  _changeBuffStack(event) {
    const spellId = event.ability.guid;
    const haste = this._getHastePerStackGain(spellId);

    if (haste) {
      // Haste stacks are additive, so at 5 stacks with 3% per you'd be at 15%, 6 stacks = 18%. This means the only right way to add a Haste stack is to reset to Haste without the old total and then add the new total Haste again.
      // 1. Calculate the total Haste percentage without the buff
      const baseHaste = this.constructor.removeHaste(this.current, event.oldStacks * haste);
      // 2. Calculate the new total Haste percentage with the Haste from the new amount of stacks
      const newHastePercentage = this.constructor.addHaste(baseHaste, event.newStacks * haste);

      this._setHaste(event, newHastePercentage);

      debug && console.log(`Haste: Current haste: ${formatPercentage(this.current)}% (gained ${formatPercentage(haste * event.stacksGained)}% from ${SPELLS[spellId] ? SPELLS[spellId].name : spellId})`);
    }
  }
  _getHastePerStackGain(spellId) {
    const hasteBuff = this.constructor.HASTE_BUFFS[spellId] || undefined;

    if (typeof hasteBuff === 'number') {
      // hasteBuff being a number is shorthand for static haste only
    } else if (typeof hasteBuff === 'object') {
      if (hasteBuff.hastePerStack) {
        return this._getHasteValue(hasteBuff.hastePerStack, hasteBuff);
      }
    }
    return null;
  }
  /**
   * Get the actual Haste value from a prop allowing various formats.
   */
  _getHasteValue(value, hasteBuff) {
    const { itemId } = hasteBuff;
    if (typeof value === 'function') {
      const selectedCombatant = this.selectedCombatant;
      let itemDetails;
      if (itemId) {
        itemDetails = selectedCombatant.getItem(itemId);
        if (!itemDetails) {
          console.error('Failed to retrieve item information for item with ID:', itemId);
        }
      }
      return value(selectedCombatant, itemDetails);
    }
    return value;
  }

  _applyHasteGain(event, haste) {
    this._setHaste(event, this.constructor.addHaste(this.current, haste));
  }
  _applyHasteLoss(event, haste) {
    this._setHaste(event, this.constructor.removeHaste(this.current, haste));
  }
  _setHaste(event, haste) {
    if (isNaN(haste)) {
      throw new Error('Attempted to set an invalid Haste value. Something broke.');
    }
    const oldHaste = this.current;
    this.current = haste;

    this._triggerChangeHaste(event, oldHaste, this.current);
  }
  _triggerChangeHaste(event, oldHaste, newHaste) {
    const fabricatedEvent = {
      type: EventType.ChangeHaste,
      sourceID: event ? event.sourceID : this.owner.playerId,
      targetID: this.owner.playerId,
      oldHaste,
      newHaste,
    };
    debug && console.log(EventType.ChangeHaste, fabricatedEvent);
    this.eventEmitter.fabricateEvent(fabricatedEvent, event);
  }

  static addHaste(baseHaste, hasteGain) {
    return baseHaste * (1 + hasteGain) + hasteGain;
  }
  static removeHaste(baseHaste, hasteLoss) {
    return (baseHaste - hasteLoss) / (1 + hasteLoss);
  }
}

export default Haste;
<|endoftext|>


<commit_before>import SPELLS from 'common/SPELLS/index';
import { formatMilliseconds, formatPercentage } from 'common/format';

import Analyzer from 'parser/core/Analyzer';
import StatTracker from 'parser/shared/modules/StatTracker';
import BLOODLUST_BUFFS from 'game/BLOODLUST_BUFFS';

const debug = false;

class Haste extends Analyzer {
  static dependencies = {
    statTracker: StatTracker,
  };

  /* eslint-disable no-useless-computed-key */
  static HASTE_BUFFS = {

    // HASTE RATING BUFFS ARE HANDLED BY THE STATTRACKER MODULE

    ...BLOODLUST_BUFFS,
    [SPELLS.HOLY_AVENGER_TALENT.id]: 0.3,
    [SPELLS.BERSERKING.id]: 0.15,
    [SPELLS.TRUESHOT.id]: 0.3, // MM Hunter main CD
    [SPELLS.ICY_VEINS.id]: 0.3,
    [SPELLS.IN_FOR_THE_KILL_TALENT_BUFF.id]: 0.1,
    [SPELLS.BONE_SHIELD.id]: 0.1, // Blood BK haste buff from maintaining boneshield
    [SPELLS.METAMORPHOSIS_HAVOC_BUFF.id]: 0.25,
    [SPELLS.HAVOC_T21_4PC_BUFF.id]: 0.25,
    [SPELLS.DIRE_BEAST_BUFF.id]: 0.1,
    [SPELLS.DARK_SOUL_MISERY_TALENT.id]: 0.3,
    [SPELLS.REVERSE_ENTROPY_BUFF.id]: 0.15,
    [SPELLS.ENRAGE.id]: 0.25, // Fury Warrior
    [SPELLS.FROTHING_BERSERKER.id]: 0.05, // Fury Warrior
    [SPELLS.QUICK_THINKER_BUFF.id]: 0.15,

    // Boss abilities:
    [209166]: 0.3, // DEBUFF - Fast Time from Elisande
    [209165]: -0.3, // DEBUFF - Slow Time from Elisande
    // [208944]: -Infinity, // DEBUFF - Time Stop from Elisande
  };

  current = null;
  constructor(...args) {
    super(...args);
    this.current = this.statTracker.currentHastePercentage;
    debug && console.log(`Haste: Starting haste: ${formatPercentage(this.current)}%`);
    this._triggerChangeHaste(null, null, this.current);
  }
  on_toPlayer_applybuff(event) {
    this._applyActiveBuff(event);
  }
  on_toPlayer_changebuffstack(event) {
    this._changeBuffStack(event);
  }
  on_toPlayer_removebuff(event) {
    this._removeActiveBuff(event);
  }
  on_toPlayer_applydebuff(event) {
    this._applyActiveBuff(event);
  }
  on_toPlayer_changedebuffstack(event) {
    this._changeBuffStack(event);
  }
  on_toPlayer_removedebuff(event) {
    this._removeActiveBuff(event);
  }

  on_toPlayer_changestats(event) { // fabbed event from StatTracker
    if (!event.delta.haste) {
      return;
    }

    // Calculating the Haste percentage difference form a rating change is hard because all rating (from gear + buffs) is additive while Haste percentage buffs are both multiplicative and additive (see the applyHaste function).
    // 1. Calculate the total Haste percentage without any rating (since the total percentage from the total rating multiplies like any other Haste buff)
    const remainingHasteBuffs = this.constructor.removeHaste(this.current, this.statTracker.hastePercentage(event.before.haste, true));
    // 2. Calculate the new total Haste percentage with the new rating and the old total buff percentage
    const newHastePercentage = this.constructor.addHaste(this.statTracker.hastePercentage(event.after.haste, true), remainingHasteBuffs);

    this._setHaste(event, newHastePercentage);

    if (debug) {
      const spellName = event.trigger.ability ? event.trigger.ability.name : 'unknown';
      console.log(`Haste: Current haste: ${formatPercentage(this.current)}% (haste RATING changed by ${event.delta.haste} from ${spellName})`);
    }
  }

  _applyActiveBuff(event) {
    const spellId = event.ability.guid;
    const hasteGain = this._getBaseHasteGain(spellId);

    if (hasteGain) {
      this._applyHasteGain(event, hasteGain);

      debug && console.log(formatMilliseconds(this.owner.fightDuration), 'Haste:', 'Current haste:', `${formatPercentage(this.current)}%`, `(gained ${formatPercentage(hasteGain)}% from ${event.ability.name})`);
    } else {
      debug && console.warn(formatMilliseconds(this.owner.fightDuration), 'Haste: Applied not recognized buff:', event.ability.name);
    }
  }
  _removeActiveBuff(event) {
    const spellId = event.ability.guid;
    const haste = this._getBaseHasteGain(spellId);

    if (haste) {
      this._applyHasteLoss(event, haste);

      debug && console.log(`Haste: Current haste: ${formatPercentage(this.current)}% (lost ${formatPercentage(haste)}% from ${SPELLS[spellId] ? SPELLS[spellId].name : spellId})`);
    } else {
      debug && console.warn(formatMilliseconds(this.owner.fightDuration), 'Haste: Removed not recognized buff:', event.ability.name);
    }
  }
  /**
   * Gets the base Haste gain for the provided spell.
   */
  _getBaseHasteGain(spellId) {
    const hasteBuff = this.constructor.HASTE_BUFFS[spellId] || undefined;

    if (typeof hasteBuff === 'number') {
      // A regular number is a static Haste percentage
      return hasteBuff;
    } else if (typeof hasteBuff === 'object') {
      // An object can provide more info
      if (hasteBuff.haste) {
        return this._getHasteValue(hasteBuff.haste, hasteBuff);
      }
    }
    return null;
  }

  _changeBuffStack(event) {
    const spellId = event.ability.guid;
    const haste = this._getHastePerStackGain(spellId);

    if (haste) {
      // Haste stacks are additive, so at 5 stacks with 3% per you'd be at 15%, 6 stacks = 18%. This means the only right way to add a Haste stack is to reset to Haste without the old total and then add the new total Haste again.
      // 1. Calculate the total Haste percentage without the buff
      const baseHaste = this.constructor.removeHaste(this.current, event.oldStacks * haste);
      // 2. Calculate the new total Haste percentage with the Haste from the new amount of stacks
      const newHastePercentage = this.constructor.addHaste(baseHaste, event.newStacks * haste);

      this._setHaste(event, newHastePercentage);

      debug && console.log(`Haste: Current haste: ${formatPercentage(this.current)}% (gained ${formatPercentage(haste * event.stacksGained)}% from ${SPELLS[spellId] ? SPELLS[spellId].name : spellId})`);
    }
  }
  _getHastePerStackGain(spellId) {
    const hasteBuff = this.constructor.HASTE_BUFFS[spellId] || undefined;

    if (typeof hasteBuff === 'number') {
      // hasteBuff being a number is shorthand for static haste only
    } else if (typeof hasteBuff === 'object') {
      if (hasteBuff.hastePerStack) {
        return this._getHasteValue(hasteBuff.hastePerStack, hasteBuff);
      }
    }
    return null;
  }
  /**
   * Get the actual Haste value from a prop allowing various formats.
   */
  _getHasteValue(value, hasteBuff) {
    const { itemId } = hasteBuff;
    if (typeof value === 'function') {
      const selectedCombatant = this.selectedCombatant;
      let itemDetails;
      if (itemId) {
        itemDetails = selectedCombatant.getItem(itemId);
        if (!itemDetails) {
          console.error('Failed to retrieve item information for item with ID:', itemId);
        }
      }
      return value(selectedCombatant, itemDetails);
    }
    return value;
  }

  _applyHasteGain(event, haste) {
    this._setHaste(event, this.constructor.addHaste(this.current, haste));
  }
  _applyHasteLoss(event, haste) {
    this._setHaste(event, this.constructor.removeHaste(this.current, haste));
  }
  _setHaste(event, haste) {
    const oldHaste = this.current;
    this.current = haste;

    this._triggerChangeHaste(event, oldHaste, this.current);
  }
  _triggerChangeHaste(event, oldHaste, newHaste) {
    const fabricatedEvent = {
      type: 'changehaste',
      sourceID: event ? event.sourceID : this.owner.playerId,
      targetID: this.owner.playerId,
      oldHaste,
      newHaste,
    };
    debug && console.log('changehaste', fabricatedEvent);
    this.owner.fabricateEvent(fabricatedEvent, event);
  }

  static addHaste(baseHaste, hasteGain) {
    return baseHaste * (1 + hasteGain) + hasteGain;
  }
  static removeHaste(baseHaste, hasteLoss) {
    return (baseHaste - hasteLoss) / (1 + hasteLoss);
  }
}

export default Haste;
<commit_msg>added empower rune weapon to haste module<commit_after>import SPELLS from 'common/SPELLS/index';
import { formatMilliseconds, formatPercentage } from 'common/format';

import Analyzer from 'parser/core/Analyzer';
import StatTracker from 'parser/shared/modules/StatTracker';
import BLOODLUST_BUFFS from 'game/BLOODLUST_BUFFS';

const debug = false;

class Haste extends Analyzer {
  static dependencies = {
    statTracker: StatTracker,
  };

  /* eslint-disable no-useless-computed-key */
  static HASTE_BUFFS = {

    // HASTE RATING BUFFS ARE HANDLED BY THE STATTRACKER MODULE

    ...BLOODLUST_BUFFS,
    [SPELLS.HOLY_AVENGER_TALENT.id]: 0.3,
    [SPELLS.BERSERKING.id]: 0.15,
    [SPELLS.TRUESHOT.id]: 0.3, // MM Hunter main CD
    [SPELLS.ICY_VEINS.id]: 0.3,
    [SPELLS.IN_FOR_THE_KILL_TALENT_BUFF.id]: 0.1,
    [SPELLS.BONE_SHIELD.id]: 0.1, // Blood BK haste buff from maintaining boneshield
    [SPELLS.METAMORPHOSIS_HAVOC_BUFF.id]: 0.25,
    [SPELLS.HAVOC_T21_4PC_BUFF.id]: 0.25,
    [SPELLS.DIRE_BEAST_BUFF.id]: 0.1,
    [SPELLS.DARK_SOUL_MISERY_TALENT.id]: 0.3,
    [SPELLS.REVERSE_ENTROPY_BUFF.id]: 0.15,
    [SPELLS.ENRAGE.id]: 0.25, // Fury Warrior
    [SPELLS.FROTHING_BERSERKER.id]: 0.05, // Fury Warrior
    [SPELLS.QUICK_THINKER_BUFF.id]: 0.15,
    [SPELLS.EMPOWER_RUNE_WEAPON.id]: 0.15, // Frost DK

    // Boss abilities:
    [209166]: 0.3, // DEBUFF - Fast Time from Elisande
    [209165]: -0.3, // DEBUFF - Slow Time from Elisande
    // [208944]: -Infinity, // DEBUFF - Time Stop from Elisande
  };

  current = null;
  constructor(...args) {
    super(...args);
    this.current = this.statTracker.currentHastePercentage;
    debug && console.log(`Haste: Starting haste: ${formatPercentage(this.current)}%`);
    this._triggerChangeHaste(null, null, this.current);
  }
  on_toPlayer_applybuff(event) {
    this._applyActiveBuff(event);
  }
  on_toPlayer_changebuffstack(event) {
    this._changeBuffStack(event);
  }
  on_toPlayer_removebuff(event) {
    this._removeActiveBuff(event);
  }
  on_toPlayer_applydebuff(event) {
    this._applyActiveBuff(event);
  }
  on_toPlayer_changedebuffstack(event) {
    this._changeBuffStack(event);
  }
  on_toPlayer_removedebuff(event) {
    this._removeActiveBuff(event);
  }

  on_toPlayer_changestats(event) { // fabbed event from StatTracker
    if (!event.delta.haste) {
      return;
    }

    // Calculating the Haste percentage difference form a rating change is hard because all rating (from gear + buffs) is additive while Haste percentage buffs are both multiplicative and additive (see the applyHaste function).
    // 1. Calculate the total Haste percentage without any rating (since the total percentage from the total rating multiplies like any other Haste buff)
    const remainingHasteBuffs = this.constructor.removeHaste(this.current, this.statTracker.hastePercentage(event.before.haste, true));
    // 2. Calculate the new total Haste percentage with the new rating and the old total buff percentage
    const newHastePercentage = this.constructor.addHaste(this.statTracker.hastePercentage(event.after.haste, true), remainingHasteBuffs);

    this._setHaste(event, newHastePercentage);

    if (debug) {
      const spellName = event.trigger.ability ? event.trigger.ability.name : 'unknown';
      console.log(`Haste: Current haste: ${formatPercentage(this.current)}% (haste RATING changed by ${event.delta.haste} from ${spellName})`);
    }
  }

  _applyActiveBuff(event) {
    const spellId = event.ability.guid;
    const hasteGain = this._getBaseHasteGain(spellId);

    if (hasteGain) {
      this._applyHasteGain(event, hasteGain);

      debug && console.log(formatMilliseconds(this.owner.fightDuration), 'Haste:', 'Current haste:', `${formatPercentage(this.current)}%`, `(gained ${formatPercentage(hasteGain)}% from ${event.ability.name})`);
    } else {
      debug && console.warn(formatMilliseconds(this.owner.fightDuration), 'Haste: Applied not recognized buff:', event.ability.name);
    }
  }
  _removeActiveBuff(event) {
    const spellId = event.ability.guid;
    const haste = this._getBaseHasteGain(spellId);

    if (haste) {
      this._applyHasteLoss(event, haste);

      debug && console.log(`Haste: Current haste: ${formatPercentage(this.current)}% (lost ${formatPercentage(haste)}% from ${SPELLS[spellId] ? SPELLS[spellId].name : spellId})`);
    } else {
      debug && console.warn(formatMilliseconds(this.owner.fightDuration), 'Haste: Removed not recognized buff:', event.ability.name);
    }
  }
  /**
   * Gets the base Haste gain for the provided spell.
   */
  _getBaseHasteGain(spellId) {
    const hasteBuff = this.constructor.HASTE_BUFFS[spellId] || undefined;

    if (typeof hasteBuff === 'number') {
      // A regular number is a static Haste percentage
      return hasteBuff;
    } else if (typeof hasteBuff === 'object') {
      // An object can provide more info
      if (hasteBuff.haste) {
        return this._getHasteValue(hasteBuff.haste, hasteBuff);
      }
    }
    return null;
  }

  _changeBuffStack(event) {
    const spellId = event.ability.guid;
    const haste = this._getHastePerStackGain(spellId);

    if (haste) {
      // Haste stacks are additive, so at 5 stacks with 3% per you'd be at 15%, 6 stacks = 18%. This means the only right way to add a Haste stack is to reset to Haste without the old total and then add the new total Haste again.
      // 1. Calculate the total Haste percentage without the buff
      const baseHaste = this.constructor.removeHaste(this.current, event.oldStacks * haste);
      // 2. Calculate the new total Haste percentage with the Haste from the new amount of stacks
      const newHastePercentage = this.constructor.addHaste(baseHaste, event.newStacks * haste);

      this._setHaste(event, newHastePercentage);

      debug && console.log(`Haste: Current haste: ${formatPercentage(this.current)}% (gained ${formatPercentage(haste * event.stacksGained)}% from ${SPELLS[spellId] ? SPELLS[spellId].name : spellId})`);
    }
  }
  _getHastePerStackGain(spellId) {
    const hasteBuff = this.constructor.HASTE_BUFFS[spellId] || undefined;

    if (typeof hasteBuff === 'number') {
      // hasteBuff being a number is shorthand for static haste only
    } else if (typeof hasteBuff === 'object') {
      if (hasteBuff.hastePerStack) {
        return this._getHasteValue(hasteBuff.hastePerStack, hasteBuff);
      }
    }
    return null;
  }
  /**
   * Get the actual Haste value from a prop allowing various formats.
   */
  _getHasteValue(value, hasteBuff) {
    const { itemId } = hasteBuff;
    if (typeof value === 'function') {
      const selectedCombatant = this.selectedCombatant;
      let itemDetails;
      if (itemId) {
        itemDetails = selectedCombatant.getItem(itemId);
        if (!itemDetails) {
          console.error('Failed to retrieve item information for item with ID:', itemId);
        }
      }
      return value(selectedCombatant, itemDetails);
    }
    return value;
  }

  _applyHasteGain(event, haste) {
    this._setHaste(event, this.constructor.addHaste(this.current, haste));
  }
  _applyHasteLoss(event, haste) {
    this._setHaste(event, this.constructor.removeHaste(this.current, haste));
  }
  _setHaste(event, haste) {
    const oldHaste = this.current;
    this.current = haste;

    this._triggerChangeHaste(event, oldHaste, this.current);
  }
  _triggerChangeHaste(event, oldHaste, newHaste) {
    const fabricatedEvent = {
      type: 'changehaste',
      sourceID: event ? event.sourceID : this.owner.playerId,
      targetID: this.owner.playerId,
      oldHaste,
      newHaste,
    };
    debug && console.log('changehaste', fabricatedEvent);
    this.owner.fabricateEvent(fabricatedEvent, event);
  }

  static addHaste(baseHaste, hasteGain) {
    return baseHaste * (1 + hasteGain) + hasteGain;
  }
  static removeHaste(baseHaste, hasteLoss) {
    return (baseHaste - hasteLoss) / (1 + hasteLoss);
  }
}

export default Haste;
<|endoftext|>


<commit_before>var VerticalTabs = {

    prefs: Components.classes["@mozilla.org/preferences-service;1"]
           .getService(Components.interfaces.nsIPrefBranch)
           .QueryInterface(Components.interfaces.nsIPrefBranch2),

    init: function() {
        window.removeEventListener("DOMContentLoaded", this, false);

        // Move the bottom stuff (statusbar, findbar) in with the
        // tabbrowser.  That way it will share the same (horizontal)
        // space as the brower.  In other words, the bottom stuff no
        // longer extends across the whole bottom of the window.
        var contentbox = document.getElementById("appcontent");
        var bottom = document.getElementById("browser-bottombox");
        contentbox.appendChild(bottom);

        // Move the tabs next to the app content, make them vertical,
        // and restore their width from previous session
        var leftbox = document.getElementById("verticaltabs-box");
        var tabs = document.getElementById("tabbrowser-tabs");
        leftbox.insertBefore(tabs, leftbox.firstChild);
        tabs.orient = "vertical";
        tabs.mTabstrip.orient = "vertical";
        tabs.tabbox.orient = "horizontal"; // probably not necessary
        tabs.setAttribute("width", this.prefs.getIntPref('extensions.verticaltabs.width'));

        // Hook up event handler for splitter so that the width of the
        // tab bar is persisted.
        var splitter = document.getElementById("verticaltabs-splitter");
        splitter.addEventListener('mouseup', this, false);

        // New methods and event handlers for drag'n'drop.  The
        // original tabbrowser naturally makes the assumption that
        // stuff is laid out horizontally.
        tabs._getDropIndex = TabbrowserTabs._getDropIndex;
        tabs._setEffectAllowedForDataTransfer
            = TabbrowserTabs._setEffectAllowedForDataTransfer;
        tabs.addEventListener('dragover', this, false);

        // Multiselect
        tabs.addEventListener('mousedown', this, true);
        tabs.addEventListener('TabSelect', this, false);

        // Fix up each individual tab for vertical layout, including
        // ones that are opened later on.
        tabs.addEventListener('TabOpen', this, true);
        for (let i=0; i < tabs.childNodes.length; i++) {
            this.initTab(tabs.childNodes[i]);
        }
    },

    initTab: function(aTab) {
        aTab.setAttribute('align', 'stretch');
        aTab.removeAttribute('maxwidth');
        aTab.removeAttribute('minwidth');
        aTab.removeAttribute('width');
        aTab.removeAttribute('flex');
        aTab.maxWidth = 65000;
        aTab.minWidth = 0;
    },

	onTabbarResized: function() {
        var tabs = document.getElementById("tabbrowser-tabs");
        setTimeout(
            function(self) {
                self.prefs.setIntPref('extensions.verticaltabs.width',
                                      tabs.boxObject.width);
            }, 10, this);
	},

    multiSelect: function(aTab) {
        // Clear the multiselect flag from the selected tab
        aTab.setAttribute("multiselect", "true");
    },

    multiSpanSelect: function(aBeginTab, aEndTab) {
        this.clearMultiSelect();
        var tabs = document.getElementById("tabbrowser-tabs");
        var begin = aBeginTab._tPos;
        var end = aEndTab._tPos;
        if (begin > end) {
            [end, begin] = [begin, end];
        }
        for (let i=begin; i <= end; i++) {
            this.multiSelect(tabs.childNodes[i]);
        }
    },

    clearMultiSelect: function() {
        var tabs = document.getElementById("tabbrowser-tabs");
        for (let i=0; i < tabs.childNodes.length; i++ ) {
            tabs.childNodes[i].removeAttribute("multiselect");
        }
    },

    /*** Event handlers ***/

    handleEvent: function(aEvent) {
        switch (aEvent.type) {
        case 'DOMContentLoaded':
            this.init();
            return;
        case 'TabOpen':
            this.onTabOpen(aEvent);
            return;
        case 'TabSelect':
            this.onTabSelect(aEvent);
            return;
        case 'mouseup':
            this.onMouseUp(aEvent);
            return;
        case 'mousedown':
            this.onMouseDown(aEvent);
            return;
        case 'dragover':
            TabbrowserTabs.onDragOver(aEvent);
            return;
        }
    },

    onTabOpen: function(aEvent) {
        this.initTab(aEvent.originalTarget);
    },

    onTabSelect: function(aEvent) {
       aEvent.target.removeAttribute("multiselect");
    },

    onMouseUp: function(aEvent) {
        if (aEvent.target.getAttribute("id") == "verticaltabs-splitter") {
            this.onTabbarResized();
        }
    },

    onMouseDown: function(aEvent) {
        if (aEvent.target.localName != "tab") {
            return;
        }
        if (aEvent.button != 0) {
            return;
        }
        var tabs = document.getElementById("tabbrowser-tabs");

        // Check for Ctrl+click (multiselection).  On the Mac it's
        // Cmd+click which is represented by metaKey.  Ctrl+click won't be
        // possible on the Mac because that would be a right click (button 2)
        if (aEvent.ctrlKey || aEvent.metaKey) {
            this.multiSelect(tabs.tabbrowser.selectedTab);
        } else if (aEvent.shiftKey) {
            this.multiSpanSelect(tabs.tabbrowser.selectedTab, aEvent.target);
        } else {
            this.clearMultiSelect();
        }
    }

};

window.addEventListener("DOMContentLoaded", VerticalTabs, false);

var TabbrowserTabs = {

    _getDropIndex: function(event) {
        var tabs = this.childNodes;
        var tab = this._getDragTargetTab(event);
        // CHANGE for Vertical Tabs: no ltr handling, X -> Y, width -> height
        for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
            if (event.screenY < tabs[i].boxObject.screenY + tabs[i].boxObject.height / 2)
                return i;
        return tabs.length;
    },

    _setEffectAllowedForDataTransfer: function(event) {
        var dt = event.dataTransfer;
        // Disallow dropping multiple items
        if (dt.mozItemCount > 1)
            return dt.effectAllowed = "none";

        var types = dt.mozTypesAt(0);
        var sourceNode = null;
        // tabs are always added as the first type
        if (types[0] == TAB_DROP_TYPE) {
            var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
            if (sourceNode instanceof XULElement &&
                sourceNode.localName == "tab" &&
                (sourceNode.parentNode == this ||
                 (sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
                  sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser"))) {
                if (sourceNode.parentNode == this &&
                    // CHANGE for Vertical Tabs: X -> Y, width -> height
                    (event.screenY >= sourceNode.boxObject.screenY &&
                     event.screenY <= (sourceNode.boxObject.screenY +
                                       sourceNode.boxObject.height))) {
                    return dt.effectAllowed = "none";
                }

                return dt.effectAllowed = "copyMove";
            }
        }

        for (let i = 0; i < this._supportedLinkDropTypes.length; i++) {
            if (types.contains(this._supportedLinkDropTypes[i])) {
                // Here we need to to do this manually
                return dt.effectAllowed = dt.dropEffect = "link";
            }
        }
        return dt.effectAllowed = "none";
    },

    // Calculate the drop indocator's position for vertical tabs.
    // Overwrites what the original 'dragover' event handler does
    // towards the end.
    onDragOver: function(aEvent) {
        var tabs = document.getElementById("tabbrowser-tabs");
        var ind = tabs._tabDropIndicator;
        var newIndex = tabs._getDropIndex(aEvent);
        var rect = tabs.getBoundingClientRect();
        var newMargin;

        if (newIndex == tabs.childNodes.length) {
            let tabRect = tabs.childNodes[newIndex-1].getBoundingClientRect();
            newMargin = tabRect.bottom - rect.top;
        } else {
            let tabRect = tabs.childNodes[newIndex].getBoundingClientRect();
            newMargin = tabRect.top - rect.top;
        }

        newMargin += ind.clientHeight / 2;
        ind.style.MozTransform = "translate(0, " + Math.round(newMargin) + "px)";
        ind.style.MozMarginStart = null;
        ind.style.marginTop = null;
        ind.style.maxWidth = rect.width + "px";
    }

};
<commit_msg>Don't move the selected tab around when we multiselect. It leads to confusing behaviour when doing a span select (Shift+Click)<commit_after>var VerticalTabs = {

    prefs: Components.classes["@mozilla.org/preferences-service;1"]
           .getService(Components.interfaces.nsIPrefBranch)
           .QueryInterface(Components.interfaces.nsIPrefBranch2),

    init: function() {
        window.removeEventListener("DOMContentLoaded", this, false);

        // Move the bottom stuff (statusbar, findbar) in with the
        // tabbrowser.  That way it will share the same (horizontal)
        // space as the brower.  In other words, the bottom stuff no
        // longer extends across the whole bottom of the window.
        var contentbox = document.getElementById("appcontent");
        var bottom = document.getElementById("browser-bottombox");
        contentbox.appendChild(bottom);

        // Move the tabs next to the app content, make them vertical,
        // and restore their width from previous session
        var leftbox = document.getElementById("verticaltabs-box");
        var tabs = document.getElementById("tabbrowser-tabs");
        leftbox.insertBefore(tabs, leftbox.firstChild);
        tabs.orient = "vertical";
        tabs.mTabstrip.orient = "vertical";
        tabs.tabbox.orient = "horizontal"; // probably not necessary
        tabs.setAttribute("width", this.prefs.getIntPref('extensions.verticaltabs.width'));

        // Hook up event handler for splitter so that the width of the
        // tab bar is persisted.
        var splitter = document.getElementById("verticaltabs-splitter");
        splitter.addEventListener('mouseup', this, false);

        // New methods and event handlers for drag'n'drop.  The
        // original tabbrowser naturally makes the assumption that
        // stuff is laid out horizontally.
        tabs._getDropIndex = TabbrowserTabs._getDropIndex;
        tabs._setEffectAllowedForDataTransfer
            = TabbrowserTabs._setEffectAllowedForDataTransfer;
        tabs.addEventListener('dragover', this, false);

        // Multiselect
        tabs.addEventListener('mousedown', this, true);

        // Fix up each individual tab for vertical layout, including
        // ones that are opened later on.
        tabs.addEventListener('TabOpen', this, true);
        for (let i=0; i < tabs.childNodes.length; i++) {
            this.initTab(tabs.childNodes[i]);
        }
    },

    initTab: function(aTab) {
        aTab.setAttribute('align', 'stretch');
        aTab.removeAttribute('maxwidth');
        aTab.removeAttribute('minwidth');
        aTab.removeAttribute('width');
        aTab.removeAttribute('flex');
        aTab.maxWidth = 65000;
        aTab.minWidth = 0;
    },

	onTabbarResized: function() {
        var tabs = document.getElementById("tabbrowser-tabs");
        setTimeout(
            function(self) {
                self.prefs.setIntPref('extensions.verticaltabs.width',
                                      tabs.boxObject.width);
            }, 10, this);
	},

    toggleMultiSelect: function(aTab) {
        if (aTab.selected) {
            return;
        }
        if (aTab.getAttribute("multiselect") == "true") {
            aTab.removeAttribute("multiselect");
        } else {
            aTab.setAttribute("multiselect", "true");
        }
    },

    multiSpanSelect: function(aBeginTab, aEndTab) {
        this.clearMultiSelect();
        var tabs = document.getElementById("tabbrowser-tabs");
        var begin = aBeginTab._tPos;
        var end = aEndTab._tPos;
        if (begin > end) {
            [end, begin] = [begin, end];
        }
        for (let i=begin; i <= end; i++) {
            tabs.childNodes[i].setAttribute("multiselect", "true");
        }
    },

    clearMultiSelect: function() {
        var tabs = document.getElementById("tabbrowser-tabs");
        for (let i=0; i < tabs.childNodes.length; i++ ) {
            tabs.childNodes[i].removeAttribute("multiselect");
        }
    },

    /*** Event handlers ***/

    handleEvent: function(aEvent) {
        switch (aEvent.type) {
        case 'DOMContentLoaded':
            this.init();
            return;
        case 'TabOpen':
            this.onTabOpen(aEvent);
            return;
        case 'mouseup':
            this.onMouseUp(aEvent);
            return;
        case 'mousedown':
            this.onMouseDown(aEvent);
            return;
        case 'dragover':
            TabbrowserTabs.onDragOver(aEvent);
            return;
        }
    },

    onTabOpen: function(aEvent) {
        this.initTab(aEvent.originalTarget);
    },

    onMouseUp: function(aEvent) {
        if (aEvent.target.getAttribute("id") == "verticaltabs-splitter") {
            this.onTabbarResized();
        }
    },

    onMouseDown: function(aEvent) {
        if (aEvent.target.localName != "tab") {
            return;
        }
        if (aEvent.button != 0) {
            return;
        }

        var tabs = document.getElementById("tabbrowser-tabs");
        // Check for Ctrl+click (multiselection).  On the Mac it's
        // Cmd+click which is represented by metaKey.  Ctrl+click won't be
        // possible on the Mac because that would be a right click (button 2)
        if (aEvent.ctrlKey || aEvent.metaKey) {
            this.toggleMultiSelect(aEvent.target);
            aEvent.stopPropagation();
        } else if (aEvent.shiftKey) {
            this.multiSpanSelect(tabs.tabbrowser.selectedTab, aEvent.target);
            aEvent.stopPropagation();
        } else {
            this.clearMultiSelect();
        }
    }

};

window.addEventListener("DOMContentLoaded", VerticalTabs, false);

var TabbrowserTabs = {

    _getDropIndex: function(event) {
        var tabs = this.childNodes;
        var tab = this._getDragTargetTab(event);
        // CHANGE for Vertical Tabs: no ltr handling, X -> Y, width -> height
        for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
            if (event.screenY < tabs[i].boxObject.screenY + tabs[i].boxObject.height / 2)
                return i;
        return tabs.length;
    },

    _setEffectAllowedForDataTransfer: function(event) {
        var dt = event.dataTransfer;
        // Disallow dropping multiple items
        if (dt.mozItemCount > 1)
            return dt.effectAllowed = "none";

        var types = dt.mozTypesAt(0);
        var sourceNode = null;
        // tabs are always added as the first type
        if (types[0] == TAB_DROP_TYPE) {
            var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
            if (sourceNode instanceof XULElement &&
                sourceNode.localName == "tab" &&
                (sourceNode.parentNode == this ||
                 (sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
                  sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser"))) {
                if (sourceNode.parentNode == this &&
                    // CHANGE for Vertical Tabs: X -> Y, width -> height
                    (event.screenY >= sourceNode.boxObject.screenY &&
                     event.screenY <= (sourceNode.boxObject.screenY +
                                       sourceNode.boxObject.height))) {
                    return dt.effectAllowed = "none";
                }

                return dt.effectAllowed = "copyMove";
            }
        }

        for (let i = 0; i < this._supportedLinkDropTypes.length; i++) {
            if (types.contains(this._supportedLinkDropTypes[i])) {
                // Here we need to to do this manually
                return dt.effectAllowed = dt.dropEffect = "link";
            }
        }
        return dt.effectAllowed = "none";
    },

    // Calculate the drop indocator's position for vertical tabs.
    // Overwrites what the original 'dragover' event handler does
    // towards the end.
    onDragOver: function(aEvent) {
        var tabs = document.getElementById("tabbrowser-tabs");
        var ind = tabs._tabDropIndicator;
        var newIndex = tabs._getDropIndex(aEvent);
        var rect = tabs.getBoundingClientRect();
        var newMargin;

        if (newIndex == tabs.childNodes.length) {
            let tabRect = tabs.childNodes[newIndex-1].getBoundingClientRect();
            newMargin = tabRect.bottom - rect.top;
        } else {
            let tabRect = tabs.childNodes[newIndex].getBoundingClientRect();
            newMargin = tabRect.top - rect.top;
        }

        newMargin += ind.clientHeight / 2;
        ind.style.MozTransform = "translate(0, " + Math.round(newMargin) + "px)";
        ind.style.MozMarginStart = null;
        ind.style.marginTop = null;
        ind.style.maxWidth = rect.width + "px";
    }

};
<|endoftext|>