Source: system/application_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.ApplicationSystem = class extends hyrrokkin.SystemBase {

    /**
     * Create a hyrrokkin application view
     */
    constructor() {
        super();
        this.open_node_clients = [];
        this.open_configuration_clients = [];
        this.node_parents = {};
        this.configuration_parents = {};

        // set up during init
        this.workspace_id = null;
        this.application = null;
        this.model = null;
        this.schema = null;
    }

    /**
     * Set the language to be used for a package, use where the package defines localisation bundles
     *
     * @param {string} package_id
     * @param {string} language
     */
    set_package_language(package_id, language) {
        let package_type = this.schema.get_package_type(package_id);
        package_type.get_l10n_utils().set_language(language);
    }

    /**
     * Initialise hyrrokkin application
     *
     * @param {string} application_id - the id of the application
     * @param {string} element_id - the name of the document element into which the application will be loaded
     * @param {Object} application_details - a JSON loaded definition of the application layout and cells
     * @param {Object} options - various options to customise hyrrokkin
     * @param {Object} model - model implementation
     */
    async init(application_id, element_id, application_details, options, model) {
        this.application_id = application_id;
        this.element_id = element_id;
        this.application_details = application_details;
        this.topology_id = application_details.topology_id;
        this.options = options;
        this.workspace_id = this.options.workspace_id;

        this.l10n_utils = await this.load_l10n();

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

        let metadata = this.application_details.metadata || {};
        if (metadata.name) {
            this.header.append("span").attr("class", "hyrrokkin_header_title").text(metadata.name);
        }

        let right_span = this.header.append("span")
            .attr("class", "hyrrokkin_header_right");

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

        this.canvas = this.container.append("div").attr("id", "hyrrokkin_application_canvas");

        this.model = model;

        this.schema = await this.load_schema(this.options.package_urls);

        let core = new hyrrokkin.Core(this.workspace_id, this.topology_id, this.l10n_utils, this.schema, this.model);

        this.set_core(core);

        this.application = new hyrrokkin.Application(core);
        await this.model.bind(this);
        await this.model.load();
        await core.init();
        this.model.request_resume();
        this.view_index = undefined;
        this.style = undefined;

    }

    load_view(width) {
        let new_index = undefined;
        for(let idx=0; idx<this.application_details.layouts.length; idx++) {
            let layout = this.application_details.layouts[idx];
            if (layout.condition.min_width && width < layout.condition.min_width) {
                continue;
            }
            if (layout.condition.max_width && width > layout.condition.max_width) {
                continue;
            }
            new_index = idx;
            break;
        }
        if (new_index === undefined) {
            new_index = 0;
        }
        if (this.view_index !== undefined) {
            if (this.view_index === new_index) {
                return;
            }
            this.remove_cells();
            if (this.style) {
                this.style.remove();
                this.style = undefined;
            }
        }
        this.layout = this.application_details.layouts[new_index];
        this.view_index = new_index;
        this.render();
    }

    open() {
        let view_update_cb = () => {
            let r = this.container.node().getBoundingClientRect();
            this.load_view(r.width);
        }

        view_update_cb();

        window.addEventListener("resize",(evt) => {
            view_update_cb();
        });

    }

    render() {
        this.render_layout_css();
        this.render_cells();
    }

    render_layout_css() {
        let max_w = 1;
        let max_h = 1;
        let css_divs = "";
        let gap = this.layout.gap || 10;
        let row_height = this.layout.row_height || 400;
        for(let div_id in this.layout.cells) {
            let cell = this.layout.cells[div_id];
            let x = cell.x || 1;
            let y = cell.y || 1;
            let w = cell.w || 1;
            let h = cell.h || 1;
            if (x + w - 1 > max_w) {
                max_w = x + w -1;
            }
            if (y + h - 1 > max_h) {
                max_h = y + h -1;
            }
            css_divs += `
            #${div_id} {
                grid-column-start: ${x};
                grid-column-end: ${x+w};
                grid-row-start: ${y};
                grid-row-end: ${y+h};
            }            
            `
        }
        let css_header = `#hyrrokkin_application_canvas {
            margin-top: ${gap}px;
            display: grid;
            grid-template-columns: repeat(${max_w}, 1fr);
            grid-auto-rows: ${row_height}px;
            gap: ${gap}px;
        }`;


        let style = document.createElement("style");
        style.innerText = css_header + css_divs;
        document.head.appendChild(style);
    }

    render_cells() {
        for(let div_id in this.layout.cells) {
            let cell = this.layout.cells[div_id];
            let div = this.canvas.append("div").attr("id",div_id);
            if (cell.node_id) {
                let page_name = cell.page_name || this.get_client_names_for_node(cell.node_id)[0];
                let show_title = cell.show_title || true;
                let show_status = cell.show_status || true;
                this.open_node_client(div_id, cell.node_id, page_name, show_title, show_status)
            }
        }
    }

    remove_cells() {
        for(let div_id in this.layout.cells) {
            let cell = this.layout.cells[div_id];
            let div = document.getElementById(div_id);
            if (cell.node_id) {
                let page_name = cell.page_name || this.get_client_names_for_node(cell.node_id)[0];
                this.close_node_client(cell.node_id, page_name);
            }
            if (div) {
                div.remove();
            }
        }
    }

    get_workspace_id() {
        return this.workspace_id;
    }

    get_id() {
        return this.topology_id;
    }

    get_client_names_for_node(node_id) {
        return this.application.get_node_client_names(node_id);
    }

    get_client_names_for_configuration(package_id) {
        return this.application.get_configuration_client_names(package_id);
    }

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

    link_added(link_id, link_type, from_node_id, from_port_name, to_node_id, to_port_name) {
        this.application.link_added(from_node_id, from_port_name, to_node_id, to_port_name, link_type, link_id);
    }

    design_metadata_updated(metadata) {
        // TODO
    }

    open_node_client(parent_elt_id, node_id, client_name, show_title, show_status) {
        let node = this.application.get_node(node_id);
        let x3_elt = hyrrokkin.$$$(parent_elt_id);
        this.application.create_node_client(x3_elt, node, client_name, show_title, show_status);
        this.open_node_clients.push({"node_id":node_id,"client_name":client_name});
    }

    close_node_client(node_id, client_name) {
        this.application.remove_node_client(node_id, client_name);
        this.open_node_clients = this.open_node_clients.filter(item => { item.node_id !== node_id || item.client_name !== client_name});
    }

    open_configuration_client(parent_elt_id, package_id, client_name, show_title, show_status) {
        let configuration = this.application.get_configuration(package_id);
        let x3_elt = hyrrokkin.$$$(parent_elt_id);
        this.application.create_configuration_client(x3_elt, configuration, client_name, show_title, show_status);
        this.open_configuration_clients.push({"package_id":package_id,"client_name":client_name});
    }

    close_configuration_client(package_id, client_name) {
        this.application.remove_configuration_client(package_id, client_name);
        this.open_configuration_clients = this.open_configuration_clients.filter(item => { item.package_id !== package_id || item.client_name !== client_name});
    }
}