Skip to content

Commit

Permalink
Merge pull request #99 from XpressAI/adry/context-menu
Browse files Browse the repository at this point in the history
✨Add context menu
  • Loading branch information
MFA-X-AI authored Feb 14, 2022
2 parents 5587450 + 15746ca commit a746884
Show file tree
Hide file tree
Showing 10 changed files with 287 additions and 104 deletions.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"files": [
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
"style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}",
"style/index.js"
"style/index.js",
"schema/**/*.json"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand Down Expand Up @@ -115,7 +116,8 @@
}
},
"extension": true,
"outputDir": "xircuits/labextension"
"outputDir": "xircuits/labextension",
"schemaDir": "schema"
},
"styleModule": "style/index.js"
}
50 changes: 50 additions & 0 deletions schema/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"title": "Xircuits Context Menu",
"description": "Xircuits Context Menu settings.",
"jupyter.lab.menus": {
"context": [
{
"command": "Xircuit-editor:edit-node",
"selector": ".xircuits-editor",
"rank": 0
},
{
"command": "Xircuit-editor:delete-node",
"selector": ".xircuits-editor",
"rank": 1
},
{
"type": "separator",
"selector": ".xircuits-editor",
"rank": 11
},
{
"command": "Xircuit-editor:save-node",
"selector": ".xircuits-editor",
"rank": 12
},
{
"command": "Xircuit-editor:run-node",
"selector": ".xircuits-editor",
"rank": 13
},
{
"type": "separator",
"selector": ".xircuits-editor",
"rank": 21
},
{
"command": "Xircuit-editor:create-new",
"selector": ".xircuits-editor",
"rank": 22
},
{
"command": "Xircuit-log:open",
"selector": ".xircuits-editor",
"rank": 23
}
]
},
"additionalProperties": false,
"type": "object"
}
150 changes: 150 additions & 0 deletions src/commands/ContextMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { JupyterFrontEnd } from '@jupyterlab/application';
import { commandIDs } from '../components/xircuitBodyWidget';
import { ITranslator } from '@jupyterlab/translation';
import { IXircuitsDocTracker } from '../index';
import * as _ from 'lodash';
import { CustomNodeModel } from '../components/CustomNodeModel';
import { XPipePanel } from '../xircuitWidget';
import { Dialog, showDialog } from '@jupyterlab/apputils';
import { DefaultLinkModel } from '@projectstorm/react-diagrams';

