Skip to content

Commit 1f2c840

Browse files
authored
Add option to filter out variables (#290)
* Check if type is in filter when adding row * Basic implementation of variable type filter * Add filter table styling * Add name filtering * Add regex to name filtering * Use wilcard-match for matching names * Add variable filter tests * Add comments
1 parent 8d15502 commit 1f2c840

File tree

5 files changed

+1020
-626
lines changed

5 files changed

+1020
-626
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@
6666
"@lumino/disposable": "^2.0.0",
6767
"@lumino/signaling": "^2.0.0",
6868
"@lumino/widgets": "^2.0.0",
69-
"react": "^18.2.0"
69+
"react": "^18.2.0",
70+
"wildcard-match": "^5.1.2"
7071
},
7172
"devDependencies": {
7273
"@jupyterlab/builder": "^4.0.0",

src/variableinspector.ts

+206-1
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,23 @@ import { DockLayout, Widget } from '@lumino/widgets';
88

99
import { IVariableInspector } from './tokens';
1010

11+
import wildcardMatch from 'wildcard-match';
12+
1113
const TITLE_CLASS = 'jp-VarInspector-title';
1214
const PANEL_CLASS = 'jp-VarInspector';
1315
const TABLE_CLASS = 'jp-VarInspector-table';
1416
const TABLE_BODY_CLASS = 'jp-VarInspector-content';
17+
const TABLE_ROW_CLASS = 'jp-VarInspector-table-row';
18+
const TABLE_ROW_HIDDEN_CLASS = 'jp-VarInspector-table-row-hidden';
19+
const TABLE_TYPE_CLASS = 'jp-VarInspector-type';
20+
const TABLE_NAME_CLASS = 'jp-VarInspector-varName';
21+
const FILTER_TYPE_CLASS = 'filter-type';
22+
const FILTER_INPUT_CLASS = 'filter-input';
23+
const FILTER_BUTTON_CLASS = 'filter-button';
24+
const FILTER_LIST_CLASS = 'filter-list';
25+
const FILTERED_BUTTON_CLASS = 'filtered-variable-button';
26+
27+
type FILTER_TYPES = 'type' | 'name';
1528

1629
/**
1730
* A panel that renders the variables
@@ -21,8 +34,10 @@ export class VariableInspectorPanel
2134
implements IVariableInspector
2235
{
2336
private _source: IVariableInspector.IInspectable | null = null;
37+
private _filteredTable: HTMLDivElement;
2438
private _table: HTMLTableElement;
2539
private _title: HTMLElement;
40+
private _filtered: { type: Array<string>; name: Array<string> };
2641

2742
constructor() {
2843
super();
@@ -31,8 +46,136 @@ export class VariableInspectorPanel
3146
this._title.className = TITLE_CLASS;
3247
this._table = Private.createTable();
3348
this._table.className = TABLE_CLASS;
49+
this._filteredTable = Private.createFilterTable();
3450
this.node.appendChild(this._title as HTMLElement);
51+
this.node.appendChild(this._filteredTable as HTMLElement);
3552
this.node.appendChild(this._table as HTMLElement);
53+
this._filtered = { type: [], name: [] };
54+
this.intializeFilteredTable();
55+
}
56+
57+
//Sets up the filter table so when the filter button is pressed, a new filter is created
58+
protected intializeFilteredTable() {
59+
const filterType = this._filteredTable.querySelector(
60+
'.' + FILTER_TYPE_CLASS
61+
) as HTMLSelectElement;
62+
const filterInput = this._filteredTable.querySelector(
63+
'.' + FILTER_INPUT_CLASS
64+
) as HTMLInputElement;
65+
const filterButton = this._filteredTable.querySelector(
66+
'.' + FILTER_BUTTON_CLASS
67+
) as HTMLButtonElement;
68+
filterButton.addEventListener('click', () => {
69+
this.onFilterChange(
70+
filterType.value as FILTER_TYPES,
71+
filterInput.value,
72+
true
73+
);
74+
});
75+
}
76+
77+
// Checks if string is in the filtered array
78+
protected stringInFilter(string: string, filterType: FILTER_TYPES) {
79+
// console.log(this._filtered[filterType]);
80+
for (let i = 0; i < this._filtered[filterType].length; i++) {
81+
const isMatch = wildcardMatch(this._filtered[filterType][i]);
82+
if (isMatch(string)) {
83+
return true;
84+
}
85+
}
86+
return false;
87+
}
88+
/*
89+
Either adds a new filter or removes a previously existing filter based
90+
Params:
91+
filterType: By what type the varName is filtering on
92+
varName: The name of the variable we are trying to filter out
93+
isAdding: If we are adding a new filter or removing a previous filter
94+
*/
95+
96+
protected onFilterChange(
97+
filterType: FILTER_TYPES,
98+
varName: string,
99+
isAdding: boolean
100+
) {
101+
if (varName === '') {
102+
return;
103+
}
104+
if (isAdding) {
105+
if (this._filtered[filterType].includes(varName)) {
106+
return;
107+
}
108+
this._filtered[filterType].push(varName);
109+
const filterList = this._filteredTable.querySelector(
110+
'.' + FILTER_LIST_CLASS
111+
) as HTMLUListElement;
112+
const newFilteredButton = Private.createFilteredButton(
113+
varName,
114+
filterType
115+
);
116+
newFilteredButton.addEventListener('click', () => {
117+
const filterText = newFilteredButton.querySelector(
118+
'.filtered-variable-button-text'
119+
) as HTMLDivElement;
120+
this.onFilterChange(filterType, filterText.innerHTML, false);
121+
this.addFilteredOutRows();
122+
newFilteredButton.remove();
123+
});
124+
filterList.appendChild(newFilteredButton);
125+
this.filterOutTable();
126+
} else {
127+
this._filtered[filterType] = this._filtered[filterType].filter(
128+
filter => filter !== varName
129+
);
130+
}
131+
}
132+
133+
/*
134+
Goes through each filtered out row and checks if they should still be filtered
135+
If not, the row becomes visible again
136+
*/
137+
protected addFilteredOutRows() {
138+
const rows = this._table.querySelectorAll(
139+
'.' + TABLE_ROW_HIDDEN_CLASS
140+
) as NodeListOf<HTMLTableRowElement>;
141+
for (let i = 0; i < rows.length; i++) {
142+
const rowName = rows[i].querySelector(
143+
'.' + TABLE_NAME_CLASS
144+
) as HTMLTableCellElement;
145+
const rowType = rows[i].querySelector(
146+
'.' + TABLE_TYPE_CLASS
147+
) as HTMLTableCellElement;
148+
if (
149+
!this.stringInFilter(rowName.innerHTML, 'name') &&
150+
!this._filtered['type'].includes(rowType.innerHTML)
151+
) {
152+
rows[i].className = TABLE_ROW_CLASS;
153+
}
154+
}
155+
}
156+
157+
/*
158+
Goes through each row and checks if the row should be filtered out
159+
A row is filtered out if it matches any of the values in the _filtered object
160+
*/
161+
protected filterOutTable() {
162+
const rows = this._table.querySelectorAll(
163+
'.' + TABLE_ROW_CLASS
164+
) as NodeListOf<HTMLTableRowElement>;
165+
for (let i = 0; i < rows.length; i++) {
166+
const rowName = rows[i].querySelector(
167+
'.' + TABLE_NAME_CLASS
168+
) as HTMLTableCellElement;
169+
const rowType = rows[i].querySelector(
170+
'.' + TABLE_TYPE_CLASS
171+
) as HTMLTableCellElement;
172+
if (
173+
this.stringInFilter(rowName.innerHTML, 'name') ||
174+
this._filtered['type'].includes(rowType.innerHTML)
175+
) {
176+
rows[i].className = TABLE_ROW_HIDDEN_CLASS;
177+
}
178+
}
36179
}
37180

38181
get source(): IVariableInspector.IInspectable | null {
@@ -99,6 +242,12 @@ export class VariableInspectorPanel
99242
const varType = item.varType;
100243

101244
row = this._table.tFoot!.insertRow();
245+
row.className = TABLE_ROW_CLASS;
246+
if (this._filtered['type'].includes(varType)) {
247+
row.className = TABLE_ROW_HIDDEN_CLASS;
248+
} else if (this.stringInFilter(name, 'name')) {
249+
row.className = TABLE_ROW_HIDDEN_CLASS;
250+
}
102251

103252
// Add delete icon and onclick event
104253
let cell = row.insertCell(0);
@@ -130,12 +279,13 @@ export class VariableInspectorPanel
130279
}
131280

132281
cell = row.insertCell(2);
133-
cell.className = 'jp-VarInspector-varName';
282+
cell.className = TABLE_NAME_CLASS;
134283
cell.innerHTML = name;
135284

136285
// Add remaining cells
137286
cell = row.insertCell(3);
138287
cell.innerHTML = varType;
288+
cell.className = TABLE_TYPE_CLASS;
139289
cell = row.insertCell(4);
140290
cell.innerHTML = item.varSize;
141291
cell = row.insertCell(5);
@@ -233,4 +383,59 @@ namespace Private {
233383
title.innerHTML = header;
234384
return title;
235385
}
386+
387+
export function createFilterTable(): HTMLDivElement {
388+
const container = document.createElement('div');
389+
container.className = 'filter-container';
390+
const filterType = document.createElement('select');
391+
filterType.className = FILTER_TYPE_CLASS;
392+
filterType.selectedIndex = 0;
393+
const varTypeOption = document.createElement('option');
394+
varTypeOption.value = 'type';
395+
varTypeOption.innerHTML = 'Type';
396+
const nameOption = document.createElement('option');
397+
nameOption.value = 'name';
398+
nameOption.innerHTML = 'Name';
399+
filterType.appendChild(varTypeOption);
400+
filterType.appendChild(nameOption);
401+
const searchContainer = document.createElement('div');
402+
searchContainer.className = 'jp-InputGroup filter-search-container';
403+
const input = document.createElement('input');
404+
input.setAttribute('type', 'text');
405+
input.setAttribute('placeholder', 'Filter out variable');
406+
input.className = FILTER_INPUT_CLASS;
407+
const filterButton = document.createElement('button');
408+
const buttonText = document.createTextNode('Filter');
409+
filterButton.appendChild(buttonText);
410+
filterButton.className = FILTER_BUTTON_CLASS;
411+
const list = document.createElement('ul');
412+
list.className = FILTER_LIST_CLASS;
413+
414+
searchContainer.appendChild(filterType);
415+
searchContainer.appendChild(input);
416+
searchContainer.appendChild(filterButton);
417+
container.appendChild(searchContainer);
418+
container.appendChild(list);
419+
return container;
420+
}
421+
422+
//Creates a button with given filter information displayed on the button
423+
export function createFilteredButton(
424+
filterName: string,
425+
filterType: FILTER_TYPES
426+
): HTMLButtonElement {
427+
const filteredButton = document.createElement('button');
428+
filteredButton.value = filterType;
429+
filteredButton.title = filterType;
430+
const buttonText = document.createElement('div');
431+
buttonText.className = 'filtered-variable-button-text';
432+
buttonText.innerHTML = filterName;
433+
const icon = closeIcon.element({
434+
container: filteredButton
435+
});
436+
filteredButton.appendChild(buttonText);
437+
filteredButton.appendChild(icon);
438+
filteredButton.className = FILTERED_BUTTON_CLASS;
439+
return filteredButton;
440+
}
236441
}

