Questions on ignition-sdk-examples/perspective-component

Worth a shot, really appreciate that. Will there need to be changes made to the component's .props.json file as well?

If you want maximal integration in the designer, it looks like you want to set up the schema for the style property like this:

          "style": {
            "$ref" : "urn:ignition-schema:schemas/style-properties.schema.json"
          },

Except your key would be buttonStyle.

Hey Paul I'm revisiting this topic and wanted to show you my attempt at it. This is the DesignerHook file.

package org.kanoa.designer;

import com.inductiveautomation.ignition.common.BundleUtil;
import com.inductiveautomation.ignition.common.licensing.LicenseState;
import com.inductiveautomation.ignition.common.util.LoggerEx;
import com.inductiveautomation.perspective.common.api.ComponentDescriptor;
import com.inductiveautomation.perspective.common.api.ComponentDescriptorImpl;
import com.inductiveautomation.ignition.designer.model.AbstractDesignerModuleHook;
import com.inductiveautomation.ignition.designer.model.DesignerContext;
import com.inductiveautomation.ignition.designer.navtree.icon.InteractiveSvgIcon;
import com.inductiveautomation.perspective.designer.DesignerComponentRegistry;
import com.inductiveautomation.perspective.designer.api.ComponentDesignDelegateRegistry;
import com.inductiveautomation.perspective.designer.api.PerspectiveDesignerInterface;
import org.kanoa.common.component.display.Image;
import org.kanoa.common.component.display.Messenger;
import org.kanoa.common.component.display.TagCounter;
import org.kanoa.common.component.display.CopyToClipboard;
import org.kanoa.common.KanoaComponents;
import org.kanoa.designer.component.TagCountDesignDelegate;


/**
 * The 'hook' class for the designer scope of the module. Registered in the ignitionModule configuration of the
 * root build.gradle file.
 */
public class KanoaDesignerHook extends AbstractDesignerModuleHook {
    private static final LoggerEx logger = LoggerEx.newBuilder().build("KanoaComponents");

    private DesignerContext context;
    private DesignerComponentRegistry registry;
    private ComponentDesignDelegateRegistry delegateRegistry;

    static {
        BundleUtil.get().addBundle("kanoacomponents", KanoaDesignerHook.class.getClassLoader(), "kanoacomponents");
    }

    public KanoaDesignerHook() {
        logger.info("Registering Kanoa Components in Designer!");
    }

    @Override
    public void startup(DesignerContext context, LicenseState activationState) {
        this.context = context;
        init();
    }

    private void init() {
        logger.debug("Initializing registry entrants...");

        PerspectiveDesignerInterface pdi = PerspectiveDesignerInterface.get(context);

        registry = pdi.getDesignerComponentRegistry();
        delegateRegistry = pdi.getComponentDesignDelegateRegistry();

        InteractiveSvgIcon componentIcon = InteractiveSvgIcon.createIcon("/gateway/src/main/resources/mounted/img/content-copy.svg");
        ComponentDescriptor CTCdescriptor = ComponentDescriptorImpl.ComponentBuilder.newBuilder()
            .setPaletteCategory(KanoaComponents.COMPONENT_CATEGORY)
            .setId(COMPONENT_ID)
            .setModuleId(KanoaComponents.MODULE_ID)
            .setSchema(SCHEMA) //  this could alternatively be created purely in Java if desired
            .setName("Copy To Clipboard")
            .addPaletteEntry("", "Copy To Clipboard", "A button that copies text to the clipboard.", null, null)
            .setIcon(componentIcon)
            .setDefaultMetaName("copyToClipboard")
            .setResources(KanoaComponents.BROWSER_RESOURCES)
            .build();
        // register components to get them on the palette
        registry.registerComponent(Image.DESCRIPTOR);
        registry.registerComponent(TagCounter.DESCRIPTOR);
        registry.registerComponent(Messenger.DESCRIPTOR);
        registry.registerComponent(CTCdescriptor);

        // register design delegates to get the special config UI when a component type is selected in the designer
        delegateRegistry.register(TagCounter.COMPONENT_ID, new TagCountDesignDelegate());
    }


    @Override
    public void shutdown() {
        removeComponents();
    }

    private void removeComponents() {
        registry.removeComponent(Image.COMPONENT_ID);
        registry.removeComponent(TagCounter.COMPONENT_ID);
        registry.removeComponent(Messenger.COMPONENT_ID);
        registry.removeComponent(CopyToClipboard.COMPONENT_ID);

        delegateRegistry.remove(TagCounter.COMPONENT_ID);
    }
}