/**
* Add the commands for the xircuits's context menu.
*/
export function addContextMenuCommands(
app: JupyterFrontEnd,
tracker: IXircuitsDocTracker,
translator: ITranslator
): void {
const trans = translator.load('jupyterlab');
const { commands, shell } = app;

/**
* Whether there is an active xircuits.
*/
function isEnabled(): boolean {
return (
tracker.currentWidget !== null &&
tracker.currentWidget === shell.currentWidget
);
}

//Add command to edit literal component
commands.addCommand(commandIDs.editNode, {
execute: editLiteral,
label: trans.__('Edit'),
isEnabled: () => {
const widget = tracker.currentWidget?.content as XPipePanel;
const selectedEntities = widget.xircuitsApp.getDiagramEngine().getModel().getSelectedEntities();
let isNodeSelected: boolean;
_.forEach(selectedEntities, (model) => {
if (model.getOptions()["name"].startsWith("Literal")) {
isNodeSelected = true;
}
});
return isNodeSelected ?? false;
}
});

//Add command to delete node
commands.addCommand(commandIDs.deleteNode, {
execute: deleteNode,
label: "Delete",
isEnabled: () => {
const widget = tracker.currentWidget?.content as XPipePanel;
const selectedEntities = widget.xircuitsApp.getDiagramEngine().getModel().getSelectedEntities();
let isNodeSelected: boolean
if (selectedEntities.length > 0) {
isNodeSelected = true
}
return isNodeSelected ?? false;
}
});

function editLiteral(): void {
const widget = tracker.currentWidget?.content as XPipePanel;

if (widget) {
const selectedEntities = widget.xircuitsApp.getDiagramEngine().getModel().getSelectedEntities();
_.forEach(selectedEntities, (model) => {

let node = null;
let links = widget.xircuitsApp.getDiagramEngine().getModel()["layers"][0]["models"];
let oldValue = model.getPorts()["out-0"].getOptions()["label"]

// Prompt the user to enter new value
let theResponse = window.prompt('Enter New Value (Without Quotes):', oldValue);
if(theResponse == null || theResponse == "" || theResponse == oldValue){
// When Cancel is clicked or no input provided, just return
return
}
node = new CustomNodeModel({ name: model["name"], color: model["color"], extras: { "type": model["extras"]["type"] } });
node.addOutPortEnhance(theResponse, 'out-0');

// Set new node to old node position
let position = model.getPosition();
node.setPosition(position);
widget.xircuitsApp.getDiagramEngine().getModel().addNode(node);

// Update the links
for (let linkID in links) {

let link = links[linkID];

if (link["sourcePort"] && link["targetPort"]) {

let newLink = new DefaultLinkModel();

let sourcePort = node.getPorts()["out-0"];
newLink.setSourcePort(sourcePort);

// This to make sure the new link came from the same literal node as previous link
let sourceLinkNodeId = link["sourcePort"].getParent().getID()
let sourceNodeId = model.getOptions()["id"]
if (sourceLinkNodeId == sourceNodeId) {
newLink.setTargetPort(link["targetPort"]);
}

widget.xircuitsApp.getDiagramEngine().getModel().addLink(newLink)
}
}

// Remove old node
model.remove();
});
}
}

function deleteNode(): void {
const widget = tracker.currentWidget?.content as XPipePanel;

if (widget) {
const selectedEntities = widget.xircuitsApp.getDiagramEngine().getModel().getSelectedEntities();
_.forEach(selectedEntities, (model) => {
if (model.getOptions()["name"] !== "undefined") {
let modelName = model.getOptions()["name"];
const errorMsg = `${modelName} node cannot be deleted!`
if (modelName !== 'Start' && modelName !== 'Finish') {
if (!model.isLocked()) {
model.remove()
} else {
showDialog({
title: 'Locked Node',
body: errorMsg,
buttons: [Dialog.warnButton({ label: 'OK' })]
});
}
}
else {
showDialog({
title: 'Undeletable Node',
body: errorMsg,
buttons: [Dialog.warnButton({ label: 'OK' })]
});
}
}
});
widget.xircuitsApp.getDiagramEngine().repaintCanvas();
}
}
}
7 changes: 5 additions & 2 deletions src/components/CustomNodeFactory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import { CustomNodeModel } from './CustomNodeModel';
import {AbstractReactFactory, GenerateModelEvent, GenerateWidgetEvent} from '@projectstorm/react-canvas-core';
import { DiagramEngine } from '@projectstorm/react-diagrams-core';
import {CustomNodeWidget} from "./CustomNodeWidget";
import { JupyterFrontEnd } from '@jupyterlab/application';

