×

Welcome to TagMyCode

Please login or create account to add a snippet.
0
0
 
0
Language: Text
Posted by: Влад Якуб
Added: Mar 1, 2023 8:41 AM
Views: 0
Tags: no tags
  1. (function() {
  2.         'use strict';
  3.  
  4.         function mergeUnique(a, b) {
  5.                 return [...a,...b].reduce((acc, v) => {
  6.                         if(!acc.includes(v))
  7.                                 acc.push(v);
  8.  
  9.                         return acc;
  10.                 }, []);
  11.         }
  12.  
  13.         function filterUnique(a) {
  14.                 return a.filter((e, k, a) => a.indexOf(e) == k);
  15.         }
  16.  
  17.         function hasIntersection(a, b) {
  18.                 return a.some(v => b.includes(v));
  19.         }
  20.  
  21.  
  22.         class QuotaRule {
  23.                 selectors = [];
  24.  
  25.                 constructor(raw = '*') {
  26.                         raw = raw
  27.                                 .trim()
  28.                                 .replace(/[\s]{2,}/g, ' ')
  29.                                 .replace(/([\w\*])\s([\w\*])/g, '$1 -> $2')
  30.                                 .split(' ');
  31.  
  32.                         for(var i = 0; i <= raw.length; i += 2) {
  33.                                 var props = raw[i];
  34.                                 var combinator = raw[i + 1];
  35.  
  36.                                 var selector = new QuotaSelector(props, combinator);
  37.                                 this.selectors.push(selector);
  38.                         }
  39.                 }
  40.  
  41.                 static rawFrom(elem) {
  42.                         var parents = elem.getParents();
  43.                         var baseRawSelector = QuotaSelector.baseRawFrom(elem);
  44.  
  45.                         return parents.reduce((acc, parent) => {
  46.                                 var selector = QuotaSelector.rawFrom(parent);
  47.                                 return `${selector} > ${acc}`;
  48.                         }, baseRawSelector);
  49.                 }
  50.  
  51.                 getSelectors() {
  52.                         return [...this.selectors];
  53.                 }
  54.  
  55.                 getBaseSelector() {
  56.                         return this.selectors[this.selectors.length - 1];
  57.                 }
  58.  
  59.                 toString() {
  60.                         return this.selectors.map(s => String(s)).join(' ');
  61.                 }
  62.         }
  63.  
  64.         class QuotaSelector {
  65.                 prop;
  66.                 values;
  67.                 combinator;
  68.  
  69.                 constructor(raw, combinator = null) {
  70.                         var prop = '(?<prop>[\\w|\\,]+|\\*)';
  71.                         var vals = '(?<vals>[\\w|,|-]+|\\*)?';
  72.                         var operator = '(?<operator>>=|<=|>|<|=|!=)?';
  73.  
  74.                         var re = new RegExp(`^${prop}${operator}${vals}$`);
  75.                         var match = raw.match(re);
  76.  
  77.                         if(!match?.groups)
  78.                                 throw new Error('Cant parse selector rule');
  79.  
  80.                         prop = match.groups.prop;
  81.                         vals = match.groups.vals;
  82.                         operator = match.groups.operator;
  83.  
  84.                         this.prop = new QuotaProp(prop);
  85.                         this.values = new QuotaValues(operator, vals);
  86.                         this.combinator = new QuotaCombinator(combinator);
  87.                 }
  88.  
  89.                 static baseRawFrom(elem) {
  90.                         var prop = QuotaProp.rawFrom(elem);
  91.                         return `${prop}=*`;
  92.                 }
  93.  
  94.                 static rawFrom(elem, combinator = '>') {
  95.                         var prop = QuotaProp.rawFrom(elem);
  96.                         var values = QuotaValues.rawFrom(elem);
  97.  
  98.                         return `${prop}${values}`;
  99.                 }
  100.  
  101.                 isPasses(values, prop) {
  102.                         return this.values.isPasses(values) && this.prop.isPasses(prop);
  103.                 }
  104.  
  105.                 getCombinator() {
  106.                         return this.combinator;
  107.                 }
  108.  
  109.                 getProp() {
  110.                         return this.prop;
  111.                 }
  112.  
  113.                 getValues() {
  114.                         return this.values;
  115.                 }
  116.  
  117.                 toString() {
  118.                         var out = `${String(this.prop)}${String(this.values)}`;
  119.                         if(this.combinator.isEmpty())
  120.                                 return out;
  121.  
  122.                         return `${out} ${String(this.combinator)}`;
  123.                 }
  124.         }
  125.  
  126.         class QuotaProp {
  127.                 prop;
  128.  
  129.                 constructor(prop) {
  130.                         this.prop = prop.split(',');
  131.                 }
  132.  
  133.                 static rawFrom(elem) {
  134.                         return String(elem.getProp());
  135.                 }
  136.  
  137.                 isPasses(prop) {
  138.                         prop = prop.getProp();
  139.                         return this.prop == '*' || prop == '*' || hasIntersection(this.prop, prop);
  140.                 }
  141.  
  142.                 isSame(prop) {
  143.                         var aProp = this.getProp().sort();
  144.                         var bProp = prop.getProp().sort();
  145.  
  146.                         return String(aProp) == String(bProp);
  147.                 }
  148.  
  149.                 getProp() {
  150.                         return [...this.prop];
  151.                 }
  152.  
  153.                 add(prop) {
  154.                         this.prop = mergeUnique(this.prop, prop.getProp());
  155.                         this.prop.sort();
  156.                 }
  157.  
  158.                 isXMin() {
  159.                         return this.prop == 'min';
  160.                 }
  161.  
  162.                 toString() {
  163.                         return String(this.prop);
  164.                 }
  165.         }
  166.  
  167.         class QuotaOperator {
  168.                 operator;
  169.  
  170.                 constructor(raw = '=') {
  171.                         if(raw == '')
  172.                                 raw = '=';
  173.  
  174.                         this.operator = raw;
  175.                 }
  176.  
  177.                 static rawFrom(elem) {
  178.                         var values = elem.getValues();
  179.                         return String(values.getOperator());
  180.                 }
  181.  
  182.                 setRaw(raw) {
  183.                         this.operator = raw;
  184.                 }
  185.  
  186.                 isEqual() {
  187.                         return this.operator === '=';
  188.                 }
  189.  
  190.                 isNotEqual() {
  191.                         return this.operator === '!=';
  192.                 }
  193.  
  194.                 isMore() {
  195.                         return this.operator === '>';
  196.                 }
  197.  
  198.                 isLess() {
  199.                         return this.operator === '<';
  200.                 }
  201.  
  202.                 isSame(operator) {
  203.                         return this.getOperator() === operator.getOperator();
  204.                 }
  205.  
  206.                 getOperator() {
  207.                         return this.operator;
  208.                 }
  209.  
  210.                 toString() {
  211.                         return this.operator;
  212.                 }
  213.         }
  214.  
  215.         class QuotaVals {
  216.                 vals;
  217.  
  218.                 constructor(raw = '*') {
  219.                         raw = raw.split(',');
  220.  
  221.                         this.vals = raw.reduce((acc, val) => {
  222.                                 return mergeUnique(acc, this.parse(val));
  223.                         }, []);
  224.                 }
  225.  
  226.                 static rawFrom(elem) {
  227.                         var values = elem.getValues();
  228.                         return String(values.getVals());
  229.                 }
  230.  
  231.                 parse(raw = '*') {
  232.                         if(typeof raw == 'number')
  233.                                 return [raw];
  234.  
  235.                         if(!isNaN(raw))
  236.                                 return [Number(raw)];
  237.  
  238.                                 var range = [...raw.matchAll(/[^\d]*(\d+)\-(\d+)[^\d]*/ig)];
  239.                                 if(!range.length)
  240.                                         return [raw];
  241.  
  242.                                 var range = range[0];
  243.  
  244.                                 var start = Number(range[1]);
  245.                                 var end = Number(range[2]);
  246.                                 var length = end - start + 1;
  247.  
  248.                         return [...Array(length)].map((val, key) => start + key);
  249.                 }
  250.  
  251.                 getVals() {
  252.                         return [...this.vals];
  253.                 }
  254.  
  255.                 hasIntersection(vals) {
  256.                         var aVals = vals.getVals();
  257.                         var bVals = this.getVals();
  258.  
  259.                         return hasIntersection(aVals, bVals);
  260.                 }
  261.  
  262.                 isConsistency() {
  263.                         var sorted = this.getVals().sort();
  264.  
  265.                         if(sorted.length == 1 || sorted.length == 2)
  266.                                 return false;
  267.  
  268.                         return sorted.every((v, k, arr) => {
  269.                                 if(k == 0)
  270.                                         return true;
  271.  
  272.                                 return v - arr[k-1] == 1;
  273.                         });
  274.                 }
  275.  
  276.                 isPasses(vals) {
  277.                         vals = vals.getVals();
  278.                         return this.vals == '*' || vals == '*' || hasIntersection(this.vals, vals);
  279.                 }
  280.  
  281.                 isSame(vals) {
  282.                         var aVals = this.getVals().sort();
  283.                         var bVals = vals.getVals().sort();
  284.  
  285.                         return String(aVals) == String(bVals);
  286.                 }
  287.  
  288.                 isSingle() {
  289.                         return this.vals.length == 1;
  290.                 }
  291.  
  292.                 isAny() {
  293.                         return this.vals == '*';
  294.                 }
  295.  
  296.                 add(vals) {
  297.                         this.vals = mergeUnique(this.vals, vals.getVals());
  298.                         this.vals.sort();
  299.                 }
  300.  
  301.                 setRaw(raw) {
  302.                         this.vals = this.parse(raw);
  303.                 }
  304.  
  305.                 toFirst() {
  306.                         this.vals = [this.vals[0]];
  307.                 }
  308.  
  309.                 toLast() {
  310.                         this.vals = [this.vals[this.vals.length - 1]];
  311.                 }
  312.  
  313.                 isOnlyInteger() {
  314.                         return this.getVals().every(val => Number.isInteger(val));
  315.                 }
  316.  
  317.                 decrease() {
  318.                         --this.vals[0];
  319.                 }
  320.  
  321.                 increase() {
  322.                         ++this.vals[0];
  323.                 }
  324.  
  325.                 getStart() {
  326.                         return this.getVals().sort((v1, v2) => v1 - v2)[0];
  327.                 }
  328.  
  329.                 getEnd() {
  330.                         return this.getVals().sort((v1, v2) => v1 - v2)[this.vals.length - 1];
  331.                 }
  332.  
  333.                 getPoint() {
  334.                         return this.getStart();
  335.                 }
  336.  
  337.                 getPoints() {
  338.                         return [this.getStart(), this.getEnd()];
  339.                 }
  340.  
  341.                 toString() {
  342.                         if(this.isConsistency())
  343.                                 return `${this.vals[0]}-${this.vals[this.vals.length-1]}`;
  344.  
  345.                         return String(this.vals);
  346.                 }
  347.         }
  348.  
  349.         class QuotaValues {
  350.                 vals;
  351.                 operator;
  352.  
  353.                 constructor(operator = '=', vals = '*') {
  354.                         this.vals = new QuotaVals(vals);
  355.                         this.operator = new QuotaOperator(operator);
  356.  
  357.                         if(this.operator.isEqual() ||
  358.                                  this.operator.isNotEqual() ||
  359.                                  this.operator.isLess() ||
  360.                                  this.operator.isMore())
  361.                                 return;
  362.  
  363.                         if(!this.vals.isOnlyInteger())
  364.                                 return;
  365.  
  366.                         var raw = this.operator.getOperator();
  367.                         if(raw != '>=' && raw != '<=')
  368.                                 throw new Error('Unknown operator val');
  369.  
  370.                         if(!this.vals.isSingle()) {
  371.                                 if(raw === '>=')
  372.                                         this.vals.toFirst();
  373.                                 else
  374.                                         this.vals.toLast();
  375.                         }
  376.  
  377.                         if(raw == '>=') {
  378.                                 this.vals.decrease();
  379.                                 this.operator.setRaw('>');
  380.                         }
  381.                         else {
  382.                                 this.vals.increase();
  383.                                 this.operator.setRaw('<');
  384.                         }
  385.                 }
  386.  
  387.                 static rawFrom(elem) {
  388.                         return `${QuotaOperator.rawFrom(elem)}${QuotaVals.rawFrom(elem)}`;
  389.                 }
  390.  
  391.                 isPasses(values) {
  392.                         var aVals = values.getVals();
  393.                         var bVals = this.getVals();
  394.                         var aOper = values.getOperator();
  395.                         var bOper = this.getOperator();
  396.  
  397.                         if(aVals.isAny() || bVals.isAny())
  398.                                 return true;
  399.  
  400.                         if(aOper.isEqual() && bOper.isEqual())
  401.                                 return aVals.hasIntersection(bVals);
  402.  
  403.                         if(aOper.isNotEqual() && bOper.isNotEqual())
  404.                                 return aVals.isSame(bVals);
  405.  
  406.                         if(aOper.isNotEqual() || bOper.isNotEqual())
  407.                                 throw new Error('Не умею находить пересечение "!=" с другими операторами');
  408.  
  409.                         if(aOper.isMore() && bOper.isMore() ||
  410.                                  aOper.isLess() && bOper.isLess())
  411.                                 return true;
  412.  
  413.                         if(aOper.isMore() && bOper.isLess() ||
  414.                                  aOper.isLess() && bOper.isMore()) {
  415.                                 var aData = [aOper, aVals];
  416.                                 var bData = [bOper, bVals];
  417.  
  418.                                 var moreData = [aData, bData].find(d => d[0].isMore());
  419.                                 var lessData = [aData, bData].find(d => d[0].isLess());
  420.  
  421.                                 var morePoint = moreData[1].getPoint();
  422.                                 var lessPoint = lessData[1].getPoint();
  423.  
  424.                                 return morePoint < lessPoint;
  425.                         }
  426.  
  427.                         if(aOper.isEqual() || bOper.isEqual())
  428.                                 return this.getHoleLength(values) <= 0;
  429.  
  430.                         throw new Error('Unknown operator val');
  431.                 }
  432.  
  433.                 getVals() {
  434.                         return this.vals;
  435.                 }
  436.  
  437.                 getOperator() {
  438.                         return this.operator;
  439.                 }
  440.  
  441.                 isSame(values) {
  442.                         var aVals = values.getVals();
  443.                         var bVals = this.getVals();
  444.                         var aOper = values.getOperator();
  445.                         var bOper = this.getOperator();
  446.  
  447.                         return aOper.isSame(bOper) && aVals.isSame(bVals);
  448.                 }
  449.  
  450.                 getHoleLength(values) {
  451.                         var aVals = values.getVals();
  452.                         var bVals = this.getVals();
  453.                         var aOper = values.getOperator();
  454.                         var bOper = this.getOperator();
  455.  
  456.                         var aData = [aOper, aVals];
  457.                         var bData = [bOper, bVals];
  458.  
  459.                         var rangeData = [aData, bData].find(d => d[0].isEqual());
  460.                         var directionData = [aData, bData].find(d => !d[0].isEqual());
  461.  
  462.                         var rangeVals = rangeData[1];
  463.                         var directionVals = directionData[1];
  464.                         var directionOperator = directionData[0];
  465.  
  466.                         var rangeStart = rangeVals.getStart();
  467.                         var rangeEnd = rangeVals.getEnd();
  468.                         var directionPoint = directionVals.getPoint();
  469.  
  470.                         if(directionOperator.isMore())
  471.                                 return (directionPoint + 1) - rangeEnd;
  472.                         if(directionOperator.isLess())
  473.                                 return rangeStart - (directionPoint - 1);
  474.                 }
  475.  
  476.                 add(values) {
  477.                         var aVals = values.getVals();
  478.                         var aOper = values.getOperator();
  479.                         var bVals = this.getVals();
  480.                         var bOper = this.getOperator();
  481.  
  482.                         if(aOper.isEqual() && bOper.isEqual())
  483.                                 return this.vals.add(values.getVals());
  484.  
  485.                         if(aOper.isNotEqual() && bOper.isNotEqual())
  486.                                 return;
  487.  
  488.                         if(aOper.isNotEqual() || bOper.isNotEqual())
  489.                                 throw new Error('Не умею сливать "!=" с другими операторами');
  490.  
  491.                         if(aOper.isMore() && bOper.isMore()) {
  492.                                 var aPoint = aVals.getPoint();
  493.                                 var bPoint = bVals.getPoint();
  494.  
  495.                                 var less = aPoint < bPoint ? aPoint : bPoint;
  496.  
  497.                                 this.vals.setRaw(less);
  498.                                 this.operator.setRaw('>');
  499.                                 return;
  500.                         }
  501.  
  502.                         if(aOper.isLess() && bOper.isLess()) {
  503.                                 var aPoint = aVals.getPoint();
  504.                                 var bPoint = bVals.getPoint();
  505.  
  506.                                 var more = aPoint > bPoint ? aPoint : bPoint;
  507.  
  508.                                 this.vals.setRaw(more);
  509.                                 this.operator.setRaw('<');
  510.                                 return;
  511.                         }
  512.  
  513.                         if(aOper.isEqual() || bOper.isEqual()) {
  514.                                 var range = [this, values].find(v => v.getOperator().isEqual());
  515.                                 var direction = [this, values].find(v => !v.getOperator().isEqual());
  516.  
  517.                                 var dVals = direction.getVals();
  518.                                 var rVals = range.getVals();
  519.  
  520.                                 var dPoint = dVals.getPoint();
  521.  
  522.                                 if(aOper.isLess() || bOper.isLess()) {
  523.                                         var rEnd = rVals.getEnd() + 1;
  524.                                         var resultPoint = dPoint > rEnd ? dPoint : rEnd;
  525.  
  526.                                         this.vals.setRaw(resultPoint);
  527.                                         this.operator.setRaw('<');
  528.                                         return;
  529.                                 }
  530.                                 if(aOper.isMore() || bOper.isMore()) {
  531.                                         var rStart = rVals.getStart() - 1;
  532.                                         var resultPoint = dPoint < rStart ? dPoint : rStart;
  533.  
  534.                                         this.vals.setRaw(resultPoint);
  535.                                         this.operator.setRaw('>');
  536.                                         return;
  537.                                 }
  538.                         }
  539.  
  540.                         throw new Error('Unknow add values type');
  541.                 }
  542.  
  543.                 toString() {
  544.                         return `${String(this.operator)}${String(this.vals)}`;
  545.                 }
  546.         }
  547.  
  548.         class QuotaCombinator {
  549.                 combinator;
  550.  
  551.                 constructor(combinator = null) {
  552.                         this.combinator = combinator;
  553.                 }
  554.  
  555.                 isDirectParent() {
  556.                         return this.combinator == '>';
  557.                 }
  558.  
  559.                 isAnyParent() {
  560.                         return this.combinator == '->';
  561.                 }
  562.  
  563.                 isSiblings() {
  564.                         return this.combinator == '~';
  565.                 }
  566.  
  567.                 isEmpty() {
  568.                         return this.combinator === null;
  569.                 }
  570.  
  571.                 toString() {
  572.                         return this.combinator;
  573.                 }
  574.         }
  575.  
  576.         class QuotaLimit {
  577.                 limit;
  578.  
  579.                 constructor(raw) {
  580.                         this.limit = raw === '' ? '' : Number(raw);
  581.                 }
  582.  
  583.                 add(limit) {
  584.                         if(this.isUnlimit() || limit.isUnlimit())
  585.                                 return this.limit = '';
  586.  
  587.                         this.limit += limit.getLimit();
  588.                 }
  589.  
  590.                 getLimit() {
  591.                         return this.limit;
  592.                 }
  593.  
  594.                 isUnlimit() {
  595.                         return this.limit === '';
  596.                 }
  597.         }
  598.  
  599.         class QuotaNode {
  600.                 $;
  601.                 level;
  602.                 parent;
  603.                 children;
  604.  
  605.                 constructor($elem, parent = null) {
  606.                         this.$ = $elem;
  607.                         this.parent = parent;
  608.                         this.level = this.parent ? this.parent.getLevel() + 1 : 0;
  609.  
  610.                         var children = this.$
  611.                                 .find('.q-inside').first()
  612.                                 .find('> .q-block');
  613.  
  614.                         this.children = [...children].map(e => new QuotaNode($(e), this));
  615.  
  616.                         this.prop = new QuotaProp(this.$prop.text());
  617.                         this.limit = new QuotaLimit(this.$limit.text());
  618.                         this.values = new QuotaValues(this.$operator.text(), this.$vals.text());
  619.                 }
  620.  
  621.                 remove() {
  622.                         this.$.remove();
  623.                         this.children = [];
  624.  
  625.                         if(this.parent)
  626.                                 this.parent.removeChild(this);
  627.                 }
  628.  
  629.                 removeChild(child) {
  630.                         this.children = this.children.filter(c => c != child);
  631.                 }
  632.  
  633.                 get $prop() {
  634.                         return this.$.find('.q-properties').first().find('div');
  635.                 }
  636.  
  637.                 get $limit() {
  638.                         return this.$.find('.q-limit').first().find('div');
  639.                 }
  640.  
  641.                 get $vals() {
  642.                         return this.$.find('.q-values').first().find('div');
  643.                 }
  644.  
  645.                 get $operator() {
  646.                         return this.$.find('.q-operator').first().find('div');
  647.                 }
  648.  
  649.                 hasChildren() {
  650.                         return this.children.length;
  651.                 }
  652.  
  653.                 getAllChildren() {
  654.                         return this.children.reduce((acc, child, k) => {
  655.                                 acc.push(child, ...child.getAllChildren());
  656.                                 return acc;
  657.                         }, []);
  658.                 }
  659.  
  660.                 getParents() {
  661.                         if(this.hasParent())
  662.                                 return [this.parent, ...this.parent.getParents()];
  663.  
  664.                         return [];
  665.                 }
  666.  
  667.                 getValues() {
  668.                         return this.values;
  669.                 }
  670.  
  671.                 getProp() {
  672.                         return this.prop;
  673.                 }
  674.  
  675.                 getLimit() {
  676.                         return this.limit;
  677.                 }
  678.  
  679.                 getParent() {
  680.                         return this.parent;
  681.                 }
  682.  
  683.                 getLevel() {
  684.                         return this.level;
  685.                 }
  686.  
  687.                 hasParent() {
  688.                         return !!this.parent && !this.parent.isRoot();
  689.                 }
  690.  
  691.                 getChildren() {
  692.                         return [...this.children];
  693.                 }
  694.  
  695.                 addLimit(limit) {
  696.                         if(this.prop.isXMin())
  697.                                 return;
  698.  
  699.                         this.limit.add(limit);
  700.                         this.$limit.text(this.limit.getLimit());
  701.                 }
  702.  
  703.                 addProp(prop) {
  704.                         this.prop.add(prop);
  705.                         this.$prop.text(String(this.prop));
  706.                 }
  707.  
  708.                 addValues(values) {
  709.                         this.values.add(values);
  710.  
  711.                         var strVals = String(this.values.getVals());
  712.                         var strOperator = String(this.values.getOperator());
  713.  
  714.                         this.$vals.text(strVals);
  715.                         this.$operator.text(strOperator);
  716.                 }
  717.  
  718.                 isRoot() {
  719.                         return this.$.is('.midbox');
  720.                 }
  721.         }
  722.  
  723.  
  724.         class QuotaMergeController {
  725.                 constructor() {
  726.                         this.root = null;
  727.                 }
  728.  
  729.                 init() {
  730.                         this.root = new QuotaNode($('.midbox'));
  731.                 }
  732.  
  733.                 mergeByRule(rule) {
  734.                         if(!this.root)
  735.                                 this.init();
  736.  
  737.                         var rule = new QuotaRule(rule);
  738.                         var elems = this.root.getAllChildren();
  739.                         var fitElems = QuotaSearchController.filter(elems, rule);
  740.  
  741.                         if(!fitElems)
  742.                                 return;
  743.  
  744.                         this._mergeSiblingsByLevels(fitElems);
  745.  
  746.                         this.root = null;
  747.                 }
  748.  
  749.                 toggleSelectedView($view) {
  750.                         $view.toggleClass('selected-for-merge');
  751.  
  752.                         if($view.is('.selected-for-merge'))
  753.                                 $view.css({fontWeight: 'bold'});
  754.                         else
  755.                                 $view.css({fontWeight: 'inherit'});
  756.                 }
  757.  
  758.                 mergeSiblingsToView($view) {
  759.                         this.init();
  760.  
  761.                         var elem = QuotaSearchController.findByView(this.root, $view);
  762.                         var rule = QuotaRule.rawFrom(elem);
  763.  
  764.                         this.mergeByRule(rule);
  765.                 }
  766.  
  767.                 mergeSelected() {
  768.                         this.init();
  769.  
  770.                         var selected = [];
  771.                         $(".selected-for-merge").each(function() {
  772.                                 selected.push(QuotaSearchController.findByView(quotaMerge.root, $(this)));
  773.                         });
  774.  
  775.                         this._mergeSiblingsByLevels(selected);
  776.                         this._clearSelected();
  777.                 }
  778.  
  779.                 clickHandler(event) {
  780.                         event.stopPropagation();
  781.  
  782.                         if(!event.altKey)
  783.                                 quotaMerge._clearSelected();
  784.  
  785.                         if(event.altKey)
  786.                                 quotaMerge.toggleSelectedView($(this));
  787.                         else if(event.ctrlKey)
  788.                                 quotaMerge.mergeSiblingsToView($(this));
  789.                 }
  790.  
  791.                 keyHandler(event) {
  792.                         if(event.code == "KeyQ" && event.altKey)
  793.                                 quotaMerge.mergeSelected();
  794.                 }
  795.  
  796.                 _clearSelected() {
  797.                         $('.selected-for-merge').each(function() {
  798.                                 $(this).removeClass('selected-for-merge');
  799.                                 $(this).css({fontWeight: 'inherit'});
  800.                         })
  801.                 }
  802.  
  803.                 _mergeSiblingsByLevels(elems) {
  804.                         var levels = elems.reduce((acc, elem) => {
  805.                                 var level = elem.getLevel();
  806.                                 var oldElems = acc[level];
  807.  
  808.                                 if(!oldElems) {
  809.                                         acc[level] = [elem];
  810.                                         return acc;
  811.                                 }
  812.  
  813.                                 acc[level] = [...oldElems, elem];
  814.                                 return acc;
  815.                         }, []).reverse().filter(l => l);
  816.  
  817.                         levels.forEach(this._mergeSiblings.bind(this));
  818.                 }
  819.  
  820.                 _mergeSiblings(elems) {
  821.                         var parents = new Map();
  822.  
  823.                         elems.forEach(elem => {
  824.                                 var parent = elem.getParent();
  825.                                 var oldElems = parents.get(parent);
  826.  
  827.                                 if(!oldElems)
  828.                                         return parents.set(parent, [elem]);
  829.  
  830.                                 parents.set(parent, [...oldElems, elem]);
  831.                         });
  832.  
  833.                         parents.forEach((mergeChildren, parent) => {
  834.                                 if(mergeChildren.length == 1)
  835.                                         return;
  836.  
  837.                                 var container = mergeChildren[0];
  838.                                 mergeChildren.forEach(child => {
  839.                                         if(child == container)
  840.                                                 return;
  841.  
  842.                                         this._mergeElems(child, container);
  843.                                 });
  844.                         });
  845.                 }
  846.  
  847.                 _canMergeChildren(a, b) {
  848.                         var aChildren = a.getChildren();
  849.                         var bChildren = b.getChildren();
  850.  
  851.                         if(aChildren.length != bChildren.length)
  852.                                 return;
  853.  
  854.                         return aChildren.every((aChild, k) => {
  855.                                 var bChild = bChildren[k];
  856.  
  857.                                 if(!this._canStrictMerge(aChild, bChild))
  858.                                         return;
  859.  
  860.                                 return this._canMergeChildren(aChild, bChild);
  861.                         });
  862.                 }
  863.  
  864.                 _mergeElems(from, to) {
  865.                         if(!this._canMergeChildren(from, to))
  866.                                 return;
  867.  
  868.                         if(!this._canMergeVals(from, to))
  869.                                 return;
  870.  
  871.                         var fromChildren = from.getChildren();
  872.                         var toChildren = to.getChildren();
  873.  
  874.                         fromChildren.forEach((fromChild, k) => {
  875.                                 var toChild = toChildren[k];
  876.                                 this._mergeElems(fromChild, toChild);
  877.                         });
  878.  
  879.                         to.addLimit(from.getLimit());
  880.                         to.addProp(from.getProp());
  881.                         to.addValues(from.getValues());
  882.  
  883.                         from.remove();
  884.                 }
  885.  
  886.                 _canStrictMerge(a, b) {
  887.                         var aProp = a.getProp();
  888.                         var bProp = b.getProp();
  889.                         var aValues = a.getValues();
  890.                         var bValues = b.getValues();
  891.  
  892.                         return aProp.isSame(bProp) && aValues.isSame(bValues);
  893.                 }
  894.  
  895.                 _canMergeVals(a, b) {
  896.                         var aValues = a.getValues();
  897.                         var bValues = b.getValues();
  898.  
  899.                         var aVals = aValues.getVals();
  900.                         var bVals = bValues.getVals();
  901.                         var aOper = aValues.getOperator();
  902.                         var bOper = bValues.getOperator();
  903.  
  904.                         if(aOper.isEqual() && bOper.isEqual())
  905.                                 return true;
  906.  
  907.                         if(aOper.isNotEqual() && bOper.isNotEqual())
  908.                                 return aVals.isSame(bVals);
  909.  
  910.                         if(aOper.isNotEqual() || bOper.isNotEqual())
  911.                                 throw new Error('Не умею сливать в одну ячейку "!=" с другими операторами');
  912.  
  913.                         if(aOper.isMore() && bOper.isMore() ||
  914.                                  aOper.isLess() && bOper.isLess())
  915.                                 return true;
  916.  
  917.                         if(aOper.isMore() && bOper.isLess() ||
  918.                                  bOper.isMore() && aOper.isLess())
  919.                                 return false;
  920.  
  921.                         if(aOper.isEqual() || bOper.isEqual())
  922.                                 return aValues.getHoleLength(bValues) <= 1;
  923.  
  924.                         throw new Error('Unknown operator val');
  925.                 }
  926.         }
  927.  
  928.         class QuotaSearchController {
  929.                 static filter(elems, rule) {
  930.                         var nodes = elems.map(elem => new QuotaSearchNode(elem));
  931.                         var selectors = rule.getSelectors().reverse();
  932.  
  933.                         selectors.forEach(selector => {
  934.                                 nodes = nodes.reduce((acc, node) => {
  935.                                         acc.push(...node.findElemsBySelector(selector));
  936.                                         return acc;
  937.                                 }, []);
  938.                         });
  939.  
  940.                         var bases = nodes.map(node => node.getBase());
  941.                         return filterUnique(bases);
  942.                 }
  943.  
  944.                 static findByView(elem, $view) {
  945.                         if(elem.$[0] == $view[0])
  946.                                 return elem;
  947.  
  948.                         var children = elem.getChildren();
  949.                         return children.reduce((acc, child) => {
  950.                                 var result = QuotaSearchController.findByView(child, $view);
  951.                                 if(result)
  952.                                         acc = result;
  953.  
  954.                                 return acc;
  955.                         }, null);
  956.                 }
  957.         }
  958.  
  959.         class QuotaSearchNode {
  960.                 elem;
  961.                 prev;
  962.  
  963.                 constructor(elem, prev = null) {
  964.                         this.elem = elem;
  965.                         this.prev = prev;
  966.                 }
  967.  
  968.                 getBase() {
  969.                         var current = this;
  970.                         while(current.prev)
  971.                                 current = current.prev;
  972.  
  973.                         return current.elem;
  974.                 }
  975.  
  976.                 findElemsBySelector(selector) {
  977.                         var out = [];
  978.                         var combinator = selector.getCombinator();
  979.  
  980.                         if(combinator.isDirectParent())
  981.                                 out = this.findDirectParents(selector);
  982.                         else if(combinator.isAnyParent())
  983.                                 out = this.findAnyParents(selector);
  984.                         else if(combinator.isEmpty())
  985.                                 out = this.checkSelf(selector);
  986.                         else if(combinator.isSiblings())
  987.                                 out = this.findSiblings(selector);
  988.                         else
  989.                                 throw new Error('Unknown combinator');
  990.  
  991.                         return out.map(elem => new QuotaSearchNode(elem, this));
  992.                 }
  993.  
  994.                 findAnyParents(selector) {
  995.                         var out = [];
  996.                         var parent, prop, values;
  997.                         var elem = this.elem;
  998.  
  999.                         while(elem.hasParent()) {
  1000.                                 parent = elem.getParent();
  1001.                                 prop = parent.getProp();
  1002.                                 values = parent.getValues();
  1003.  
  1004.                                 if(selector.isPasses(values, prop))
  1005.                                         out.push(parent);
  1006.  
  1007.                                 elem = elem.getParent();
  1008.                         }
  1009.  
  1010.                         return out;
  1011.                 }
  1012.  
  1013.                 checkSelf(selector) {
  1014.                         var elem = this.elem;
  1015.                         var prop = elem.getProp();
  1016.                         var values = elem.getValues();
  1017.  
  1018.                         return selector.isPasses(values, prop) ? [elem] : [];
  1019.                 }
  1020.  
  1021.                 findDirectParents(selector) {
  1022.                         var elem = this.elem;
  1023.                         var parent = elem.getParent();
  1024.                         if(!parent)
  1025.                                 return [];
  1026.  
  1027.                         var values = parent.getValues();
  1028.                         var prop = parent.getProp();
  1029.  
  1030.                         return selector.isPasses(values, prop) ? [parent] : [];
  1031.                 }
  1032.  
  1033.                 findSiblings(selector) {
  1034.                         var elem = this.elem;
  1035.                         var parent = elem.getParent();
  1036.                         if(!parent)
  1037.                                 return [];
  1038.  
  1039.                         var children = parent.getChildren();
  1040.  
  1041.                         return children.reduce((acc, child) => {
  1042.                                 var prop = child.getProp();
  1043.                                 var values = child.getValues();
  1044.  
  1045.                                 if(selector.isPasses(values, prop))
  1046.                                         acc.push(child);
  1047.  
  1048.                                 return acc;
  1049.                         }, []);
  1050.                 }
  1051.         }
  1052.  
  1053.         window.quotaMerge = new QuotaMergeController();
  1054.  
  1055.         $(document).on('keyup', quotaMerge.keyHandler);
  1056.         $(document).on('click', '.q-block', quotaMerge.clickHandler);
  1057. })();
  1058.