<commit_before>/**
 * Created by <NAME> on 14.08.2015.
 */

import {merge, suffix} from '../../utils';
import {nest} from 'd3';
import Ranking, {isSupportType} from '../../model/Ranking';
import {default as Column, IColumnDesc} from '../../model/Column';
import SidePanelEntry from './SidePanelEntry';
import DataProvider, {IDataProvider} from '../../provider/ADataProvider';
import {IRankingHeaderContext} from '../engine/interfaces';

export interface ISidePanelOptions {
  additionalDescs: IColumnDesc[];
}

export default class SidePanel {

  private readonly options: ISidePanelOptions = {
    additionalDescs: []
  };

  readonly node: HTMLElement;
  private readonly descs = new Map<IColumnDesc, SidePanelEntry>();

  constructor(private ctx: IRankingHeaderContext, document: Document, options: Partial<ISidePanelOptions> = {}) {
    merge(this.options, options);

    this.node = document.createElement('aside');
    this.node.classList.add('lu-side-panel');
    this.init();
    this.update(ctx);
  }

  private init() {
    this.node.innerHTML = `
      <header>
        <form>
            <select>
                <option value="">Add Column...</option>
            </select>
        </form>
      </header>
      <div><main></main></div>
    `;
    this.node.querySelector('header select')!.addEventListener('change', (evt) => {
      evt.preventDefault();
      const id = (<HTMLSelectElement>evt.currentTarget).value;
      if (id === '') {
        return;
      }
      const entry = Array.from(this.descs.values()).find((d) => d.id === id)!;
      console.assert(Boolean(entry));

      const col = this.data.create(entry.desc);
      if (!col) {
        return;
      }
      this.data.getLastRanking().push(col);
    });


    this.changeDataStorage(null, this.data);
    this.updateChooser();
    this.updateList();
  }

  private get data() {
    return this.ctx.provider;
  }

  private changeDataStorage(old: IDataProvider|null, data: IDataProvider) {
    const that = this;
    if (old) {
      old.on(suffix('.panel', DataProvider.EVENT_ADD_RANKING, DataProvider.EVENT_REMOVE_RANKING,
        DataProvider.EVENT_ADD_DESC), null);
    }
    this.descs.forEach((v) => v.destroyVis());
    this.descs.clear();
    data.getColumns().concat(this.options.additionalDescs).forEach((col) => {
      this.descs.set(col, new SidePanelEntry(col));
    });

    const handleRanking = (ranking: Ranking, added: boolean) => {
      const change = added ? +1 : -1;
      ranking.flatColumns.forEach((col) => {
        if (this.descs.has(col.desc)) {
          this.descs.get(col.desc)!.used += change;
        }
      });

      if (!added) {
        ranking.on(suffix('.panel', Ranking.EVENT_GROUP_CRITERIA_CHANGED, Ranking.EVENT_SORT_CRITERIAS_CHANGED, Ranking.EVENT_ADD_COLUMN, Ranking.EVENT_REMOVE_COLUMN), null);
        return;
      }

      ranking.on(suffix('.panel', Ranking.EVENT_GROUP_CRITERIA_CHANGED, Ranking.EVENT_SORT_CRITERIAS_CHANGED), () => {
        if (ranking === data.getRankings()[0]) {
          // primary ranking only
          this.updateList();
        }
      });
      ranking.on(suffix('.panel', DataProvider.EVENT_ADD_COLUMN, DataProvider.EVENT_REMOVE_COLUMN), function (this: { type: string }, col) {
        const desc = col.desc;
        const added = this.type === 'addColumn';
        if (!that.descs.has(desc)) {
          return;
        }
        const entry = that.descs.get(desc)!;
        entry.used += added ? +1 : -1;
        that.updateList();
      });
    };
    // init data
    data.getRankings().forEach((ranking) => handleRanking(ranking, true));

    data.on(`${DataProvider.EVENT_ADD_DESC}.panel`, (desc) => {
      const v = new SidePanelEntry(desc);
      that.descs.set(desc, v);
      this.updateChooser();
    });

    data.on(suffix('.panel', DataProvider.EVENT_ADD_RANKING, DataProvider.EVENT_REMOVE_RANKING), function (this: { type: string }, ranking: Ranking) {
      handleRanking(ranking, this.type === 'addRanking');
      that.updateList();
    });
  }

  update(ctx: IRankingHeaderContext) {
    const bak = this.data;
    this.ctx = ctx;
    if (ctx.provider !== bak) {
      this.changeDataStorage(bak, ctx.provider);
    }

    this.updateChooser();
    this.updateList();
  }

