org.openremote.client.shell.nodeeditor.NodeEditorPresenter.java Source code

Java tutorial

Introduction

Here is the source code for org.openremote.client.shell.nodeeditor.NodeEditorPresenter.java

Source

/*
 * Copyright 2015, OpenRemote Inc.
 *
 * See the CONTRIBUTORS.txt file in the distribution for a
 * full listing of individual contributors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.openremote.client.shell.nodeeditor;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsonUtils;
import elemental.dom.Element;
import jsinterop.annotations.JsPackage;
import jsinterop.annotations.JsType;
import org.openremote.client.event.*;
import org.openremote.client.shared.AbstractPresenter;
import org.openremote.client.shared.JsUtil;
import org.openremote.client.shared.View;
import org.openremote.client.shared.DOM;
import org.openremote.client.shell.floweditor.FlowDesignerConstants;
import org.openremote.shared.event.FlowLoadEvent;
import org.openremote.shared.event.Message;
import org.openremote.shared.event.client.MessageSendEvent;
import org.openremote.shared.flow.Flow;
import org.openremote.shared.flow.Node;
import org.openremote.shared.flow.Slot;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.openremote.client.shared.Timeout.debounce;

@JsType
public class NodeEditorPresenter extends AbstractPresenter<NodeEditorPresenter.NodeEditorView> {

    private static final Logger LOG = LoggerFactory.getLogger(NodeEditorPresenter.class);

    @JsType(isNative = true)
    public interface NodeEditorView extends View {
        void toggleNodeEditor();
    }

    public Flow flow;
    public Node node;
    public boolean isSubflow;
    public boolean flowNodeDirty;
    public String flowNodeTitle = "Node";
    public Slot[] sinks = new Slot[0];
    public boolean hasMultipleSinks;

    public NodeEditorPresenter(NodeEditorView view) {
        super(view);

        addListener(ShortcutEvent.class, event -> {
            if (node == null)
                return;
            if (event.getKey() == 68) {
                getView().toggleNodeEditor();
            } else if (event.getKey() == 67) {
                duplicateNode();
            } else if (event.getKey() == 46 || event.getKey() == 8) {
                deleteNode();
            }
        });

        addListener(NodeEditEvent.class, event -> {
            this.flow = event.getFlow();
            notifyPath("flow");

            this.node = event.getNode();
            notifyPath("node");

            isSubflow = node.isOfTypeSubflow();
            notifyPath("isSubflow", isSubflow);

            createEditorComponents(node.getEditorSettings().getComponents());

            setSinkSlots();

            // Undo the dirty state by the notifyPath above, so we don't fire node update event
            setFlowNodeDirty(false);
            setFlowNodeTitle();

        });

        addListener(NodeDeletedEvent.class, event -> {
            closeEditor();
        });

        addListener(NodePropertiesRefreshEvent.class, event -> {
            if (node != null && node.getId().equals(event.getNodeId())) {
                updateEditorComponents();
            }
        });

        addListener(NodePropertiesModifiedEvent.class, event -> {
            if (node != null && node.getId().equals(event.getNodeId())) {
                node.setProperties(event.getNodeProperties());
                nodeChanged();
            }
        });

        addListener(FlowEditEvent.class, event -> closeEditor());
        addListener(FlowDeletedEvent.class, event -> closeEditor());

    }

    public void editSubflow() {
        if (!isSubflow)
            return;
        dispatch(new FlowLoadEvent(node.getSubflowId()));
    }

    public void duplicateNode() {
        dispatch(new NodeDuplicateEvent(flow, node));
    }

    public void deleteNode() {
        dispatch(new NodeDeleteEvent(flow, node));
    }

    public String getSinkLabel(Slot sink) {
        return !sink.isLabelEmpty() ? sink.getLabel() + " Slot" : FlowDesignerConstants.SLOT_SINK_LABEL + " Slot";
    }

    public void nodeChanged() {
        // This is complicated: We set everything to dirty and expect to fire an update to
        // everyone. But we give it a chance to become non-dirty of a few milliseconds, then
        // we don't fire. The purpose here is to tame the eager Polymer change observers and
        // to avoid firing too many updates when users edit text.
        setFlowNodeDirty(true);
        setFlowNodeTitle();
        debounce("NodeChange", () -> {
            if (flowNodeDirty && flow != null && node != null) {
                dispatch(new FlowModifiedEvent(flow, true));
                dispatch(new NodeModifiedEvent(flow, node));
                setSinkSlots();
                setFlowNodeDirty(false);
            }
        }, 500);
    }

    public void sendSinkMessage(Slot sink, String body) {
        String instanceId = null;

        if (node.isOfTypeSubflow()) {
            instanceId = flow.getId();
        }

        Message message = new Message(sink, instanceId, body);
        dispatch(new MessageSendEvent(message));
    }

    protected void closeEditor() {
        this.flow = null;
        notifyPathNull("flow");
        this.node = null;
        notifyPathNull("node");
        setFlowNodeDirty(false);
        setFlowNodeTitle();
        removeEditorComponents();
    }

    protected DOM getEditorComponentContainer() {
        return getRequiredElementDOM("#editorComponentContainer");
    }

    protected void createEditorComponents(String[] editorComponents) {
        DOM container = removeEditorComponents();

        if (editorComponents == null)
            return;

        for (String editorComponent : editorComponents) {
            LOG.debug("Creating and adding editor component: " + editorComponent);
            View view = JsUtil.createView(getView(), editorComponent);
            // TODO: error handling, how do we detect a missing editor component?
            view.set("nodeId", node.getId());
            container.appendChild(JsUtil.asElementalElement(view));
        }

        updateEditorComponents();
    }

    protected void updateEditorComponents() {
        JavaScriptObject nodeProperties = JavaScriptObject.createObject();
        if (node.getProperties() != null)
            nodeProperties = JsonUtils.safeEval(node.getProperties());

        // TODO: This was stripped out to null if I use the Component.DOM API by the GWT compiler...
        Element container = JsUtil.asElementalElement(getRequiredElement("#editorComponentContainer"));
        for (int i = 0; i < container.getChildNodes().getLength(); i++) {
            View view = (View) container.getChildNodes().item(i);
            view.set("nodeProperties", nodeProperties);
        }
    }

    protected DOM removeEditorComponents() {
        DOM container = getEditorComponentContainer();
        while (container.getLastChild() != null) {
            container.removeChild(container.getLastChild());
        }
        return container;
    }

    protected void setSinkSlots() {
        if (node.isClientAccess()) {
            sinks = node.findNonPropertySlots(Slot.TYPE_SINK);
            notifyPath("sinks", sinks);
            hasMultipleSinks = sinks.length > 1;
            notifyPath("hasMultipleSinks", hasMultipleSinks);
        } else {
            sinks = new Slot[0];
            notifyPath("sinks", sinks);
            hasMultipleSinks = false;
            notifyPath("hasMultipleSinks", hasMultipleSinks);
        }
    }

    protected void setFlowNodeDirty(boolean dirty) {
        flowNodeDirty = dirty;
        notifyPath("flowNodeDirty", flowNodeDirty);
    }

    protected void setFlowNodeTitle() {
        if (node != null) {
            flowNodeTitle = !node.isLabelEmpty() ? node.getLabel() : node.getEditorSettings().getTypeLabel();
        } else {
            flowNodeTitle = "No node selected";
        }
        notifyPath("flowNodeTitle", flowNodeTitle);
    }
}