/* 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});
}
}