export class CustomNodeFactory extends AbstractReactFactory<CustomNodeModel, DiagramEngine> {
constructor() {
app : JupyterFrontEnd
constructor(app) {
super('custom-node');
this.app = app;
}

generateModel(initialConfig: GenerateModelEvent) {
return new CustomNodeModel();
}

generateReactWidget(event: GenerateWidgetEvent<any>): JSX.Element {
return <CustomNodeWidget engine={this.engine as DiagramEngine} node={event.model} />;
return <CustomNodeWidget engine={this.engine as DiagramEngine} node={event.model} app={this.app}/>;
}
}
71 changes: 9 additions & 62 deletions src/components/CustomNodeWidget.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import * as React from 'react';
import * as _ from 'lodash';
import { DiagramEngine } from '@projectstorm/react-diagrams-core';

import { DefaultLinkModel, DefaultNodeModel ,DefaultPortLabel} from '@projectstorm/react-diagrams';
import { DefaultNodeModel ,DefaultPortLabel} from '@projectstorm/react-diagrams';
import styled from '@emotion/styled';
import "react-image-gallery/styles/css/image-gallery.css";
import ImageGallery from 'react-image-gallery';
Expand All @@ -11,7 +10,8 @@ import { Pagination } from "krc-pagination";
import 'krc-pagination/styles.css';
import { Action, ActionEvent, InputType } from '@projectstorm/react-canvas-core';
import Toggle from 'react-toggle'
import { CustomNodeModel } from './CustomNodeModel';
import { JupyterFrontEnd } from '@jupyterlab/application';
import { commandIDs } from './xircuitBodyWidget';


var S;
Expand Down Expand Up @@ -66,15 +66,17 @@ var S;
export interface DefaultNodeProps {
node: DefaultNodeModel;
engine: DiagramEngine;
app: JupyterFrontEnd;
}

interface CustomDeleteItemsActionOptions {
keyCodes?: number[];
customDelete?: CustomNodeWidget;
app: JupyterFrontEnd;
}

export class CustomDeleteItemsAction extends Action {
constructor(options: CustomDeleteItemsActionOptions = {}) {
constructor(options: CustomDeleteItemsActionOptions) {
options = {
keyCodes: [46, 8],
...options
Expand All @@ -84,24 +86,8 @@ export class CustomDeleteItemsAction extends Action {
type: InputType.KEY_DOWN,
fire: (event: ActionEvent<React.KeyboardEvent>) => {
if (options.keyCodes.indexOf(event.event.keyCode) !== -1) {
const selectedEntities = this.engine.getModel().getSelectedEntities();

_.forEach(selectedEntities, (model) => {
if (model.getOptions()["name"] !== "undefined") {
let modelName = model.getOptions()["name"];
if (modelName !== 'Start' && modelName !== 'Finish') {
if (!model.isLocked()) {
model.remove()
} else {
alert(`${modelName}'s node cannot be deleted!`);
}
}
else {
alert(`${modelName}'s node cannot be deleted!`);
}
}
});
this.engine.repaintCanvas();
const app = options.app;
app.commands.execute(commandIDs.deleteNode)
}
}
});
Expand Down Expand Up @@ -178,46 +164,7 @@ export class CustomNodeWidget extends React.Component<DefaultNodeProps> {
if (!this.props.node.getOptions()["name"].startsWith("Literal")) {
return;
}

let node = null;
var data = this.props.node;
let links = this.props.engine.getModel()["layers"][0]["models"]

// Prompt the user to enter new value
let theResponse = window.prompt('Enter New Value (Without Quotes):');
node = new CustomNodeModel({ name: data["name"], color: data["color"], extras: { "type": data["extras"]["type"] } });
node.addOutPortEnhance(theResponse, 'out-0');

// Set new node to old node position
let position = this.props.node.getPosition();
node.setPosition(position);
this.props.engine.getModel().addNode(node);

// Update the links
for (let linkID in links) {

let link = links[linkID];

if (link["sourcePort"] && link["targetPort"]) {

let newLink = new DefaultLinkModel();

let sourcePort = node.getPorts()["out-0"];
newLink.setSourcePort(sourcePort);

// This to make sure the new link came from the same literal node as previous link
let sourceLinkNodeId = link["sourcePort"].getParent().getID()
let sourceNodeId = data.getOptions()["id"]
if (sourceLinkNodeId == sourceNodeId) {
newLink.setTargetPort(link["targetPort"]);
}

this.props.engine.getModel().addLink(newLink)
}
}

// Remove old node
this.props.node.remove();
this.props.app.commands.execute(commandIDs.editNode)
}

render() {
Expand Down
Loading

0 comments on commit a746884

Please sign in to comment.