  remove() {
    this.node.remove();
    if (!this.data) {
      return;
    }
    this.data.getRankings().forEach((ranking) => {
      ranking.on(suffix('.panel', Ranking.EVENT_GROUP_CRITERIA_CHANGED, Ranking.EVENT_SORT_CRITERIAS_CHANGED, Ranking.EVENT_ADD_COLUMN, Ranking.EVENT_REMOVE_COLUMN), null);
    });
    this.data.on(suffix('.panel', DataProvider.EVENT_ADD_RANKING, DataProvider.EVENT_REMOVE_RANKING,
      DataProvider.EVENT_ADD_DESC), null);
  }

  private columnOrder() {
    // sort column by first ranking order
    const ranking = this.data.getRankings()[0];
    if (!ranking) {
      return [];
    }
    const hierarchy = ranking.getSortCriterias().map((d) => d.col);
    if (ranking.getGroupCriteria()) {
      hierarchy.unshift(ranking.getGroupCriteria()!);
    }
    return hierarchy;
  }

  private prepareListData() {
    const order = this.columnOrder();
    // columns with reference column impl
    const referenceColumns = new Map<IColumnDesc, Column>();

    // preset with the order
    order.forEach((col) => referenceColumns.set(col.desc, col));

    this.data.getRankings().forEach((ranking) => {
      ranking.flatColumns.forEach((col) => {
        const key = col.desc;
        // just if not already part of
        if (referenceColumns.has(key) || isSupportType(key)) {
          return;
        }
        referenceColumns.set(key, col);
      });
    });
    const columns = Array.from(referenceColumns.values());

    // sort by order
    columns.sort((a, b) => {
      const ai = order.indexOf(a);
      const bi = order.indexOf(b);
      if (ai < 0) {
        return bi <0 ? 0 : 1;
      }
      if (bi < 0) {
        return -1;
      }
      return ai - bi;
    });

    return columns;

  }

