Skip to content

Commit

Permalink
Added ability to delete selectable cards (#46)
Browse files Browse the repository at this point in the history
* adding delete icon

* added basic delete functionality

* able to delete cards but need to clear selections

* finished adding delete feature

* added comment

* updating exported graph as well

* added deletedIndices to widget

* fixed update bug in export and deletion buttons

* fixed keeping track of deletions between exports

* fixed bug to have exportedIndices sync on delete

* changed logic to use observer in backend
  • Loading branch information
jakeatgalileo authored Oct 6, 2020
1 parent 91f53a2 commit 67ce451
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 9 deletions.
11 changes: 10 additions & 1 deletion css/widget.css
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,19 @@ input[type=text], select {
font-size: 20px;
padding: 5px;
}

#deleteBtn{
position: absolute;
top: 42px;
right: 33px;
font-size: 20px;
padding: 5px;
}

#alertBox{
position: absolute;
left: 23%;
width: 52%;
width: 54%;
bottom: 6%;
padding: 5px 30px 5px 10px;
}
Expand Down
1 change: 1 addition & 0 deletions luxWidget/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class LuxWidget(DOMWidget):
recommendations = List([]).tag(sync=True)
data = List([]).tag(sync=True)
_exportedVisIdxs = Dict({}).tag(sync=True)
deletedIndices = Dict({}).tag(sync=True)
intent = Unicode("").tag(sync=True)
message = Unicode("").tag(sync=True)
def __init__(self, currentVis=None, recommendations=None, intent=None, message=None,**kwargs):
Expand Down
8 changes: 7 additions & 1 deletion src/chartGallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ChartGalleryComponent extends Component<chartGalleryProps,any> {
};
this.state = initialState;
}

onItemSelected(index) {
// Implementation based on https://codepen.io/j-burgos/pen/VpQxLv
this.setState((prevState, props) => {
Expand Down Expand Up @@ -54,8 +55,13 @@ class ChartGalleryComponent extends Component<chartGalleryProps,any> {
}
});
}

removeDeletedCharts() {
this.setState({selected:[]});
}

