Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to filter out variables #290

Merged
merged 9 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@
"@lumino/disposable": "^2.0.0",
"@lumino/signaling": "^2.0.0",
"@lumino/widgets": "^2.0.0",
"react": "^18.2.0"
"react": "^18.2.0",
"wildcard-match": "^5.1.2"
},
"devDependencies": {
"@jupyterlab/builder": "^4.0.0",
Expand Down
207 changes: 206 additions & 1 deletion src/variableinspector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,23 @@ import { DockLayout, Widget } from '@lumino/widgets';

import { IVariableInspector } from './tokens';

import wildcardMatch from 'wildcard-match';

const TITLE_CLASS = 'jp-VarInspector-title';
const PANEL_CLASS = 'jp-VarInspector';
const TABLE_CLASS = 'jp-VarInspector-table';
const TABLE_BODY_CLASS = 'jp-VarInspector-content';
const TABLE_ROW_CLASS = 'jp-VarInspector-table-row';
const TABLE_ROW_HIDDEN_CLASS = 'jp-VarInspector-table-row-hidden';
const TABLE_TYPE_CLASS = 'jp-VarInspector-type';
const TABLE_NAME_CLASS = 'jp-VarInspector-varName';
const FILTER_TYPE_CLASS = 'filter-type';
const FILTER_INPUT_CLASS = 'filter-input';
const FILTER_BUTTON_CLASS = 'filter-button';
const FILTER_LIST_CLASS = 'filter-list';
const FILTERED_BUTTON_CLASS = 'filtered-variable-button';

type FILTER_TYPES = 'type' | 'name';

/**
* A panel that renders the variables
Expand All @@ -21,8 +34,10 @@ export class VariableInspectorPanel
implements IVariableInspector
{
private _source: IVariableInspector.IInspectable | null = null;
private _filteredTable: HTMLDivElement;
private _table: HTMLTableElement;
private _title: HTMLElement;
private _filtered: { type: Array<string>; name: Array<string> };

constructor() {
super();
Expand All @@ -31,8 +46,136 @@ export class VariableInspectorPanel
this._title.className = TITLE_CLASS;
this._table = Private.createTable();
this._table.className = TABLE_CLASS;
this._filteredTable = Private.createFilterTable();
this.node.appendChild(this._title as HTMLElement);
this.node.appendChild(this._filteredTable as HTMLElement);
this.node.appendChild(this._table as HTMLElement);
this._filtered = { type: [], name: [] };
this.intializeFilteredTable();
}

//Sets up the filter table so when the filter button is pressed, a new filter is created
protected intializeFilteredTable() {
const filterType = this._filteredTable.querySelector(
'.' + FILTER_TYPE_CLASS
) as HTMLSelectElement;
const filterInput = this._filteredTable.querySelector(
'.' + FILTER_INPUT_CLASS
) as HTMLInputElement;
const filterButton = this._filteredTable.querySelector(
'.' + FILTER_BUTTON_CLASS
) as HTMLButtonElement;
filterButton.addEventListener('click', () => {
this.onFilterChange(
filterType.value as FILTER_TYPES,
filterInput.value,
true
);
});
}

// Checks if string is in the filtered array
protected stringInFilter(string: string, filterType: FILTER_TYPES) {
// console.log(this._filtered[filterType]);
for (let i = 0; i < this._filtered[filterType].length; i++) {
const isMatch = wildcardMatch(this._filtered[filterType][i]);
if (isMatch(string)) {
return true;
}
}
return false;
}
/*
Either adds a new filter or removes a previously existing filter based
Params:
filterType: By what type the varName is filtering on
varName: The name of the variable we are trying to filter out
isAdding: If we are adding a new filter or removing a previous filter
*/

protected onFilterChange(
filterType: FILTER_TYPES,
varName: string,
isAdding: boolean
) {
if (varName === '') {
return;
}
if (isAdding) {
if (this._filtered[filterType].includes(varName)) {
return;
}
this._filtered[filterType].push(varName);
const filterList = this._filteredTable.querySelector(
'.' + FILTER_LIST_CLASS
) as HTMLUListElement;
const newFilteredButton = Private.createFilteredButton(
varName,
filterType
);
newFilteredButton.addEventListener('click', () => {
const filterText = newFilteredButton.querySelector(
'.filtered-variable-button-text'
) as HTMLDivElement;
this.onFilterChange(filterType, filterText.innerHTML, false);
this.addFilteredOutRows();
newFilteredButton.remove();
});
filterList.appendChild(newFilteredButton);
this.filterOutTable();
} else {
this._filtered[filterType] = this._filtered[filterType].filter(
filter => filter !== varName
);
}
}

/*
Goes through each filtered out row and checks if they should still be filtered
If not, the row becomes visible again
*/
protected addFilteredOutRows() {
const rows = this._table.querySelectorAll(
'.' + TABLE_ROW_HIDDEN_CLASS
) as NodeListOf<HTMLTableRowElement>;
for (let i = 0; i < rows.length; i++) {
const rowName = rows[i].querySelector(
'.' + TABLE_NAME_CLASS
) as HTMLTableCellElement;
const rowType = rows[i].querySelector(
'.' + TABLE_TYPE_CLASS
) as HTMLTableCellElement;
if (
!this.stringInFilter(rowName.innerHTML, 'name') &&
!this._filtered['type'].includes(rowType.innerHTML)
) {
rows[i].className = TABLE_ROW_CLASS;
}
}
}

/*
Goes through each row and checks if the row should be filtered out
A row is filtered out if it matches any of the values in the _filtered object
*/
protected filterOutTable() {
const rows = this._table.querySelectorAll(
'.' + TABLE_ROW_CLASS
) as NodeListOf<HTMLTableRowElement>;
for (let i = 0; i < rows.length; i++) {
const rowName = rows[i].querySelector(
'.' + TABLE_NAME_CLASS
) as HTMLTableCellElement;
const rowType = rows[i].querySelector(
'.' + TABLE_TYPE_CLASS
) as HTMLTableCellElement;
if (
this.stringInFilter(rowName.innerHTML, 'name') ||
this._filtered['type'].includes(rowType.innerHTML)
) {
rows[i].className = TABLE_ROW_HIDDEN_CLASS;
}
}
}

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

row = this._table.tFoot!.insertRow();
row.className = TABLE_ROW_CLASS;
if (this._filtered['type'].includes(varType)) {
row.className = TABLE_ROW_HIDDEN_CLASS;
} else if (this.stringInFilter(name, 'name')) {
row.className = TABLE_ROW_HIDDEN_CLASS;
}

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

cell = row.insertCell(2);
cell.className = 'jp-VarInspector-varName';
cell.className = TABLE_NAME_CLASS;
cell.innerHTML = name;

// Add remaining cells
cell = row.insertCell(3);
cell.innerHTML = varType;
cell.className = TABLE_TYPE_CLASS;
cell = row.insertCell(4);
cell.innerHTML = item.varSize;
cell = row.insertCell(5);
Expand Down Expand Up @@ -233,4 +383,59 @@ namespace Private {
title.innerHTML = header;
return title;
}

export function createFilterTable(): HTMLDivElement {
const container = document.createElement('div');
container.className = 'filter-container';
const filterType = document.createElement('select');
filterType.className = FILTER_TYPE_CLASS;
filterType.selectedIndex = 0;
const varTypeOption = document.createElement('option');
varTypeOption.value = 'type';
varTypeOption.innerHTML = 'Type';
const nameOption = document.createElement('option');
nameOption.value = 'name';
nameOption.innerHTML = 'Name';
filterType.appendChild(varTypeOption);
filterType.appendChild(nameOption);
const searchContainer = document.createElement('div');
searchContainer.className = 'jp-InputGroup filter-search-container';
const input = document.createElement('input');
input.setAttribute('type', 'text');
input.setAttribute('placeholder', 'Filter out variable');
input.className = FILTER_INPUT_CLASS;
const filterButton = document.createElement('button');
const buttonText = document.createTextNode('Filter');
filterButton.appendChild(buttonText);
filterButton.className = FILTER_BUTTON_CLASS;
const list = document.createElement('ul');
list.className = FILTER_LIST_CLASS;

searchContainer.appendChild(filterType);
searchContainer.appendChild(input);
searchContainer.appendChild(filterButton);
container.appendChild(searchContainer);
container.appendChild(list);
return container;
}

//Creates a button with given filter information displayed on the button
export function createFilteredButton(
filterName: string,
filterType: FILTER_TYPES
): HTMLButtonElement {
const filteredButton = document.createElement('button');
filteredButton.value = filterType;
filteredButton.title = filterType;
const buttonText = document.createElement('div');
buttonText.className = 'filtered-variable-button-text';
buttonText.innerHTML = filterName;
const icon = closeIcon.element({
container: filteredButton
});
filteredButton.appendChild(buttonText);
filteredButton.appendChild(icon);
filteredButton.className = FILTERED_BUTTON_CLASS;
return filteredButton;
}
}
63 changes: 63 additions & 0 deletions style/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,69 @@
padding-left: 10px;
}