style/base.css

+63
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,69 @@
4545
padding-left: 10px;
4646
}
4747

48+
.filter-container {
49+
display: flex;
50+
flex-direction: column;
51+
}
52+
53+
.filter-search-container {
54+
display: flex;
55+
padding: 0 1rem;
56+
}
57+
58+
.filter-type {
59+
background-color: var(--jp-ui-font-color2);
60+
color: var(--jp-ui-font-color1);
61+
box-shadow: inset 0 0 0 var(--jp-border-width) var(--jp-input-border-color);
62+
border: none;
63+
padding: 0 0.5rem;
64+
text-align: center;
65+
}
66+
67+
.filter-input {
68+
width: 20rem !important;
69+
margin-left: 1rem;
70+
}
71+
72+
.filter-button {
73+
background-color: var(--jp-ui-font-color2);
74+
color: var(--jp-ui-font-color1);
75+
box-shadow: inset 0 0 0 var(--jp-border-width) var(--jp-input-border-color);
76+
border: none;
77+
padding: 0 0.5rem;
78+
}
79+
80+
.filtered-variable-button {
81+
background-color: transparent;
82+
box-shadow: inset 0 0 0 var(--jp-border-width) var(--jp-input-border-color);
83+
border: none;
84+
padding: 0.25rem 0.5rem;
85+
display: flex;
86+
align-items: center;
87+
gap: 0.5rem;
88+
}
89+
90+
.filtered-variable-button-text {
91+
color: var(--jp-ui-font-color1);
92+
background-color: transparent;
93+
}
94+
95+
.type-button {
96+
color: var(--jp-ui-font-color0);
97+
}
98+
99+
.name-button {
100+
color: var(--jp-ui-font-color1);
101+
}
102+
103+
.filter-list {
104+
display: flex;
105+
}
106+
107+
.jp-VarInspector-table-row-hidden {
108+
display: none;
109+
}
110+
48111
.jp-VarInspector-deleteButton {
49112
text-align: center;
50113
width: 1em;

0 commit comments

Comments
 (0)