Widget:PageFilterJS

From Granblue Fantasy Wiki
Jump to navigation Jump to search
<script type="application/javascript">
// Widget:PageFilterJS
(function() {
  const addEventForChild = function(parent, eventName, childSelector, callback){
    parent.addEventListener(eventName, function(event){
      const clickedElement = event.target;
      const matchingChild = clickedElement.closest(childSelector);
      if (matchingChild)
        callback(event, matchingChild);
    });
  };
  const getPageFilterGroup = function(element) {
    if (element.pageFilterGroupID)
      return element.pageFilterGroupID;
    for (let e = element; e; e = e.parentElement)
      if (e.dataset && e.dataset.pageFilterGroupId)
        return element.pageFilterGroupID = e.dataset.pageFilterGroupId;
    return element.pageFilterGroupID = 'g0';
  };

  const getFilterElement = function(filterID, groupID) {
    for (const e of document.querySelectorAll('.page-filter-group-' + filterID)) {
      if (getPageFilterGroup(e.parentElement) == groupID)
        return e;
    }
    // TODO: remove this fallback to element ID
    var eid = document.getElementById('filter-'+ filterID);
    if (eid && getPageFilterGroup(eid) == groupID) return eid;
    
    return null;
  };

  const getFilter = function(name, groupID) {
    const filterElement = getFilterElement(name, groupID);
    const filters = filterElement ? filterElement.querySelectorAll("label.mw-ui-progressive") : "";
    if (filters.length <= 0)
      return false;

    const result = [];
    for (const filter of filters) {
      if (filter.dataset.value === '*')
        return true;
      const values = filter.dataset.value.split(';');
      for (const value of values) {
        result.push(value);
      };
    }
    return result;
  };

  const filterDefinitions = [
    ['Owned', 'short-id', ['All','*', 'Yes','true', 'No','false']],
    ['Rarity', 'filter-rarity', ['All','*', 'SSR','ssr', 'SR','sr', 'R','r']],
    ['Element', 'filter-element', ['All','*', 'Fire','fire', 'Water','water', 'Earth','earth', 'Wind','wind', 'Light','light', 'Dark','dark', 'Any','any']],
    ['Race', 'filter-race', ['All','*', 'Human','human', 'Erune','erune', 'Draph','draph', 'Harvin','harvin', 'Primal','primal', 'Unknown','unknown', 'Other','other']],
    ['Specialty', 'filter-weapon', ['All','*', 'Sabre','sabre', 'Dagger','dagger', 'Spear','spear', 'Axe','axe', 'Staff','staff', 'Gun','gun', 'Melee','melee', 'Bow','bow', 'Harp','harp', 'Katana','katana']],
    ['Weapon Type', 'filter-type', ['All','*', 'Sabre','sabre', 'Dagger','dagger', 'Spear','spear', 'Axe','axe', 'Staff','staff', 'Gun','gun', 'Melee','melee', 'Bow','bow', 'Harp','harp', 'Katana','katana']],
    ['Series', 'filter-series', ['All','*','None','none','Vintage','vintage','Epic','epic','Vyrmament','vyrmament','Olden Primal','olden primal','Ultima','ultima','Cosmos','cosmos','Superlative','superlative','Sephira','sephira','Seraphic','seraphic','Bahamut','bahamut','Primal','primal','Omega','omega','Grand','grand','Regalia','regalia','Rose','rose','Relics','relic','Class Champion','ccw','Rusted','rusted','Beast','beast','Revenant','revenant','Replicas','replica','Xeno','xeno','Hollowsky','hollowsky','Dark Opus','dark opus','Astral','astral','Draconic','draconic','Weapons of Eternal Splendor','splendor','Ancestral','ancestral','New World Foundation','newworld','Upgraders','upgrader','Eternals','eternals','Evokers','evokers','12 Generals','12generals','Valentine','valentine','Summer','summer','Halloween','halloween','Holiday','holiday','Yukata','yukata','Fantasy','fantasy','Tie-in','tie-in']]
  ];

  const addFilterUI = function(container) {
    if (!container) return;
    let filterGroup = getPageFilterGroup(container);
    for (const ex of document.querySelectorAll('[data-extra-filter]')) {
        if (ex.dataset && ex.dataset.extraFilter && ex.dataset.extraFilter.match(/^[a-z:A-Z0-9_ \-*()]*$/) && getPageFilterGroup(ex) == filterGroup) {
            let filterEntry = ex.dataset.extraFilter.split(':');
            if (ex.dataset.filterType && ex.dataset.filterType == 'dynamic') {
                let dynamicFilters = ['All', '*'];
                let kv = {};
                for (let ex2 of document.querySelectorAll('[data-'+filterEntry[1]+']')) {
                    let ftags = ex2.dataset[filterEntry[1]].split(/\s*,\s*/);
                    for(let ftag of ftags) {
                        if (kv[ftag]) { continue; }
                        let fname = ftag.replaceAll('-',' ').trim();
                        if (''==fname) { continue; }
                        fname = fname[0].toUpperCase() + fname.slice(1);
                        kv[ftag] = fname;
                    }
                }
                for (let tag in kv) {
                    dynamicFilters.push(kv[tag]);
                    dynamicFilters.push(tag);
                }
                filterEntry[2] = dynamicFilters;
            } else {
                filterEntry[2] = filterEntry.slice(2);
            }
            filterDefinitions.push(filterEntry.slice(0,3));
        }
    }
    let skipFilters = {}, whitelistFilters = false;
    if (container.dataset.skipFilters) {
      for (const f of container.dataset.skipFilters.match(/[^, ]+/g)) {
        skipFilters[f] = true;
      }
    }
    if (container.dataset.onlyFilter) {
      whitelistFilters = {};
      for (const f of container.dataset.onlyFilter.match(/[^, ]+/g)) {
        whitelistFilters[f] = true;
      }
    }
    let hasFilters = false;
    for (let i = 0, g; g = filterDefinitions[i]; i++) {
      let groupName = g[0], groupData = g[1], groupList = g[2];
      if (skipFilters[groupData] || (whitelistFilters && !whitelistFilters[groupData])) continue;
      if (getFilterElement(groupData, filterGroup)) continue;
      if (groupData == 'short-id' && !(window.CollectionTrackerState && window.CollectionTrackerState.hasSavedData())) continue;
      const entries = document.querySelectorAll('[data-' + groupData + ']');
      if (entries.length <= 1) continue;
      const dataKey = groupData.replace(/-([a-z])/g, function(_, a) { return a.toUpperCase(); } );
      let hasEntry = {}, hasDistinctValues = false, firstValue = entries[0].dataset[dataKey];
      if (groupData != 'short-id') {
        for (const entry of entries) {
          if (getPageFilterGroup(entry) != filterGroup) continue;
          let ev = entry.dataset[dataKey];
          if (!ev) continue;
          if (ev != firstValue) hasDistinctValues = true;
          for (const fv of ev.matchAll(/[^,]+/g)) {
            hasEntry[fv[0].trim()] = true;
          }
        }
        if (!hasDistinctValues) continue;
        hasEntry['*'] = true;
      }

      let div = document.createElement('div');
      div.className = "mw-ui-button-group page-filter-group page-filter-group-" + groupData;
      let label = document.createElement('label');
      label.className = "mw-ui-button mw-ui-disabled label";
      label.appendChild(document.createTextNode(groupName));
      div.appendChild(label);
      let opts = document.createElement('div');
      opts.className = "mw-ui-button-group items";
      div.appendChild(opts);
      let hasOptions = true;
      for (let i = 0; i < groupList.length; i+=2) {
        if (!hasEntry[groupList[i+1]] && groupData != 'short-id') continue;
        let b = document.createElement('label');
        b.className = "mw-ui-button";
        b.dataset.value = groupList[i+1];
        b.appendChild(document.createTextNode(groupList[i]));
        opts.appendChild(b);
        hasOptions = hasOptions || (groupList[i+1] != '*');
      }
      if (!hasOptions) continue;
      if (hasFilters) {
        let clr = document.createElement('p');
        clr.className = 'visualClear';
        clr.style.height = '1px';
        container.appendChild(clr);
      }
      container.appendChild(div);
      hasFilters = true;
    }
    let tr = document.querySelector('tr');
    if (tr) {
      let ov = tr.style.visibility, oh = tr.offsetHeight;
      tr.style.visibility = 'collapse';
      if (tr.offsetHeight >= oh && oh > 0) {
        var style = document.createElement('style');
        style.appendChild(document.createTextNode('tr.hide {display: none}'));
        document.head.appendChild(style);
      }
      tr.style.visibility = ov;
    }
  };

  const refilter = function(updateGroup) {
    const CollectionTrackerState = window.CollectionTrackerState;
    console.time('check filters');
    const filters = {}, filterDataKeys = {};
    let elementSelectors = [];
    for (const fg of filterDefinitions) {
      const filterID = fg[1];
      const val = getFilter(filterID, updateGroup);
      if (val !== false) {
        elementSelectors.push('[data-' + filterID + ']');
        filters[filterID] = val;
        filterDataKeys[filterID] = filterID.replace(/-([a-z])/g, function(_, a) { return a.toUpperCase(); } )
      }
    }
    const filterOwned2 = typeof filters["short-id"] == "object" ? filters["short-id"][0] === 'true' : null;

    if (elementSelectors.length <= 0)
      return;

    const elementSelector = elementSelectors.join(',')
    console.timeEnd('check filters');
    console.time('select elements');
    const elements = document.querySelectorAll(elementSelector);
    console.timeEnd('select elements');
    console.time('update elements');
    for (const element of elements) {
      if (getPageFilterGroup(element) != updateGroup) continue;
      let visible = true;
      for (let fkey in filters) {
        let fval = filters[fkey];
        let ev = element.dataset[filterDataKeys[fkey]];
        if (typeof fval != 'object') continue;
        if (typeof ev == 'undefined') continue;
        if (fkey == 'short-id') {
          if (CollectionTrackerState.isObtained(ev, element.dataset.type) == filterOwned2) continue;
        } else {
          if (element.dataset[fkey] === '*') continue;
          if (fval.indexOf(ev) >= 0) continue;
          if (ev && ev.indexOf(",")) {
            let found = false;
            for (let ev2data of ev.split(/\s*,\s*/)) {
              if (fval.indexOf(ev2data) >= 0) {
                found = true;
                break;
              }
            }
            if (found) continue;
          }
        }
        visible = false;
        break;
      }
      element.classList.toggle('hide', !visible);
    }
    console.timeEnd('update elements');
    console.time('update filterable dividers');
    let rules = document.querySelectorAll('.filterable-divider');
    for (let i = 0, j; i < rules.length; i++) {
      let p = rules[i].parentNode;
      for (j = 0; j < i; j++) {
        if (p == rules[j].parentNode) {
          break;
        }
      }
      if (j < i) continue;
      if (getPageFilterGroup(p) != updateGroup) continue;
      let hasVisibleContent = false, lastVisibleDivider = false, lastHiddenDivider = false;
      for (let e = p.firstElementChild; e; e = e.nextElementSibling) {
        if (e.classList.contains('filterable-divider')) {
          e.classList.toggle('hide', !hasVisibleContent);
          lastVisibleDivider = hasVisibleContent ? e : lastVisibleDivider;
          lastHiddenDivider = hasVisibleContent ? false : e;
          hasVisibleContent = false;
        } else if (!hasVisibleContent && !e.classList.contains('hide')) {
          if (lastVisibleDivider && lastHiddenDivider) {
            lastVisibleDivider.classList.toggle('hide', true);
            lastHiddenDivider.classList.toggle('hide', false);
            lastVisibleDivider = lastHiddenDivider;
            lastHiddenDivider = false;
          }
          hasVisibleContent = true;
        }
      }
      if (!hasVisibleContent && lastVisibleDivider) {
        lastVisibleDivider.classList.toggle('hide', true);
      }
    }
    console.timeEnd('update filterable dividers');
  };

  // Generate filter UI
  // TODO: remove ID variant
  document.querySelectorAll('#page-filter-container, .page-filter-container').forEach(addFilterUI);

  // page setup below this point
  document.querySelectorAll('.page-filter-group label.mw-ui-progressive').forEach(function(label) { label.classList.remove('mw-ui-progressive'); });
  document.querySelectorAll('.page-filter-group .items > label:first-child').forEach(function(label) { label.classList.add('mw-ui-progressive'); });

  // Filter from the start
  for (let label of document.querySelectorAll('.page-filter-group .items > label:not(.mw-ui-disabled)')) {
    let group = label.parentElement;
    const filterGroup = getPageFilterGroup(group.parentElement);
    setTimeout(function() {
      refilter(filterGroup);
    }, 0);
  }

  addEventForChild(document, 'click', '.page-filter-group .items > label:not(.mw-ui-disabled)', function(event, label) {
    console.time('Click Label');
    let group = label.parentElement;
    let all = group.querySelector('label[data-value="*"]');
    const filterGroup = getPageFilterGroup(group.parentElement);

    if (label === all) {
      if (!all.classList.contains('mw-ui-progressive')) {
        group.querySelectorAll('label').forEach(function(sibling) {
          sibling.classList.remove('mw-ui-progressive');
        });
        all.classList.add('mw-ui-progressive');
      }
    } else if (group.childElementCount <= 3) {
        group.querySelectorAll('label').forEach(function(sibling) {
          sibling.classList.remove('mw-ui-progressive');
        });
        label.classList.add('mw-ui-progressive');
    } else {
      if (all) all.classList.remove('mw-ui-progressive');
      label.classList.toggle('mw-ui-progressive');
      if (group.querySelectorAll('.mw-ui-progressive').length < 1 && all)
        all.classList.add('mw-ui-progressive');
    }
    setTimeout(function() {
      console.time('Click Label Update');
      refilter(filterGroup);
      console.timeEnd('Click Label Update');
    }, 0);
    console.timeEnd('Click Label');
  });
})();
</script>