Commit 02e0b003 authored by Patrik Meijer's avatar Patrik Meijer
Browse files

Initial work on pointer-creation

parent 035fdb89
Loading
Loading
Loading
Loading
+26 −50
Original line number Diff line number Diff line
@@ -12,12 +12,12 @@ import MenuItem from '@material-ui/core/MenuItem';
export default class ContextMenu extends Component {
    static propTypes = {
        gmeClient: PropTypes.object.isRequired,
        data: PropTypes.object.isRequired,
        nodeId: PropTypes.string.isRequired, // FIXME: should be nodeIds
        eventX: PropTypes.number.isRequired,
        eventY: PropTypes.number.isRequired,
        onClose: PropTypes.func.isRequired,
        setActiveNode: PropTypes.func.isRequired,
        createPointer: PropTypes.func.isRequired,

        readOnly: PropTypes.bool,
    };
@@ -41,6 +41,13 @@ export default class ContextMenu extends Component {
        onClose();
    }

    getCreatePointerFunc = (ptrName) => {
        const {createPointer, nodeId} = this.props;
        return () => {
            createPointer(nodeId, ptrName);
        };
    }

    render() {
        const {
            gmeClient,
@@ -48,63 +55,32 @@ export default class ContextMenu extends Component {
            eventX,
            eventY,
            readOnly,
            data,
            onClose,
        } = this.props;

        // console.log(data);
        const menuItems = [];
        const nodeObj = gmeClient.getNode(nodeId);
        if (false) {
        const metaNodeObj = gmeClient.getNode(nodeObj.getMetaTypeId());

        const pointerNames = metaNodeObj.getValidPointerNames();

        menuItems.push((
                <MenuItem key="meta-type" onClick={this.props.onClose} disabled={readOnly}>
            <MenuItem key="meta-type" onClick={onClose} disabled={readOnly}>
                {`<<${metaNodeObj.getAttribute('name')}>>`}
            </MenuItem>));

            if (metaNodeObj.getAttribute('name') === 'System') {
        menuItems.push((
            <MenuItem key="open-sub-system" onClick={this.setActiveNode} >
                Open Subsystem ...
            </MenuItem>));
            }
        } else if (data.memberAttrs && data.memberAttrs.length > 0) {
            let args;
            data.memberAttrs.forEach((memAttr) => {
                if (memAttr.name === 'args') {
                    args = memAttr.value ? `(${memAttr.value})` : '()';
                }
            });

            if (args) {
        pointerNames.forEach((ptr) => {
            menuItems.push((
                    <MenuItem key="invocation-args" onClick={this.props.onClose}>
                        Invocation arguments: {args}
                <MenuItem key={`create-pointer-${ptr}`} onClick={this.getCreatePointerFunc(ptr)} >
                    {`Create pointer ${ptr} from here ...`}
                </MenuItem>));
            }
        } else if (data.pointerName === 'post' || data.pointerName === 'pre') {
            const methodNode = gmeClient.getNode(data.source);
            const modeNode = gmeClient.getNode(data.target);
            if (methodNode && modeNode) {
                // const parentName = gmeClient.getNode(modeNode.getParentId()).getAttribute('name');
                // const modeName = modeNode.getAttribute('name');
                const methodName = methodNode.getAttribute('name');
                menuItems.push((
                    <MenuItem key="pre-post-condition" onClick={this.props.onClose}>
                        {`${data.pointerName}-condition for ${methodName}`}
                    </MenuItem>));
            }
        } else if (data.setName === 'network-nodes') {
            menuItems.push((
                <MenuItem key="cell-network-nodes" onClick={this.props.onClose}>
                    {`${data.setName}`}
                </MenuItem>));
        }

        if (menuItems.length === 0) {
            this.props.onClose();
            return <div/>;
        }
        });

        return (
            <Menu
@@ -116,7 +92,7 @@ export default class ContextMenu extends Component {
                    left: eventX,
                }}
                open
                onClose={this.props.onClose}
                onClose={onClose}
            >
                {menuItems}
            </Menu>
+63 −57
Original line number Diff line number Diff line
@@ -34,13 +34,20 @@ export default class FilterSelector extends Component {

    static defaultProps = {
        open: true,
    }
    };

    render() {
        const {validItems, activeItems} = this.props;
        const {
            validItems,
            activeItems,
            handleToggle,
            open,
            onClose,
        } = this.props;
        const lists = [];

        Object.keys(validItems).forEach((groupName) => {
        Object.keys(validItems)
            .forEach((groupName) => {
                const items = [];
                validItems[groupName].forEach((item) => {
                    if (item.hidden) {
@@ -55,9 +62,8 @@ export default class FilterSelector extends Component {
                            icon = <TrendingFlatIcon style={{color: 'rgb(85, 123, 139)'}}/>;
                            break;
                        case 'sets':
                        icon = (item.name === 'network-nodes') ?
                            <MoreHorizIcon/> :
                            <CallSplitIcon className="rotate-90"/>;
                            icon = (item.name === 'network-nodes')
                                ? <MoreHorizIcon/> : <CallSplitIcon className="rotate-90"/>;
                            break;
                        case 'nodes':
                            icon = item.isConnection ? <RemoveIcon/> : <FiberManualRecordIcon/>;
@@ -76,7 +82,7 @@ export default class FilterSelector extends Component {
                            <ListItemSecondaryAction>
                                <Switch
                                    onChange={() => {
                                    this.props.handleToggle(itemId);
                                        handleToggle(itemId);
                                    }}
                                    checked={activeItems[itemId]}
                                />
@@ -100,7 +106,7 @@ export default class FilterSelector extends Component {
            });

        return (
            <Dialog open={this.props.open} onClose={this.props.onClose}>
            <Dialog open={open} onClose={onClose}>
                <DialogTitle id="filter-dialog-title">Apply Filters</DialogTitle>
                <DialogContent>
                    {lists}
+231 −163
Original line number Diff line number Diff line
@@ -85,6 +85,12 @@ const DEFAULT_STYLES = [
            'background-color': 'rgba(82, 168, 236, 0.6)',
        },
    },
    {
        selector: 'node.valid-pointer-target',
        style: {
            'background-color': 'rgb(0, 0, 255)',
        },
    },
    // {
    //     selector: 'edge.in-active-selection',
    //     style: {
@@ -186,7 +192,8 @@ export default class GraphEditor extends Component {
            const {validFilters} = this.props;
            const res = {};

            Object.keys(validFilters).forEach((filterType) => {
            Object.keys(validFilters)
                .forEach((filterType) => {
                    validFilters[filterType].forEach((f) => {
                        res[`${filterType}$${f.name}`] = f.active;
                    });
@@ -200,25 +207,27 @@ export default class GraphEditor extends Component {
            return res;
        })(),
        showFilterSelector: false,
        createPointer: null,
    };

    componentWillReceiveProps(newProps) {
        const {activeNode} = newProps;

        if (activeNode !== this.props.activeNode) {
    componentWillReceiveProps({activeNode}) {
        const {activeNode: preActiveNode} = this.props;
        if (activeNode !== preActiveNode) {
            this.reposition = {};
        }
    }

    onListItemClick = id => (
        (/* e */) => {
            this.props.setActiveSelection([id]);
        })
            const {setActiveSelection} = this.props;
            setActiveSelection([id]);
        });

    onListItemDoubleClick = id => (
        (/* e */) => {
            this.props.setActiveNode(id);
        })
            const {setActiveNode} = this.props;
            setActiveNode(id);
        });

    getCytoscapeElements() {
        const {
@@ -227,7 +236,7 @@ export default class GraphEditor extends Component {
            activeSelection,
            nodes,
        } = this.props;
        const {activeFilters} = this.state;
        const {activeFilters, createPointer} = this.state;
        // https://github.com/ybarukh/react-cytoscape/blob/master/sample/app/Graph.js
        // http://js.cytoscape.org/#notation/elements-json
        const result = {
@@ -250,11 +259,12 @@ export default class GraphEditor extends Component {
            //     parentNode = children[parentNode.parent];
            // }

            return JSON.parse(JSON.stringify(parentNode.registries[CONSTANTS.CYTOSCAPE_POS_REG_KEY] ||
                parentNode.registries.position));
            return JSON.parse(JSON.stringify(parentNode.registries[CONSTANTS.CYTOSCAPE_POS_REG_KEY]
                || parentNode.registries.position));
        };

        Object.keys(nodes).forEach((id) => {
        Object.keys(nodes)
            .forEach((id) => {
                if (id === activeNode) {
                    return;
                }
@@ -280,8 +290,9 @@ export default class GraphEditor extends Component {
                        data: {
                            id,
                            name: childData.attributes.name,
                        label: childData.attributes.label ?
                            `${childData.attributes.name}::${childData.attributes.label}` : childData.attributes.name,
                            label: childData.attributes.label
                                ? `${childData.attributes.name}::${childData.attributes.label}`
                                : childData.attributes.name,
                            parent: childData.parent,
                            metaType: childData.metaType,
                        },
@@ -290,6 +301,10 @@ export default class GraphEditor extends Component {
                        classes: `${activeSelection.includes(id) ? 'in-active-selection' : ''}`,
                    };

                    if (createPointer && createPointer.target === id) {
                        cytoData.classes += ' valid-pointer-target';
                    }

                    result.elements.nodes.push(cytoData);

                    // Keep track of the containers s.t. they cannot be grabbed.
@@ -298,7 +313,8 @@ export default class GraphEditor extends Component {
                        nodeIdsWithChildren[childData.parent] = true;
                    }

                Object.keys(childData.sets).forEach((setName) => {
                    Object.keys(childData.sets)
                        .forEach((setName) => {
                            if (!childData.sets[setName] || activeFilters[`sets$${setName}`]) {
                                return;
                            }
@@ -313,7 +329,8 @@ export default class GraphEditor extends Component {
                                        label: setName,
                                        memberAttrs: setMemberData.memberAttrs,
                                    },
                            classes: `set-member ${activeSelection.includes(edgeId) ? 'in-active-selection' : ''}`,
                                    classes: `set-member ${activeSelection.includes(edgeId)
                                        ? 'in-active-selection' : ''}`,
                                };

                                if (setMemberData.label !== null) {
@@ -324,7 +341,8 @@ export default class GraphEditor extends Component {
                            });
                        });

                Object.keys(childData.pointers).forEach((pName) => {
                    Object.keys(childData.pointers)
                        .forEach((pName) => {
                            if (!childData.pointers[pName] || activeFilters[`pointers$${pName}`]) {
                                return;
                            }
@@ -356,7 +374,8 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
                }
            });

        Object.keys(nodeIdsWithChildren).forEach((id) => {
        Object.keys(nodeIdsWithChildren)
            .forEach((id) => {
                nodeMap[id].data.hasChildren = true;
            });

@@ -368,7 +387,8 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,

        if (Object.keys(this.reposition).length > 0) {
            gmeClient.startTransaction();
            Object.keys(this.reposition).forEach((id) => {
            Object.keys(this.reposition)
                .forEach((id) => {
                    if (id.indexOf(nodeId) === 0 || nodeId.indexOf(id) === 0) {
                        gmeClient.setRegistry(
                            id,
@@ -389,52 +409,69 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
    };

    toggleFilter = (filterId) => {
        this.setState({
        this.setState(({activeFilters}) => ({
            activeFilters: update(
                this.state.activeFilters,
                {[filterId]: {$set: !this.state.activeFilters[filterId]}},
                activeFilters,
                {[filterId]: {$set: activeFilters[filterId]}},
            ),
        }));
    };

    startNewPointer = (nodeId, ptrName) => {
        this.setState({
            showNodeMenu: false,
            createPointer: {
                nodeId,
                ptrName,
                target: null,
            },
        });
    };

    attachCytoscapeHandlers() {
        this.cy.on('position', (e) => {
            const cyNode = e.target;
            const childNodeDesc = this.props.nodes[cyNode.id()];
        this.cy.on('position', ({target}) => {
            const {nodes} = this.props;
            const childNodeDesc = nodes[target.id()];
            const floorPos = {
                x: Math.floor(cyNode.position().x),
                y: Math.floor(cyNode.position().y),
                x: Math.floor(target.position().x),
                y: Math.floor(target.position().y),
            };

            if (childNodeDesc &&
                (!childNodeDesc[CONSTANTS.CYTOSCAPE_POS_REG_KEY] ||
                    childNodeDesc[CONSTANTS.CYTOSCAPE_POS_REG_KEY].x !== floorPos.x ||
                    childNodeDesc[CONSTANTS.CYTOSCAPE_POS_REG_KEY].y !== floorPos.y)) {
                this.reposition[cyNode.id()] = {
                    id: cyNode.id(),
            if (childNodeDesc
                && (!childNodeDesc[CONSTANTS.CYTOSCAPE_POS_REG_KEY]
                    || childNodeDesc[CONSTANTS.CYTOSCAPE_POS_REG_KEY].x !== floorPos.x
                    || childNodeDesc[CONSTANTS.CYTOSCAPE_POS_REG_KEY].y !== floorPos.y)) {
                this.reposition[target.id()] = {
                    id: target.id(),
                    position: floorPos,
                };
            }
        });

        this.cy.on('free', (e) => {
            const cyNode = e.target;
            if (typeof cyNode.id === 'function') {
        this.cy.on('free', ({target}) => {
            const {setActiveSelection} = this.props;
            if (typeof target.id === 'function') {
                // console.log('free', cyNode.id(), JSON.stringify(this.reposition));
                this.storePosition(cyNode.id());
                this.storePosition(target.id());
                setTimeout(() => {
                    this.props.setActiveSelection([e.target.id()]);
                    setActiveSelection([target.id()]);
                });
            } else {
                // Free outside of canvas
                this.reposition = {};
                this.props.setActiveSelection([]);
                setActiveSelection([]);
            }
        });

        this.cy.on('vclick', (e) => {
            this.reposition = {};
            const {setActiveSelection, gmeClient} = this.props;
            const {createPointer} = this.state;
            if (typeof e.target.id === 'function') {
                if (createPointer && createPointer.target === e.target.id()) {
                    this.setState({createPointer: null});
                    gmeClient.setPointer(createPointer.nodeId, createPointer.ptrName, createPointer.target);
                } else {
                    // console.log('vclick', e.target.id(), JSON.stringify(this.reposition));
                    this.setState({
                        showNodeMenu: {
@@ -445,13 +482,33 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
                                y: e.originalEvent.clientY,
                            },
                        },
                        createPointer: null,
                    });
                }

                setTimeout(() => {
                    this.props.setActiveSelection([e.target.id()]);
                    setActiveSelection([e.target.id()]);
                });
            } else {
                this.props.setActiveSelection([]);
                setActiveSelection([]);
            }
        });

        this.cy.on('mouseover', 'node', (e) => {
            const {createPointer} = this.state;
            const {gmeClient} = this.props;
            if (createPointer && typeof e.target.id === 'function') {
                const gmeNode = gmeClient.getNode(e.target.id());
                if (gmeNode && gmeNode.isValidTargetOf(createPointer.nodeId, createPointer.ptrName)
                    && createPointer.target !== e.target.id()) {
                    this.setState({
                        createPointer: {
                            nodeId: createPointer.nodeId,
                            prtName: createPointer.prtName,
                            target: e.target.id(),
                        },
                    });
                }
            }
        });
    }
@@ -460,6 +517,7 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
        const {
            showNodeMenu,
            showFilterSelector,
            activeFilters,
        } = this.state;
        const {
            readOnly,
@@ -470,6 +528,8 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
            activeNode,
            activeSelection,
            nodes,
            setActiveNode,
            validFilters,
        } = this.props;

        const cytoData = this.getCytoscapeElements();
@@ -477,7 +537,11 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
        const activeNodeName = nodes[activeNode] ? nodes[activeNode].attributes.name : '';

        return (
            <div style={{width: '100%', height: '100%'}}>
            <div style={{
                width: '100%',
                height: '100%',
            }}
            >
                <div style={{
                    position: 'absolute',
                    top: 5,
@@ -487,7 +551,10 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
                >
                    {readOnly ? <LockIcon/> : null}
                    <span
                        style={{fontSize: 24, color: isActivePanel ? null : 'grey'}}
                        style={{
                            fontSize: 24,
                            color: isActivePanel ? null : 'grey',
                        }}
                    >
                        {activeNodeName}
                        <Tooltip id="tooltip-auto-layout" title="Run auto-layout. Warning this will move everything!">
@@ -551,7 +618,8 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
                    layout={{name: 'preset'/* preset dagre */}}
                    style={DEFAULT_STYLES.concat(cytoData.style)}
                />
                {showNodeMenu && showNodeMenu.id === activeSelection[0] ?
                {showNodeMenu && showNodeMenu.id === activeSelection[0]
                    ? (
                        <ContextMenu
                            gmeClient={gmeClient}
                            nodeId={showNodeMenu.id}
@@ -561,13 +629,13 @@ ${activeSelection.includes(edgeId) ? ' in-active-selection' : ''}`,
                            onClose={() => {
                                this.setState({showNodeMenu: null});
                            }}
                        setActiveNode={this.props.setActiveNode}
                    /> : null}
                            setActiveNode={setActiveNode}
                        />) : null}

                <FilterSelector
                    open={showFilterSelector}
                    validItems={this.props.validFilters}
                    activeItems={this.state.activeFilters}
                    validItems={validFilters}
                    activeItems={activeFilters}
                    handleToggle={this.toggleFilter}
                    onClose={(() => this.setState({showFilterSelector: false}))}
                />