Source: system/designer_system.js

/*   Hyrrokkin - A visual modelling tool for constructing directed graphs.

     Copyright (C) 2022-2026 Visual Topology Ltd

     Licensed under the MIT License
*/

var hyrrokkin = hyrrokkin || {};

hyrrokkin.DesignerSystem = class extends hyrrokkin.SystemBase {

    /**
     * Create a hyrrokkin graphical editor
     */
    constructor() {
        super();
        this.design = null;
        this.container = null;
        this.header = null;
        this.canvas = null;
        this.topology_id = null;
        this.workspace_id = null;
        this.splash_screen = null;
        this.restart_alert = null;
        this.model = null;
        this.available_packages = undefined;
        this.config_status_event_handler = null;
    }

    /**
     * Initialise hyrrokkin graphical editor
     *
     * @param {string} element_id - the name of the document element into which hyrrokkin will be loaded
     * @param {Object} options - various options to customise hyrrokkin
     * @param {Object} model - class instance implementing the hyrrokkin.ModelBase API
     */
    async init(element_id, options, model) {
        this.min_window_width = options["min_window_width"] || 800;
        this.min_window_height = options["min_window_height"] || 600;
        this.splash_options = options["designer_splash"] || { "image_url": "hyrrokkin-ui/images/hyrrokkin-ui.svg" };
        this.canvas_width = options.canvas_width || hyrrokkin.SystemBase.DEFAULT_CANVAS_WIDTH;
        this.canvas_height = options.canvas_height || hyrrokkin.SystemBase.DEFAULT_CANVAS_HEIGHT;

        this.workspace_id = options.workspace_id;
        this.topology_id = options.topology_id;

        // l10n

        let l10n_utils = await this.load_l10n();


        // retrieve available package metadata

        try {
            if (model.get_model_type() === "local") {
                let available_packages = await fetch("https://visualtopology.com/json/available-packages-lite.json");
            } else {
                let available_packages = await fetch("https://visualtopology.com/json/available-packages.json");
            }
            if (available_packages.ok) {
                this.available_packages = await available_packages.json();
            }
        } catch(ex) {
            // ignore errors if available packages cannot be obtained
            this.available_packages = [];
        }

        this.model = model;

        this.container = hyrrokkin.$$$(element_id);
        this.header = this.container.append("div").attr("class", "hyrrokkin_header");

        if (options.designer_title) {
            this.header.append("h4").attr("class", "hyrrokkin_header_title").text(options.designer_title);
            let header_text = this.workspace_id;
            if (header_text) {
                header_text += "/";
            }
            header_text =  this.topology_id;
            this.header.append("span").attr("class", "hyrrokkin_header_text").text(header_text);
        }

        let right_div = this.header.append("div")
            .attr("class", "hyrrokkin_header_right")

        if (options.directory_url) {
            right_div.append("a").text(l10n_utils.localise("directory.open")).attr("href", options.directory_url).attr("class", "hyrrokkin_header_link").attr("target","_self");
        }

        right_div.append("img").attr("src", options.icon_url || "hyrrokkin-ui/images/hyrrokkin-ui.svg").attr("class", "hyrrokkin_header_icon");

        this.canvas = this.container.append("div");

        let schema = await this.load_schema(options.package_urls);
        let core = new hyrrokkin.Core(this.workspace_id, this.topology_id, l10n_utils, schema, model, options.platform_extensions);

        this.set_core(core);

        // open splash screen as soon as possible

        this.open_splash_screen(l10n_utils.localise("designer.loading"));

        await this.open_design();

        await this.model.bind(this);
        await this.model.load();
        await core.init();

        setTimeout(() => {
            this.close_splash_screen();
            this.add_window_size_alerts();
        }, 1000);
    }

    open_splash_screen(message) {
        this.splash_screen = new hyrrokkin.Alert(message, this.splash_options);
        this.config_status_event_handler = this.get_core().add_configuration_status_event_handler((package_id, details) => {
            if (this.splash_screen) {
                if (details && "status_message" in details) {
                    this.splash_screen.update_message(this.get_core().get_schema().get_package_type(package_id).localise(details["status_message"]));
                }
            }
        });
    }

    close_splash_screen() {
        if (this.splash_screen) {
            this.splash_screen.close();
            this.splash_screen = null;
        }
        if (this.config_status_event_handler) {
            this.config_status_event_handler = this.get_core().remove_configuration_event_handler(this.config_status_event_handler);
        }
    }

    async open_design() {
        this.canvas.html("");
        this.canvas.attr("class","hyrrokkin_design_container");
        this.container.style("overflow","hidden");
        this.design = new hyrrokkin.Design(this.core, this.canvas, this.canvas_width, this.canvas_height);
    }

    get_id() {
        return this.topology_id;
    }

    get_workspace_id() {
        return this.workspace_id;
    }

    get_available_packages() {
        return this.available_packages;
    }

    add_window_size_alerts() {
        if (this.min_window_width && this.min_window_height) {
            this.design.add_window_size_alerts(this.min_window_width, this.min_window_height);
        }
    }

    is_empty() {
        return (this.get_nodes().length === 0);
    }

    get_nodes() {
        return this.design.get_network().get_node_list();
    }

    get_package_ids_used_by_nodes() {
        return this.design.get_network().get_package_ids_used_by_nodes();
    }

    node_added(node_id, node_type_name, metadata, copy_from_node_id) {
        let node_type = this.design.get_schema().get_node_type(node_type_name);
        this.design.node_added(node_id, node_type, metadata, copy_from_node_id);
    }

    node_removed(node_id) {
        this.design.node_removed(node_id);
    }

    node_metadata_updated(node_id, metadata) {
        this.design.node_metadata_updated(node_id, metadata);
    }

    get_upstream_nodes(node_id) {
        return this.design.get_network().get_upstream_nodes(node_id);
    }

    get_downstream_nodes(node_id) {
        return this.design.get_network().get_downstream_nodes(node_id);
    }

    design_metadata_updated(metadata) {
        this.design.design_metadata_updated(metadata);
    }

    link_added(link_id, link_type, from_node_id, from_port_name, to_node_id, to_port_name) {
        let from_node = this.design.get_node(from_node_id);
        let from_port = from_node.get_ports()[from_port_name];
        let to_node = this.design.get_node(to_node_id);
        let to_port = to_node.get_ports()[to_port_name];
        this.design.link_added(from_port, to_port, link_type, link_id);
    }

    link_removed(link_id) {
        this.design.link_removed(link_id);
    }

    get_packages() {
        return this.design.get_schema().get_package_types();
    }

    remove_node(node_id) {
        this.design.node_removed(node_id);
    }

    remove_link(link_id) {
        this.design.link_removed(link_id);
    }

    node_blocked(node_id, is_blocked) {
        this.design.node_blocked(node_id, is_blocked);
    }

    clear() {
        this.design.clear();
    }

    close() {
    }

    note_paused() {
        super.note_paused();
        this.design.note_paused();
    }

    note_resumed() {
        super.note_resumed();
        this.design.note_resumed();
    }

    note_restarting() {
        this.design.close_all_node_windows();
        this.open_splash_screen(this.get_core().get_l10n_utils().localise("designer.restarting"));
    }

    note_restarted() {
         setTimeout(() => {
            this.close_splash_screen();
            this.add_window_size_alerts();
        }, 1000);
    }
}