  private updateList() {
    const node = this.node.querySelector('main')!;
    const columns = this.prepareListData();

    if (columns.length === 0) {
      node.innerHTML = '';
      this.descs.forEach((d) => d.destroyVis());
      return;
    }

    node.innerHTML = ``;

    columns.forEach((col) => {
      const entry = this.descs.get(col.desc);
      if (!entry) {
        return;
      }
      if (entry.visColumn === col) {
        node.appendChild(entry.updateVis(this.ctx)!);
        return;<commit_msg>fix side panel column order<commit_after>/**
 * Created by <NAME> on 14.08.2015.
 */

import {merge, suffix} from '../../utils';
import {nest} from 'd3';
import Ranking, {isSupportType} from '../../model/Ranking';
import {default as Column, IColumnDesc} from '../../model/Column';
import SidePanelEntry from './SidePanelEntry';
import DataProvider, {IDataProvider} from '../../provider/ADataProvider';
import {IRankingHeaderContext} from '../engine/interfaces';

export interface ISidePanelOptions {
  additionalDescs: IColumnDesc[];
}

export default class SidePanel {

  private readonly options: ISidePanelOptions = {
    additionalDescs: []
  };

  readonly node: HTMLElement;
  private readonly descs = new Map<IColumnDesc, SidePanelEntry>();

  constructor(private ctx: IRankingHeaderContext, document: Document, options: Partial<ISidePanelOptions> = {}) {
    merge(this.options, options);

    this.node = document.createElement('aside');
    this.node.classList.add('lu-side-panel');
    this.init();
    this.update(ctx);
  }

  private init() {
    this.node.innerHTML = `
      <header>
        <form>
            <select>
                <option value="">Add Column...</option>
            </select>
        </form>
      </header>
      <div><main></main></div>
    `;
    this.node.querySelector('header select')!.addEventListener('change', (evt) => {
      evt.preventDefault();
      const id = (<HTMLSelectElement>evt.currentTarget).value;
      if (id === '') {
        return;
      }
      const entry = Array.from(this.descs.values()).find((d) => d.id === id)!;
      console.assert(Boolean(entry));

      const col = this.data.create(entry.desc);
      if (!col) {
        return;
      }
      this.data.getLastRanking().push(col);
    });


    this.changeDataStorage(null, this.data);
    this.updateChooser();
    this.updateList();
  }

  private get data() {
    return this.ctx.provider;
  }

  private changeDataStorage(old: IDataProvider|null, data: IDataProvider) {
    const that = this;
    if (old) {
      old.on(suffix('.panel', DataProvider.EVENT_ADD_RANKING, DataProvider.EVENT_REMOVE_RANKING,
        DataProvider.EVENT_ADD_DESC), null);
    }
    this.descs.forEach((v) => v.destroyVis());
    this.descs.clear();
    data.getColumns().concat(this.options.additionalDescs).forEach((col) => {
      this.descs.set(col, new SidePanelEntry(col));
    });

    const handleRanking = (ranking: Ranking, added: boolean) => {
      const change = added ? +1 : -1;
      ranking.flatColumns.forEach((col) => {
        if (this.descs.has(col.desc)) {
          this.descs.get(col.desc)!.used += change;
        }
      });

      if (!added) {
        ranking.on(suffix('.panel', Ranking.EVENT_GROUP_CRITERIA_CHANGED, Ranking.EVENT_SORT_CRITERIAS_CHANGED, Ranking.EVENT_ADD_COLUMN, Ranking.EVENT_REMOVE_COLUMN), null);
        return;
      }

      ranking.on(suffix('.panel', Ranking.EVENT_GROUP_CRITERIA_CHANGED, Ranking.EVENT_SORT_CRITERIAS_CHANGED), () => {
        if (ranking === data.getRankings()[0]) {
          // primary ranking only
          this.updateList();
        }
      });
      ranking.on(suffix('.panel', DataProvider.EVENT_ADD_COLUMN, DataProvider.EVENT_REMOVE_COLUMN), function (this: { type: string }, col) {
        const desc = col.desc;
        const added = this.type === 'addColumn';
        if (!that.descs.has(desc)) {
          return;
        }
        const entry = that.descs.get(desc)!;
        entry.used += added ? +1 : -1;
        that.updateList();
      });
    };
    // init data
    data.getRankings().forEach((ranking) => handleRanking(ranking, true));

    data.on(`${DataProvider.EVENT_ADD_DESC}.panel`, (desc) => {
      const v = new SidePanelEntry(desc);
      that.descs.set(desc, v);
      this.updateChooser();
    });

    data.on(suffix('.panel', DataProvider.EVENT_ADD_RANKING, DataProvider.EVENT_REMOVE_RANKING), function (this: { type: string }, ranking: Ranking) {
      handleRanking(ranking, this.type === 'addRanking');
      that.updateList();
    });
  }

  update(ctx: IRankingHeaderContext) {
    const bak = this.data;
    this.ctx = ctx;
    if (ctx.provider !== bak) {
      this.changeDataStorage(bak, ctx.provider);
    }

    this.updateChooser();
    this.updateList();
  }

  remove() {
    this.node.remove();
    if (!this.data) {
      return;
    }
    this.data.getRankings().forEach((ranking) => {
      ranking.on(suffix('.panel', Ranking.EVENT_GROUP_CRITERIA_CHANGED, Ranking.EVENT_SORT_CRITERIAS_CHANGED, Ranking.EVENT_ADD_COLUMN, Ranking.EVENT_REMOVE_COLUMN), null);
    });
    this.data.on(suffix('.panel', DataProvider.EVENT_ADD_RANKING, DataProvider.EVENT_REMOVE_RANKING,
      DataProvider.EVENT_ADD_DESC), null);
  }

  private columnOrder() {
    // sort column by first ranking order
    const ranking = this.data.getRankings()[0];
    if (!ranking) {
      return [];
    }
    const hierarchy = ranking.getSortCriterias().map((d) => d.col);
    if (ranking.getGroupCriteria()) {
      hierarchy.unshift(ranking.getGroupCriteria()!);
    }
    // add rest in ranking order
    const used = new Set(hierarchy);
    ranking.flatColumns.forEach((c) => {
      if (!used.has(c)) {
        hierarchy.push(c);
      }
    });
    return hierarchy;
  }

  private prepareListData() {
    const order = this.columnOrder();
    // columns with reference column impl
    const referenceColumns = new Map<IColumnDesc, Column>();

    // preset with the order
    order.forEach((col) => referenceColumns.set(col.desc, col));

    this.data.getRankings().forEach((ranking) => {
      ranking.flatColumns.forEach((col) => {
        const key = col.desc;
        // just if not already part of
        if (referenceColumns.has(key) || isSupportType(key)) {
          return;
        }
        referenceColumns.set(key, col);
      });
    });
    const columns = Array.from(referenceColumns.values());

    // sort by order
    columns.sort((a, b) => {
      const ai = order.indexOf(a);
      const bi = order.indexOf(b);
      if (ai < 0) {
        return bi < 0 ? 0 : 1;
      }
      if (bi < 0) {
        return -1;
      }
      return ai - bi;
    });

    return columns;

  }

  private updateList() {
    const node = this.node.querySelector('main')!;
    const columns = this.prepareListData();

    if (columns.length === 0) {
      node.innerHTML = '';
      this.descs.forEach((d) => d.destroyVis());
      return;
    }

    node.innerHTML = ``;

    columns.forEach((col) => {
      const entry = this.descs.get(col.desc);
      if (!entry) {
        return;
      }
      if (entry.visColumn === col) {
        node.appendChild(entry.updateVis(this.ctx)!);
        return;
{'fraction_non_alphanumeric': 0.15431771739953076, 'fraction_numerical': 0.003330053734957996, 'mean_word_length': 2.70140056022409, 'pattern_counts': {'":': 0, '<': 43, '<?xml version=': 0, '>': 72, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1277411', 'n_tokens_mistral': 4240, 'n_tokens_neox': 4137, 'n_words': 786}