Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/lineup-v4' into sgratzl/dialogs
Browse files Browse the repository at this point in the history
  • Loading branch information
sgratzl committed Mar 25, 2020
2 parents bab0805 + d3eec73 commit 17a5a82
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 90 deletions.
4 changes: 2 additions & 2 deletions src/model/LinkColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {IDataRow, IGroup, IValueColumnDesc, ITypeFactory} from './interfaces';
import {patternFunction, integrateDefaults} from './internal';
import ValueColumn, {dataLoaded} from './ValueColumn';
import {IEventListener, ISequence} from '../internal';
import {IStringDesc, EAlignment, IStringGroupCriteria, EStringGroupCriteriaType} from './StringColumn';
import {IStringDesc, EAlignment, IStringGroupCriteria, EStringGroupCriteriaType, IStringFilter} from './StringColumn';
import StringColumn from './StringColumn';

export interface ILinkDesc extends IStringDesc {
Expand Down Expand Up @@ -186,7 +186,7 @@ export default class LinkColumn extends ValueColumn<string | ILink> {
return this.currentFilter;
}

setFilter(filter: string | RegExp | null) {
setFilter(filter: IStringFilter) {
return StringColumn.prototype.setFilter.call(this, filter);
}

Expand Down
65 changes: 48 additions & 17 deletions src/model/StringColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export interface IStringDesc {

export declare type IStringColumnDesc = IStringDesc & IValueColumnDesc<string>;

export interface IStringFilter {
filter: string | RegExp | null;
filterMissing: boolean;
}

/**
* emitted when the filter property changes
* @asMemberOf StringColumn
Expand All @@ -65,8 +70,8 @@ export default class StringColumn extends ValueColumn<string> {
static readonly EVENT_GROUPING_CHANGED = 'groupingChanged';

//magic key for filtering missing ones
static readonly FILTER_MISSING = '__FILTER_MISSING';
private currentFilter: string | RegExp | null = null;
private static readonly FILTER_MISSING = '__FILTER_MISSING';
private currentFilter: IStringFilter | null = null;

readonly alignment: EAlignment;
readonly escape: boolean;
Expand Down Expand Up @@ -136,11 +141,36 @@ export default class StringColumn extends ValueColumn<string> {

restore(dump: any, factory: ITypeFactory) {
super.restore(dump, factory);
if (dump.filter && (<string>dump.filter).startsWith('REGEX:')) {
this.currentFilter = new RegExp(dump.filter.slice(6), 'gm');
if (dump.filter) {
const filter = dump.filter;
if (typeof filter === 'string') {
// compatibility case
if ((<string>filter).startsWith('REGEX:')) {
this.currentFilter = {
filter: new RegExp(filter.slice(6), 'gm'),
filterMissing: false
};
} else if (filter === StringColumn.FILTER_MISSING) {
this.currentFilter = {
filter: null,
filterMissing: true
};
} else {
this.currentFilter = {
filter,
filterMissing: false
};
}
} else {
this.currentFilter = {
filter: filter.filter && (<string>filter.filter).startsWith('REGEX:') ? new RegExp(filter.slice(6), 'gm') : filter.filter || '',
filterMissing: filter.filterMissing === true
};
}
} else {
this.currentFilter = dump.filter || null;
this.currentFilter = null;
}

// tslint:disable-next-line: early-exit
if (dump.groupCriteria) {
const {type, values} = <IStringGroupCriteria>dump.groupCriteria;
Expand All @@ -160,29 +190,30 @@ export default class StringColumn extends ValueColumn<string> {
return true;
}
const r = this.getLabel(row);
const filter = this.currentFilter;
const filter = this.currentFilter!;
const ff = filter.filter;

if (filter === StringColumn.FILTER_MISSING) { //filter empty
return r != null && r.trim() !== '';
if (r == null || r.trim() === '') {
return !filter.filterMissing;
}
if (typeof filter === 'string' && filter.length > 0) {
return r !== '' && r.toLowerCase().indexOf(filter.toLowerCase()) >= 0;
if (!ff) {
return true;
}
if (filter instanceof RegExp) {
return r !== '' && r.match(filter) != null; // You can not use RegExp.test(), because of https://stackoverflow.com/a/6891667
if (ff instanceof RegExp) {
return r !== '' && r.match(ff) != null; // You can not use RegExp.test(), because of https://stackoverflow.com/a/6891667
}
return true;
return r !== '' && r.toLowerCase().includes(ff.toLowerCase());
}

getFilter() {
return this.currentFilter;
}

setFilter(filter: string | RegExp | null) {
if (filter === '') {
filter = null;
setFilter(filter: IStringFilter | null) {
if (filter === this.currentFilter) {
return;
}
if (this.currentFilter === filter) {
if (this.currentFilter && filter && this.currentFilter.filterMissing === filter.filterMissing && this.currentFilter.filter === filter.filter) {
return;
}
this.fire([StringColumn.EVENT_FILTER_CHANGED, Column.EVENT_DIRTY_VALUES, Column.EVENT_DIRTY], this.currentFilter, this.currentFilter = filter);
Expand Down
8 changes: 4 additions & 4 deletions src/model/internalCategorical.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,16 @@ export function toMostFrequentCategoricals(rows: ISequence<IDataRow>, col: ICate
/** @internal */
export function toGroupCompareCategoryValue(rows: ISequence<IDataRow>, col: ICategoricalColumn, valueCache?: ISequence<ICategory | null>): ICompareValue[] {
if (isSeqEmpty(rows)) {
return [NaN, 0];
return [NaN, null];
}
const mostFrequent = findMostFrequent(rows.map((d) => col.getCategory(d)), valueCache);
if (mostFrequent.cat == null) {
return [NaN, 0];
return [NaN, null];
}
return [mostFrequent.cat.value, mostFrequent.count];
return [mostFrequent.cat.value, mostFrequent.cat.name.toLowerCase()];
}

export const COMPARE_GROUP_CATEGORY_VALUE_TYPES = [ECompareValueType.FLOAT, ECompareValueType.COUNT];
export const COMPARE_GROUP_CATEGORY_VALUE_TYPES = [ECompareValueType.FLOAT, ECompareValueType.STRING];

/** @internal */
function compareCategory(a: ICategory | null, b: ICategory | null) {
Expand Down
52 changes: 19 additions & 33 deletions src/renderer/StringCellRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {StringColumn, Column, IDataRow, IOrderedGroup} from '../model';
import {StringColumn, Column, IDataRow, IOrderedGroup, IStringFilter} from '../model';
import {filterMissingMarkup, findFilterMissing} from '../ui/missing';
import {IRenderContext, ICellRendererFactory} from './interfaces';
import {renderMissingDOM} from './missing';
Expand Down Expand Up @@ -61,20 +61,15 @@ export default class StringCellRenderer implements ICellRendererFactory {
const isRegex = <HTMLInputElement>node.querySelector('input[type="checkbox"]');

const update = () => {
input.disabled = filterMissing.checked;
isRegex.disabled = filterMissing.checked;

if (filterMissing.checked) {
col.setFilter(StringColumn.FILTER_MISSING);
return;
}
const valid = input.value.trim();
filterMissing.disabled = valid.length > 0;
if (valid.length <= 0) {
col.setFilter(null);
col.setFilter({filter: null, filterMissing: filterMissing.checked});
return;
}
col.setFilter(isRegex.checked ? new RegExp(input.value) : input.value);
col.setFilter({
filter: isRegex.checked ? new RegExp(input.value) : input.value,
filterMissing: filterMissing.checked
});
};

filterMissing.onchange = update;
Expand All @@ -89,17 +84,11 @@ export default class StringCellRenderer implements ICellRendererFactory {

return (actCol: StringColumn) => {
col = actCol;
let bak = col.getFilter() || '';
const bakMissing = bak === StringColumn.FILTER_MISSING;
if (bakMissing) {
bak = '';
}
filterMissing.checked = bakMissing;
input.value = bak instanceof RegExp ? bak.source : bak;
const f = col.getFilter() || {filter: null, filterMissing: false};
const bak = f.filter;
filterMissing.checked = f.filterMissing;
input.value = bak instanceof RegExp ? bak.source : bak || '';
isRegex.checked = bak instanceof RegExp;
filterMissing.disabled = input.value.trim().length > 0;
input.disabled = filterMissing.checked;
isRegex.disabled = filterMissing.checked;
};
}

Expand All @@ -108,21 +97,18 @@ export default class StringCellRenderer implements ICellRendererFactory {
return {
template: `<div></div>`,
update: (node: HTMLElement) => {
const filter = col.getFilter() || '';
const filter = col.getFilter();
node.textContent = toString(filter);
}
};
}
let bak = col.getFilter() || '';
const bakMissing = bak === StringColumn.FILTER_MISSING;
if (bakMissing) {
bak = '';
}
const f = col.getFilter() || {filter: null, filterMissing: false};
const bak = f.filter || '';
let update: (col: StringColumn) => void;
return {
template: `<form><input type="text" placeholder="Filter ${col.desc.label}..." autofocus value="${(bak instanceof RegExp) ? bak.source : bak}">
<label class="${cssClass('checkbox')}"><input type="checkbox" ${(bak instanceof RegExp) ? 'checked="checked"' : ''}><span>Use regular expressions</span></label>
${filterMissingMarkup(bakMissing)}</form>`,
${filterMissingMarkup(f.filterMissing)}</form>`,
update: (node: HTMLElement) => {
if (!update) {
update = StringCellRenderer.interactiveSummary(col, node);
Expand All @@ -133,12 +119,12 @@ export default class StringCellRenderer implements ICellRendererFactory {
}
}

function toString(filter: null | string | RegExp) {
if (filter == null || filter === '' || filter === StringColumn.FILTER_MISSING) {
function toString(filter: IStringFilter | null) {
if (filter == null || !filter.filter) {
return '';
}
if (filter instanceof RegExp) {
return filter.source;
if (filter.filter instanceof RegExp) {
return filter.filter.source;
}
return String(filter);
return filter.filter;
}
49 changes: 15 additions & 34 deletions src/ui/dialogs/StringFilterDialog.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {StringColumn} from '../../model';
import {StringColumn, IStringFilter} from '../../model';
import {filterMissingMarkup, findFilterMissing} from '../missing';
import ADialog, {IDialogContext} from './ADialog';
import {updateFilterState} from './utils';
Expand All @@ -8,7 +8,7 @@ import {cssClass} from '../../styles';
/** @internal */
export default class StringFilterDialog extends ADialog {

private readonly before: string | RegExp | null;
private readonly before: IStringFilter | null;

constructor(private readonly column: StringColumn, dialog: IDialogContext) {
super(dialog, {
Expand All @@ -18,9 +18,9 @@ export default class StringFilterDialog extends ADialog {
this.before = this.column.getFilter();
}

private updateFilter(filter: string | RegExp | null) {
updateFilterState(this.attachment, this.column, filter != null && filter !== '');
this.column.setFilter(filter);
private updateFilter(filter: string | RegExp | null, filterMissing: boolean) {
updateFilterState(this.attachment, this.column, filterMissing || (filter != null && filter !== ''));
this.column.setFilter({filter, filterMissing});
}

protected reset() {
Expand All @@ -29,50 +29,31 @@ export default class StringFilterDialog extends ADialog {
}

protected cancel() {
this.updateFilter(this.before);
if (this.before) {
this.updateFilter(this.before.filter, this.before.filterMissing);
} else {
this.updateFilter(null, false);
}
}

protected submit() {
const filterMissing = findFilterMissing(this.node).checked;
if (filterMissing) {
this.updateFilter(StringColumn.FILTER_MISSING);
return true;
}
const input = this.findInput('input[type="text"]').value;
const isRegex = this.findInput('input[type="checkbox"]').checked;
this.updateFilter(isRegex ? new RegExp(input, 'gm') : input);
this.updateFilter(isRegex ? new RegExp(input, 'gm') : input, filterMissing);
return true;
}

protected build(node: HTMLElement) {
let bak = this.column.getFilter() || '';
const bakMissing = bak === StringColumn.FILTER_MISSING;
if (bakMissing) {
bak = '';
}
node.insertAdjacentHTML('beforeend', `<input type="text" placeholder="Filter ${this.column.desc.label}..." autofocus value="${(bak instanceof RegExp) ? bak.source : bak}" style="width: 100%">
<label class="${cssClass('checkbox')}"><input type="checkbox" ${(bak instanceof RegExp) ? 'checked="checked"' : ''}><span>Use regular expressions</span></label>
${filterMissingMarkup(bakMissing)}`);
const bak = this.column.getFilter() || {filter: '', filterMissing: false};
node.insertAdjacentHTML('beforeend', `<input type="text" placeholder="Filter ${this.column.desc.label}..." autofocus value="${(bak.filter instanceof RegExp) ? bak.filter.source : bak.filter || ''}" style="width: 100%">
<label class="${cssClass('checkbox')}"><input type="checkbox" ${(bak.filter instanceof RegExp) ? 'checked="checked"' : ''}><span>Use regular expressions</span></label>
${filterMissingMarkup(bak.filterMissing)}`);

const filterMissing = findFilterMissing(node);
const input = <HTMLInputElement>node.querySelector('input[type="text"]');
const isRegex = <HTMLInputElement>node.querySelector('input[type="checkbox"]');

const update = () => {
input.disabled = filterMissing.checked;
isRegex.disabled = filterMissing.checked;

if (filterMissing.checked) {
return;
}
const valid = input.value.trim();
filterMissing.disabled = valid.length > 0;
};

filterMissing.onchange = update;
input.onchange = update;
isRegex.onchange = update;

this.enableLivePreviews([filterMissing, input, isRegex]);
}
}

0 comments on commit 17a5a82

Please sign in to comment.