render() {
console.log('chart render');
console.log('chart render');
return (
<div className="chartGalleryTabContent">
<p className="text-description" dangerouslySetInnerHTML={{__html: this.props.description}}/>
Expand Down
83 changes: 76 additions & 7 deletions src/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export class ExampleModel extends DOMWidgetModel {
}

export class JupyterWidgetView extends DOMWidgetView {

initialize(){
let view = this;
interface WidgetProps{
Expand All @@ -61,13 +60,21 @@ export class JupyterWidgetView extends DOMWidgetView {
showAlert:boolean,
selectedRec:object,
_exportedVisIdxs:object,
deletedIndices:object,
currentVisSelected:number,
openWarning: boolean,
openWarning: boolean
}

class ReactWidget extends React.Component<JupyterWidgetView,WidgetProps> {
private chartComponents = Array<any>();

constructor(props:any){
super(props);

for (var i = 0; i < this.props.model.get("recommendations").length; i++) {
this.chartComponents.push(React.createRef<ChartGalleryComponent>());
}

this.state = {
currentVis : props.model.get("current_vis"),
recommendations: props.model.get("recommendations"),
Expand All @@ -77,16 +84,19 @@ export class JupyterWidgetView extends DOMWidgetView {
activeTab: props.activeTab,
showAlert:false,
selectedRec:{},
_exportedVisIdxs:[],
_exportedVisIdxs:{},
deletedIndices: {},
currentVisSelected: -2,
openWarning:false
}

// This binding is necessary to make `this` work in the callback
this.handleCurrentVisSelect = this.handleCurrentVisSelect.bind(this);
this.handleSelect = this.handleSelect.bind(this);
this.exportSelection = this.exportSelection.bind(this);
this.openPanel = this.openPanel.bind(this);
this.closePanel = this.closePanel.bind(this);
this.deleteSelection = this.deleteSelection.bind(this);
}

openPanel(e){
Expand Down Expand Up @@ -154,7 +164,7 @@ export class JupyterWidgetView extends DOMWidgetView {
}

exportSelection() {
dispatchLogEvent("exportBtnClick",this.state._exportedVisIdxs)
dispatchLogEvent("exportBtnClick",this.state._exportedVisIdxs);
this.setState(
state => ({
showAlert:true
Expand All @@ -166,7 +176,46 @@ export class JupyterWidgetView extends DOMWidgetView {
showAlert:false
}));
},60000);
view.model.set('_exportedVisIdxs',this.state._exportedVisIdxs);

view.model.set('_exportedVisIdxs', this.state._exportedVisIdxs);
view.model.save();

}

/*
* Goes through all selections and removes and clears any selections across recommendation tabs.
* Changing deletedIndices triggers an observer in the backend to update backend data structure.
* Re-renders each tab's chart component, with the updated recommendations.
*/
deleteSelection() {
dispatchLogEvent("deleteBtnClick", this.state.deletedIndices);
var currDeletions = this.state._exportedVisIdxs;

// Deleting from the frontend's visualization data structure
for (var recommendation of this.state.recommendations) {
if (this.state._exportedVisIdxs[recommendation.action]) {
let delCount = 0;
for (var index of this.state._exportedVisIdxs[recommendation.action]) {
recommendation.vspec.splice(index - delCount, 1);
delCount++;
}
}
}

this.setState({
selectedRec: {},
_exportedVisIdxs: {},
deletedIndices: currDeletions
});

// Re-render each tab's components to update deletions on front end
for (var i = 0; i < this.props.model.get("recommendations").length; i++) {
this.chartComponents[i].current.removeDeletedCharts();
}

view.model.set('deletedIndices', currDeletions);
view.model.set('_exportedVisIdxs', {});
view.model.save();
}

generateTabItems() {
Expand All @@ -177,19 +226,21 @@ export class JupyterWidgetView extends DOMWidgetView {
// this exists to prevent chart gallergy from refreshing while changing tabs
// This is an anti-pattern for React, but is necessary here because our chartgallery is very expensive to initialize
key={'no refresh'}
ref={this.chartComponents[tabIdx]}
title={actionResult.action}
description={actionResult.description}
multiple={true}
maxSelectable={10}
onChange={this.onListChanged.bind(this,tabIdx)}
graphSpec={actionResult.vspec}
currentVisShow={!_.isEmpty(this.props.model.get("current_vis"))}/>
currentVisShow={!_.isEmpty(this.props.model.get("current_vis"))}
/>
</Tab>
)
)
}

render(){
render() {
let exportBtn;
var exportEnabled = Object.keys(this.state._exportedVisIdxs).length > 0
if (this.state.tabItems.length>0){
Expand All @@ -207,6 +258,22 @@ export class JupyterWidgetView extends DOMWidgetView {
}
}

let deleteBtn;
var deleteEnabled = Object.keys(this.state._exportedVisIdxs).length > 0
if (this.state.tabItems.length > 0){
if (deleteEnabled) {
deleteBtn = <i id="deleteBtn"
className="fa fa-trash"
title='Delete Selected Cards'
onClick={() => this.deleteSelection()}/>
} else {
deleteBtn = <i id="deleteBtn"
className="fa fa-trash"
style={{opacity: 0.2, cursor: 'not-allowed'}}
title='Select card(s) to delete'/>
}
}

let alertBtn;
if (this.state.showAlert){
alertBtn= <Alert id="alertBox"
Expand Down Expand Up @@ -237,6 +304,7 @@ export class JupyterWidgetView extends DOMWidgetView {
<div style={{ display: 'flex', flexDirection: 'row' }}>
<CurrentVisComponent intent={this.state.intent} currentVisSpec={this.state.currentVis} numRecommendations={0}
onChange={this.handleCurrentVisSelect}/>
{deleteBtn}
{exportBtn}
{alertBtn}
</div>
Expand All @@ -254,6 +322,7 @@ export class JupyterWidgetView extends DOMWidgetView {
{this.state.tabItems}
</Tabs>
</div>
{deleteBtn}
{exportBtn}
{alertBtn}
</div>
Expand Down

0 comments on commit 67ce451

Please sign in to comment.