.filter-container {
display: flex;
flex-direction: column;
}

.filter-search-container {
display: flex;
padding: 0 1rem;
}

.filter-type {
background-color: var(--jp-ui-font-color2);
color: var(--jp-ui-font-color1);
box-shadow: inset 0 0 0 var(--jp-border-width) var(--jp-input-border-color);
border: none;
padding: 0 0.5rem;
text-align: center;
}

.filter-input {
width: 20rem !important;
margin-left: 1rem;
}

.filter-button {
background-color: var(--jp-ui-font-color2);
color: var(--jp-ui-font-color1);
box-shadow: inset 0 0 0 var(--jp-border-width) var(--jp-input-border-color);
border: none;
padding: 0 0.5rem;
}

.filtered-variable-button {
background-color: transparent;
box-shadow: inset 0 0 0 var(--jp-border-width) var(--jp-input-border-color);
border: none;
padding: 0.25rem 0.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}

.filtered-variable-button-text {
color: var(--jp-ui-font-color1);
background-color: transparent;
}

.type-button {
color: var(--jp-ui-font-color0);
}

.name-button {
color: var(--jp-ui-font-color1);
}

.filter-list {
display: flex;
}

.jp-VarInspector-table-row-hidden {
display: none;
}

.jp-VarInspector-deleteButton {
text-align: center;
width: 1em;
Expand Down