So there's a lot in the component's java file in the common subproject that seems like it would need to be imported over. I feel as if this really muddles up the modularity of the code, so I was just checking to see if this is what you meant.

Unfortunately, since the builder on ComponentDescriptorImpl doesn't have a copy constructor, there's no way to avoid the duplication. You could create your own class that implements ComponentDescriptor, instead of using ours. That way, you could use a Supplier<Icon> to allow for a null icon in the 'common' scope, but a 'real' icon in the designer scope. There's definitely improvements we could make in the API here, but there are options. You could also create a 'delegate' abstract class to use in the designer scope, something like the one below. Then when you create an instance, you can 'delegate' to the component descriptor in Common, but choose to override only the getIcon method:

Java Code
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.Icon;

import com.inductiveautomation.ignition.common.gson.JsonObject;
import com.inductiveautomation.ignition.common.jsonschema.JsonSchema;
import com.inductiveautomation.perspective.common.api.BrowserResource;
import com.inductiveautomation.perspective.common.api.ComponentDescriptor;
import com.inductiveautomation.perspective.common.api.ComponentEventDescriptor;
import com.inductiveautomation.perspective.common.api.PaletteEntry;

public abstract class DelegatingComponentDescriptor implements ComponentDescriptor {
    private final ComponentDescriptor delegate;

    public DelegatingComponentDescriptor(ComponentDescriptor delegate) {
        this.delegate = delegate;
    }

    @Override
    @Nonnull
    public String id() {
        return delegate.id();
    }

    @Override
    public String name() {
        return delegate.name();
    }

    @Override
    public boolean deprecated() {
        return delegate.deprecated();
    }

    @Override
    @Nonnull
    public Collection<PaletteEntry> paletteEntries() {
        return delegate.paletteEntries();
    }

    @Override
    @Nonnull
    public String paletteCategory() {
        return delegate.paletteCategory();
    }

    @Override
    @Nonnull
    public String defaultMetaName() {
        return delegate.defaultMetaName();
    }

    @Override
    @Nonnull
    public String moduleId() {
        return delegate.moduleId();
    }

    @Override
    public JsonObject defaultProperties() {
        return delegate.defaultProperties();
    }

    @Override
    public Optional<JsonObject> childPositionDefaults() {
        return delegate.childPositionDefaults();
    }

    @Override
    @Nonnull
    public Set<BrowserResource> browserResources() {
        return delegate.browserResources();
    }

    @Override
    @Nullable
    public JsonSchema schema() {
        return delegate.schema();
    }

    @Override
    @Nullable
    public JsonSchema childPositionSchema() {
        return delegate.childPositionSchema();
    }

    @Override
    @Nonnull
    public Collection<ComponentEventDescriptor> events() {
        return delegate.events();
    }

    @Override
    @Nonnull
    public Collection<ExtensionFunctionDescriptor> extensionFunctions() {
        return delegate.extensionFunctions();
    }

    @Override
    @Nullable
    public JsonObject getInitialProps(String variantId) {
        return delegate.getInitialProps(variantId);
    }

    @Override
    public Optional<JsonObject> getExampleChildPositionDefaults() {
        return delegate.getExampleChildPositionDefaults();
    }

    @Override
    @Nonnull
    public Optional<Icon> getIcon() {
        return delegate.getIcon();
    }
}
        componentRegistry.registerComponent(new DelegatingComponentDescriptor(Image.DESCRIPTOR) {
            @Nonnull
            @Override
            public Optional<Icon> getIcon() {
                return Optional.of(MY_ICON);
            }
        });

This is a common Java approach to make up for concrete classes that aren't sufficiently flexible.

Got it, I think i'd rather just be okay with not having an icon in the designer at this point, but thanks a lot anyway for the suggestion.

Do you know of any resources I could reference to generate a passable certificate and keystore in order to have the module be signed? I've done a good amount of digging online and there aren't too many resources, or they involve using really specific software. Any recommendations or pointers that you would know of?

So I'm running

keytool -genkey -alias server -keyalg RSA -keysize 2048 -keystore keystore.jks

in the root folder of the module project, and get:

'keytool' is not recognized as an internal or external command,
operable program or batch file.

keytool is part of the JDK; go to your Java installation directory and run the command there.