--- a/svghmi/detachable_pages.ysl2 Sun Apr 19 10:48:34 2020 +0200
+++ b/svghmi/detachable_pages.ysl2 Sun Apr 19 22:01:12 2020 +0200
@@ -7,9 +7,6 @@
const "hmi_pages_descs", "$parsed_widgets/widget[@type = 'Page']";
const "hmi_pages", "$hmi_elements[@id = $hmi_pages_descs/@id]";
-const "keypads_descs", "$parsed_widgets/widget[@type = 'Keypad']";
-const "keypads", "$hmi_elements[@id = $keypads_descs/@id]";
const "default_page" choose {
when "count($hmi_pages) > 1" {
@@ -25,6 +22,14 @@
otherwise > «func:widget($hmi_pages/@id)/arg[1]/@value»
+emit "preamble:default-page" { + | var default_page = "«$default_page»"; +const "keypads_descs", "$parsed_widgets/widget[@type = 'Keypad']"; +const "keypads", "$hmi_elements[@id = $keypads_descs/@id]"; // returns all directly or indirectly refered elements
def "func:refered_elements" {
@@ -94,6 +99,15 @@
const "_detachable_elements", "func:detachable_elements($hmi_pages | $keypads)";
const "detachable_elements", "$_detachable_elements[not(ancestor::*/@id = $_detachable_elements/@id)]";
+emit "epilogue:detachable-elements" { + | var detachable_elements = { + foreach "$detachable_elements"{ + | "«@id»":[id("«@id»"), id("«../@id»")]`if "position()!=last()" > ,` const "forEach_widgets_ids", "$parsed_widgets/widget[@type = 'ForEach']/@id";
const "forEach_widgets", "$hmi_elements[@id = $forEach_widgets_ids]";
const "in_forEach_widget_ids", "func:refered_elements($forEach_widgets)[not(@id = $forEach_widgets_ids)]/@id";
@@ -154,11 +168,18 @@
| }`if "position()!=last()" > ,`
+emit "epilogue:page-desc" { + apply "$hmi_pages", mode="page_desc"; template "*", mode="per_page_widget_template";
emit "debug:detachable-pages" {
foreach "$detachable_elements"{
--- a/svghmi/gen_index_xhtml.xslt Sun Apr 19 10:48:34 2020 +0200
+++ b/svghmi/gen_index_xhtml.xslt Sun Apr 19 22:01:12 2020 +0200
@@ -1,6 +1,7 @@
<xsl:stylesheet xmlns:func="http://exslt.org/functions" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:epilogue="epilogue" xmlns:svg="http://www.w3.org/2000/svg" xmlns:str="http://exslt.org/strings" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:exsl="http://exslt.org/common" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:preamble="preamble" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ns="beremiz" xmlns:cc="http://creativecommons.org/ns#" xmlns:regexp="http://exslt.org/regular-expressions" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:debug="debug" xmlns:dc="http://purl.org/dc/elements/1.1/" extension-element-prefixes="ns func exsl regexp str dyn" version="1.0" exclude-result-prefixes="ns func exsl regexp str dyn debug preamble epilogue">
<xsl:output method="xml" cdata-section-elements="xhtml:script"/>
+ <xsl:variable name="svg" select="/svg:svg"/> <xsl:variable name="hmi_elements" select="//svg:*[starts-with(@inkscape:label, 'HMI:')]"/>
<xsl:variable name="hmitree" select="ns:GetHMITree()"/>
<xsl:variable name="_categories">
@@ -16,6 +17,41 @@
<xsl:apply-templates mode="index" select="$hmitree"/>
<xsl:variable name="indexed_hmitree" select="exsl:node-set($_indexed_hmitree)"/>
+ <xsl:template match="preamble:hmi-tree"> + <xsl:text>var hmi_hash = [</xsl:text> + <xsl:value-of select="$hmitree/@hash"/> + <xsl:text>var heartbeat_index = </xsl:text> + <xsl:value-of select="$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index"/> + <xsl:text>var hmitree_types = [ + <xsl:for-each select="$indexed_hmitree/*"> + <xsl:text> /* </xsl:text> + <xsl:value-of select="@index"/> + <xsl:value-of select="@hmipath"/> + <xsl:text> */ "</xsl:text> + <xsl:value-of select="substring(local-name(), 5)"/> + <xsl:if test="position()!=last()"> <xsl:template mode="index" match="*">
<xsl:param name="index" select="0"/>
<xsl:param name="parentpath" select="''"/>
@@ -262,8 +298,6 @@
<xsl:variable name="hmi_pages_descs" select="$parsed_widgets/widget[@type = 'Page']"/>
<xsl:variable name="hmi_pages" select="$hmi_elements[@id = $hmi_pages_descs/@id]"/>
- <xsl:variable name="keypads_descs" select="$parsed_widgets/widget[@type = 'Keypad']"/>
- <xsl:variable name="keypads" select="$hmi_elements[@id = $keypads_descs/@id]"/>
<xsl:variable name="default_page">
<xsl:when test="count($hmi_pages) > 1">
@@ -284,6 +318,17 @@
+ <preamble:default-page/> + <xsl:template match="preamble:default-page"> + <xsl:text>var default_page = "</xsl:text> + <xsl:value-of select="$default_page"/> + <xsl:variable name="keypads_descs" select="$parsed_widgets/widget[@type = 'Keypad']"/> + <xsl:variable name="keypads" select="$hmi_elements[@id = $keypads_descs/@id]"/> <func:function name="func:refered_elements">
<xsl:param name="elems"/>
<xsl:variable name="descend" select="$elems/descendant-or-self::svg:*"/>
@@ -338,6 +383,29 @@
<xsl:variable name="_detachable_elements" select="func:detachable_elements($hmi_pages | $keypads)"/>
<xsl:variable name="detachable_elements" select="$_detachable_elements[not(ancestor::*/@id = $_detachable_elements/@id)]"/>
+ <epilogue:detachable-elements/> + <xsl:template match="epilogue:detachable-elements"> + <xsl:text>var detachable_elements = { + <xsl:for-each select="$detachable_elements"> + <xsl:text> "</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text>":[id("</xsl:text> + <xsl:value-of select="@id"/> + <xsl:text>"), id("</xsl:text> + <xsl:value-of select="../@id"/> + <xsl:text>")]</xsl:text> + <xsl:if test="position()!=last()"> <xsl:variable name="forEach_widgets_ids" select="$parsed_widgets/widget[@type = 'ForEach']/@id"/>
<xsl:variable name="forEach_widgets" select="$hmi_elements[@id = $forEach_widgets_ids]"/>
<xsl:variable name="in_forEach_widget_ids" select="func:refered_elements($forEach_widgets)[not(@id = $forEach_widgets_ids)]/@id"/>
@@ -459,9 +527,21 @@
+ <xsl:template match="epilogue:page-desc"> + <xsl:text>var page_desc = { + <xsl:apply-templates mode="page_desc" select="$hmi_pages"/> <xsl:template mode="per_page_widget_template" match="*"/>
<debug:detachable-pages/>
<xsl:template match="debug:detachable-pages">
<xsl:for-each select="$detachable_elements">
@@ -590,8 +670,19 @@
<xsl:apply-templates mode="inline_svg" select="/"/>
<xsl:variable name="result_svg_ns" select="exsl:node-set($result_svg)"/>
- <xsl:template match="debug:inline-svg">
+ <xsl:template match="preamble:inline-svg"> + <xsl:text>let id = document.getElementById.bind(document); + <xsl:text>var svg_root = id("</xsl:text> + <xsl:value-of select="$svg/@id"/> + <debug:clone-unlinking/> + <xsl:template match="debug:clone-unlinking"> <xsl:for-each select="$to_unlink">
@@ -600,14 +691,6 @@
- <xsl:template match="preamble:hmi-widget">
- <xsl:text>var hmi_widgets = {
- <xsl:apply-templates mode="hmi_elements" select="$hmi_elements"/>
<xsl:template mode="hmi_elements" match="svg:*">
<xsl:variable name="widget" select="func:widget(@id)"/>
<xsl:variable name="eltid" select="@id"/>
@@ -683,6 +766,14 @@
+ <preamble:hmi-elements/> + <xsl:template match="preamble:hmi-elements"> + <xsl:text>var hmi_widgets = { + <xsl:apply-templates mode="hmi_elements" select="$hmi_elements"/> <xsl:template mode="widget_subscribe" match="widget">
<xsl:text> sub: subscribe,
@@ -1756,6 +1847,31 @@
+ <xsl:template match="epilogue:keypad"> + <xsl:text>var keypads = { + <xsl:for-each select="$keypads_descs"> + <xsl:variable name="keypad_id" select="@id"/> + <xsl:for-each select="arg"> + <xsl:variable name="g" select="$geometry[@Id = $keypad_id]"/> + <xsl:text> "</xsl:text> + <xsl:value-of select="@value"/> + <xsl:text>":["</xsl:text> + <xsl:value-of select="$keypad_id"/> + <xsl:text>", </xsl:text> + <xsl:value-of select="$g/@x"/> + <xsl:text>, </xsl:text> + <xsl:value-of select="$g/@y"/> <xsl:template mode="widget_defs" match="widget[@type='Keypad']">
<xsl:param name="hmi_element"/>
<xsl:call-template name="defs_by_labels">
@@ -2080,1153 +2196,6 @@
- <xsl:template name="scripts">
- <xsl:text>id = idstr => document.getElementById(idstr);
- <xsl:apply-templates select="document('')/*/preamble:*"/>
- <xsl:text>var hmi_hash = [</xsl:text>
- <xsl:value-of select="$hmitree/@hash"/>
- <xsl:text>var heartbeat_index = </xsl:text>
- <xsl:value-of select="$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index"/>
- <xsl:text>var hmitree_types = [
- <xsl:for-each select="$indexed_hmitree/*">
- <xsl:text> /* </xsl:text>
- <xsl:value-of select="@index"/>
- <xsl:value-of select="@hmipath"/>
- <xsl:text> */ "</xsl:text>
- <xsl:value-of select="substring(local-name(), 5)"/>
- <xsl:if test="position()!=last()">
- <xsl:text>var detachable_elements = {
- <xsl:for-each select="$detachable_elements">
- <xsl:text> "</xsl:text>
- <xsl:value-of select="@id"/>
- <xsl:text>":[id("</xsl:text>
- <xsl:value-of select="@id"/>
- <xsl:text>"), id("</xsl:text>
- <xsl:value-of select="../@id"/>
- <xsl:text>")]</xsl:text>
- <xsl:if test="position()!=last()">
- <xsl:text>var page_desc = {
- <xsl:apply-templates mode="page_desc" select="$hmi_pages"/>
- <xsl:text>var keypads = {
- <xsl:for-each select="$keypads_descs">
- <xsl:variable name="keypad_id" select="@id"/>
- <xsl:for-each select="arg">
- <xsl:variable name="g" select="$geometry[@Id = $keypad_id]"/>
- <xsl:text> "</xsl:text>
- <xsl:value-of select="@value"/>
- <xsl:text>":["</xsl:text>
- <xsl:value-of select="$keypad_id"/>
- <xsl:text>", </xsl:text>
- <xsl:value-of select="$g/@x"/>
- <xsl:text>, </xsl:text>
- <xsl:value-of select="$g/@y"/>
- <xsl:text>var default_page = "</xsl:text>
- <xsl:value-of select="$default_page"/>
- <xsl:text>var svg_root = id("</xsl:text>
- <xsl:value-of select="/svg:svg/@id"/>
- <xsl:text>var cache = hmitree_types.map(_ignored => undefined);
- <xsl:text>var updates = {};
- <xsl:text>var need_cache_apply = [];
- <xsl:text>var jumps_need_update = false;
- <xsl:text>var jump_history = [[default_page, undefined]];
- <xsl:text>function dispatch_value_to_widget(widget, index, value, oldval) {
- <xsl:text> let idx = widget.offset ? index - widget.offset : index;
- <xsl:text> let idxidx = widget.indexes.indexOf(idx);
- <xsl:text> let d = widget.dispatch;
- <xsl:text> if(typeof(d) == "function" && idxidx == 0){
- <xsl:text> d.call(widget, value, oldval);
- <xsl:text> else if(typeof(d) == "object" && d.length >= idxidx){
- <xsl:text> d[idxidx].call(widget, value, oldval);
- <xsl:text> /* else dispatch_0, ..., dispatch_n ? */
- <xsl:text> throw new Error("Dunno how to dispatch to widget at index = " + index);
- <xsl:text> } catch(err) {
- <xsl:text> console.log(err);
- <xsl:text>function dispatch_value(index, value) {
- <xsl:text> let widgets = subscribers[index];
- <xsl:text> let oldval = cache[index];
- <xsl:text> cache[index] = value;
- <xsl:text> if(widgets.size > 0) {
- <xsl:text> for(let widget of widgets){
- <xsl:text> dispatch_value_to_widget(widget, index, value, oldval);
- <xsl:text>function init_widgets() {
- <xsl:text> Object.keys(hmi_widgets).forEach(function(id) {
- <xsl:text> let widget = hmi_widgets[id];
- <xsl:text> let init = widget.init;
- <xsl:text> if(typeof(init) == "function"){
- <xsl:text> init.call(widget);
- <xsl:text> } catch(err) {
- <xsl:text> console.log(err);
- <xsl:text>// Open WebSocket to relative "/ws" address
- <xsl:text>var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
- <xsl:text>ws.binaryType = 'arraybuffer';
- <xsl:text>const dvgetters = {
- <xsl:text> INT: (dv,offset) => [dv.getInt16(offset, true), 2],
- <xsl:text> BOOL: (dv,offset) => [dv.getInt8(offset, true), 1],
- <xsl:text> NODE: (dv,offset) => [dv.getInt8(offset, true), 1],
- <xsl:text> STRING: (dv, offset) => {
- <xsl:text> size = dv.getInt8(offset);
- <xsl:text> String.fromCharCode.apply(null, new Uint8Array(
- <xsl:text> dv.buffer, /* original buffer */
- <xsl:text> offset + 1, /* string starts after size*/
- <xsl:text> size /* size of string */
- <xsl:text> )), size + 1]; /* total increment */
- <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets
- <xsl:text>function apply_updates() {
- <xsl:text> for(let index in updates){
- <xsl:text> // serving as a key, index becomes a string
- <xsl:text> // -> pass Number(index) instead
- <xsl:text> dispatch_value(Number(index), updates[index]);
- <xsl:text> delete updates[index];
- <xsl:text>// Called on requestAnimationFrame, modifies DOM
- <xsl:text>var requestAnimationFrameID = null;
- <xsl:text>function animate() {
- <xsl:text> // Do the page swith if any one pending
- <xsl:text> if(current_subscribed_page != current_visible_page){
- <xsl:text> switch_visible_page(current_subscribed_page);
- <xsl:text> while(widget = need_cache_apply.pop()){
- <xsl:text> widget.apply_cache();
- <xsl:text> if(jumps_need_update) update_jumps();
- <xsl:text> apply_updates();
- <xsl:text> requestAnimationFrameID = null;
- <xsl:text>function requestHMIAnimation() {
- <xsl:text> if(requestAnimationFrameID == null){
- <xsl:text> requestAnimationFrameID = window.requestAnimationFrame(animate);
- <xsl:text>// Message reception handler
- <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing
- <xsl:text>// are stored until browser can compute next frame, DOM is left untouched
- <xsl:text>ws.onmessage = function (evt) {
- <xsl:text> let data = evt.data;
- <xsl:text> let dv = new DataView(data);
- <xsl:text> for(let hash_int of hmi_hash) {
- <xsl:text> if(hash_int != dv.getUint8(i)){
- <xsl:text> throw new Error("Hash doesn't match");
- <xsl:text> while(i < data.byteLength){
- <xsl:text> let index = dv.getUint32(i, true);
- <xsl:text> let iectype = hmitree_types[index];
- <xsl:text> if(iectype != undefined){
- <xsl:text> let dvgetter = dvgetters[iectype];
- <xsl:text> let [value, bytesize] = dvgetter(dv,i);
- <xsl:text> updates[index] = value;
- <xsl:text> i += bytesize;
- <xsl:text> throw new Error("Unknown index "+index);
- <xsl:text> // register for rendering on next frame, since there are updates
- <xsl:text> requestHMIAnimation();
- <xsl:text> } catch(err) {
- <xsl:text> // 1003 is for "Unsupported Data"
- <xsl:text> // ws.close(1003, err.message);
- <xsl:text> // TODO : remove debug alert ?
- <xsl:text> alert("Error : "+err.message+"\nHMI will be reloaded.");
- <xsl:text> // force reload ignoring cache
- <xsl:text> location.reload(true);
- <xsl:text>function send_blob(data) {
- <xsl:text> if(data.length > 0) {
- <xsl:text> ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data)));
- <xsl:text>const typedarray_types = {
- <xsl:text> INT: (number) => new Int16Array([number]),
- <xsl:text> BOOL: (truth) => new Int16Array([truth]),
- <xsl:text> NODE: (truth) => new Int16Array([truth]),
- <xsl:text> STRING: (str) => {
- <xsl:text> // beremiz default string max size is 128
- <xsl:text> str = str.slice(0,128);
- <xsl:text> binary = new Uint8Array(str.length + 1);
- <xsl:text> binary[0] = str.length;
- <xsl:text> for(var i = 0; i < str.length; i++){
- <xsl:text> binary[i+1] = str.charCodeAt(i);
- <xsl:text> return binary;
- <xsl:text>function send_reset() {
- <xsl:text> send_blob(new Uint8Array([1])); /* reset = 1 */
- <xsl:text>// subscription state, as it should be in hmi server
- <xsl:text>// hmitree indexed array of integers
- <xsl:text>var subscriptions = hmitree_types.map(_ignored => 0);
- <xsl:text>// subscription state as needed by widget now
- <xsl:text>// hmitree indexed array of Sets of widgets objects
- <xsl:text>var subscribers = hmitree_types.map(_ignored => new Set());
- <xsl:text>// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
- <xsl:text>// Since dispatch directly calls change_hmi_value,
- <xsl:text>// PLC will periodically send variable at given frequency
- <xsl:text>subscribers[heartbeat_index].add({
- <xsl:text> /* type: "Watchdog", */
- <xsl:text> frequency: 1,
- <xsl:text> indexes: [heartbeat_index],
- <xsl:text> dispatch: function(value) {
- <xsl:text> change_hmi_value(heartbeat_index, "+1");
- <xsl:text>function update_subscriptions() {
- <xsl:text> let delta = [];
- <xsl:text> for(let index = 0; index < subscribers.length; index++){
- <xsl:text> let widgets = subscribers[index];
- <xsl:text> // periods are in ms
- <xsl:text> let previous_period = subscriptions[index];
- <xsl:text> // subscribing with a zero period is unsubscribing
- <xsl:text> let new_period = 0;
- <xsl:text> if(widgets.size > 0) {
- <xsl:text> let maxfreq = 0;
- <xsl:text> for(let widget of widgets)
- <xsl:text> if(maxfreq < widget.frequency)
- <xsl:text> maxfreq = widget.frequency;
- <xsl:text> if(maxfreq != 0)
- <xsl:text> new_period = 1000/maxfreq;
- <xsl:text> if(previous_period != new_period) {
- <xsl:text> subscriptions[index] = new_period;
- <xsl:text> new Uint8Array([2]), /* subscribe = 2 */
- <xsl:text> new Uint32Array([index]),
- <xsl:text> new Uint16Array([new_period]));
- <xsl:text> send_blob(delta);
- <xsl:text>function send_hmi_value(index, value) {
- <xsl:text> let iectype = hmitree_types[index];
- <xsl:text> let tobinary = typedarray_types[iectype];
- <xsl:text> new Uint8Array([0]), /* setval = 0 */
- <xsl:text> new Uint32Array([index]),
- <xsl:text> tobinary(value)]);
- <xsl:text> // DON'T DO THAT unless read_iterator in svghmi.c modifies wbuf as well, not only rbuf
- <xsl:text> // cache[index] = value;
- <xsl:text>function apply_hmi_value(index, new_val) {
- <xsl:text> let old_val = cache[index]
- <xsl:text> if(new_val != undefined && old_val != new_val)
- <xsl:text> send_hmi_value(index, new_val);
- <xsl:text> return new_val;
- <xsl:text>function change_hmi_value(index, opstr) {
- <xsl:text> let op = opstr[0];
- <xsl:text> let given_val = opstr.slice(1);
- <xsl:text> let old_val = cache[index]
- <xsl:text> let new_val;
- <xsl:text> eval("new_val"+opstr);
- <xsl:text> if(old_val != undefined)
- <xsl:text> new_val = eval("old_val"+opstr);
- <xsl:text> if(new_val != undefined && old_val != new_val)
- <xsl:text> send_hmi_value(index, new_val);
- <xsl:text> return new_val;
- <xsl:text>var current_visible_page;
- <xsl:text>var current_subscribed_page;
- <xsl:text>var current_page_index;
- <xsl:text>function prepare_svg() {
- <xsl:text> for(let eltid in detachable_elements){
- <xsl:text> let [element,parent] = detachable_elements[eltid];
- <xsl:text> parent.removeChild(element);
- <xsl:text>function switch_page(page_name, page_index) {
- <xsl:text> if(current_subscribed_page != current_visible_page){
- <xsl:text> /* page switch already going */
- <xsl:text> /* TODO LOG ERROR */
- <xsl:text> return false;
- <xsl:text> if(page_name == undefined)
- <xsl:text> page_name = current_subscribed_page;
- <xsl:text> let old_desc = page_desc[current_subscribed_page];
- <xsl:text> let new_desc = page_desc[page_name];
- <xsl:text> if(new_desc == undefined){
- <xsl:text> /* TODO LOG ERROR */
- <xsl:text> return false;
- <xsl:text> if(page_index == undefined){
- <xsl:text> page_index = new_desc.page_index;
- <xsl:text> if(old_desc){
- <xsl:text> old_desc.absolute_widgets.map(w=>w.unsub());
- <xsl:text> old_desc.relative_widgets.map(w=>w.unsub());
- <xsl:text> new_desc.absolute_widgets.map(w=>w.sub());
- <xsl:text> var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
- <xsl:text> new_desc.relative_widgets.map(w=>w.sub(new_offset));
- <xsl:text> update_subscriptions();
- <xsl:text> current_subscribed_page = page_name;
- <xsl:text> current_page_index = page_index;
- <xsl:text> jumps_need_update = true;
- <xsl:text> requestHMIAnimation();
- <xsl:text> jump_history.push([page_name, page_index]);
- <xsl:text> if(jump_history.length > 42)
- <xsl:text> jump_history.shift();
- <xsl:text> return true;
- <xsl:text>function* chain(a,b){
- <xsl:text>function unsubscribe(){
- <xsl:text> /* remove subsribers */
- <xsl:text> for(let index of this.indexes){
- <xsl:text> let idx = index + this.offset;
- <xsl:text> subscribers[idx].delete(this);
- <xsl:text> this.offset = 0;
- <xsl:text>function subscribe(new_offset=0){
- <xsl:text> /* set the offset because relative */
- <xsl:text> this.offset = new_offset;
- <xsl:text> /* add this's subsribers */
- <xsl:text> for(let index of this.indexes){
- <xsl:text> subscribers[index + new_offset].add(this);
- <xsl:text> need_cache_apply.push(this);
- <xsl:text>function foreach_unsubscribe(){
- <xsl:text> for(let item of this.items){
- <xsl:text> for(let widget of item) {
- <xsl:text> unsubscribe.call(widget);
- <xsl:text> this.offset = 0;
- <xsl:text>function foreach_widgets_do(new_offset, todo){
- <xsl:text> this.offset = new_offset;
- <xsl:text> for(let i = 0; i < this.items.length; i++) {
- <xsl:text> let item = this.items[i];
- <xsl:text> let orig_item_index = this.index_pool[i];
- <xsl:text> let item_index = this.index_pool[i+this.item_offset];
- <xsl:text> let item_index_offset = item_index - orig_item_index;
- <xsl:text> for(let widget of item) {
- <xsl:text> todo.call(widget, new_offset + item_index_offset);
- <xsl:text>function foreach_subscribe(new_offset=0){
- <xsl:text> foreach_widgets_do.call(this, new_offset, subscribe);
- <xsl:text>function widget_apply_cache() {
- <xsl:text> for(let index of this.indexes){
- <xsl:text> /* dispatch current cache in newly opened page widgets */
- <xsl:text> let realindex = index+this.offset;
- <xsl:text> let cached_val = cache[realindex];
- <xsl:text> if(cached_val != undefined)
- <xsl:text> dispatch_value_to_widget(this, realindex, cached_val, cached_val);
- <xsl:text>function foreach_apply_cache() {
- <xsl:text> foreach_widgets_do.call(this, this.offset, widget_apply_cache);
- <xsl:text>function foreach_onclick(opstr, evt) {
- <xsl:text> new_item_offset = eval(String(this.item_offset)+opstr)
- <xsl:text> if(new_item_offset + this.items.length > this.index_pool.length) {
- <xsl:text> if(this.item_offset + this.items.length == this.index_pool.length)
- <xsl:text> new_item_offset = 0;
- <xsl:text> new_item_offset = this.index_pool.length - this.items.length;
- <xsl:text> } else if(new_item_offset < 0) {
- <xsl:text> if(this.item_offset == 0)
- <xsl:text> new_item_offset = this.index_pool.length - this.items.length;
- <xsl:text> new_item_offset = 0;
- <xsl:text> this.item_offset = new_item_offset;
- <xsl:text> off = this.offset;
- <xsl:text> foreach_unsubscribe.call(this);
- <xsl:text> foreach_subscribe.call(this,off);
- <xsl:text> update_subscriptions();
- <xsl:text> need_cache_apply.push(this);
- <xsl:text> jumps_need_update = true;
- <xsl:text> requestHMIAnimation();
- <xsl:text>function switch_visible_page(page_name) {
- <xsl:text> let old_desc = page_desc[current_visible_page];
- <xsl:text> let new_desc = page_desc[page_name];
- <xsl:text> if(old_desc){
- <xsl:text> for(let eltid in old_desc.required_detachables){
- <xsl:text> if(!(eltid in new_desc.required_detachables)){
- <xsl:text> let [element, parent] = old_desc.required_detachables[eltid];
- <xsl:text> parent.removeChild(element);
- <xsl:text> for(let eltid in new_desc.required_detachables){
- <xsl:text> if(!(eltid in old_desc.required_detachables)){
- <xsl:text> let [element, parent] = new_desc.required_detachables[eltid];
- <xsl:text> parent.appendChild(element);
- <xsl:text> for(let eltid in new_desc.required_detachables){
- <xsl:text> let [element, parent] = new_desc.required_detachables[eltid];
- <xsl:text> parent.appendChild(element);
- <xsl:text> svg_root.setAttribute('viewBox',new_desc.bbox.join(" "));
- <xsl:text> current_visible_page = page_name;
- <xsl:text>function update_jumps() {
- <xsl:text> page_desc[current_visible_page].jumps.map(w=>w.notify_page_change(current_visible_page,current_page_index));
- <xsl:text> jumps_need_update = false;
- <xsl:text>// Once connection established
- <xsl:text>ws.onopen = function (evt) {
- <xsl:text> init_widgets();
- <xsl:text> send_reset();
- <xsl:text> // show main page
- <xsl:text> prepare_svg();
- <xsl:text> switch_page(default_page);
- <xsl:text>ws.onclose = function (evt) {
- <xsl:text> // TODO : add visible notification while waiting for reload
- <xsl:text> console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s.");
- <xsl:text> // TODO : re-enable auto reload when not in debug
- <xsl:text> //window.setTimeout(() => location.reload(true), 10000);
- <xsl:text> alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
- <xsl:text>var xmlns = "http://www.w3.org/2000/svg";
- <xsl:text>var edit_callback;
- <xsl:text>function edit_value(path, valuetype, callback, initial) {
- <xsl:text> let [keypadid, xcoord, ycoord] = keypads[valuetype];
- <xsl:text> console.log('XXX TODO : Edit value', path, valuetype, callback, initial, keypadid);
- <xsl:text> edit_callback = callback;
- <xsl:text> let widget = hmi_widgets[keypadid];
- <xsl:text> widget.start_edit(path, valuetype, callback, initial);
- <xsl:text>var current_modal; /* TODO stack ?*/
- <xsl:text>function show_modal() {
- <xsl:text> let [element, parent] = detachable_elements[this.element.id];
- <xsl:text> tmpgrp = document.createElementNS(xmlns,"g");
- <xsl:text> tmpgrpattr = document.createAttribute("transform");
- <xsl:text> let [xcoord,ycoord] = this.coordinates;
- <xsl:text> let [xdest,ydest] = page_desc[current_visible_page].bbox;
- <xsl:text> tmpgrpattr.value = "translate("+String(xdest-xcoord)+","+String(ydest-ycoord)+")";
- <xsl:text> tmpgrp.setAttributeNode(tmpgrpattr);
- <xsl:text> tmpgrp.appendChild(element);
- <xsl:text> parent.appendChild(tmpgrp);
- <xsl:text> current_modal = [this.element.id, tmpgrp];
- <xsl:text>function end_modal() {
- <xsl:text> let [eltid, tmpgrp] = current_modal;
- <xsl:text> let [element, parent] = detachable_elements[this.element.id];
- <xsl:text> parent.removeChild(tmpgrp);
- <xsl:text> current_modal = undefined;
- <xsl:text>function widget_active_activable(eltsub) {
- <xsl:text> if(eltsub.inactive_style === undefined)
- <xsl:text> eltsub.inactive_style = eltsub.inactive.getAttribute("style");
- <xsl:text> eltsub.inactive.setAttribute("style", "display:none");
- <xsl:text> if(eltsub.active_style !== undefined)
- <xsl:text> eltsub.active.setAttribute("style", eltsub.active_style);
- <xsl:text> console.log("active", eltsub);
- <xsl:text>function widget_inactive_activable(eltsub) {
- <xsl:text> if(eltsub.active_style === undefined)
- <xsl:text> eltsub.active_style = eltsub.active.getAttribute("style");
- <xsl:text> eltsub.active.setAttribute("style", "display:none");
- <xsl:text> if(eltsub.inactive_style !== undefined)
- <xsl:text> eltsub.inactive.setAttribute("style", eltsub.inactive_style);
- <xsl:text> console.log("inactive", eltsub);
- <xsl:apply-templates select="document('')/*/epilogue:*"/>
<xsl:text>Made with SVGHMI. https://beremiz.org</xsl:text>
@@ -3244,7 +2213,1058 @@
<body style="margin:0;overflow:hidden;">
<xsl:copy-of select="$result_svg"/>
- <xsl:call-template name="scripts"/>
+ <xsl:apply-templates select="document('')/*/preamble:*"/> + <xsl:apply-templates select="document('')/*/epilogue:*"/> + <xsl:text>var cache = hmitree_types.map(_ignored => undefined); + <xsl:text>var updates = {}; + <xsl:text>var need_cache_apply = []; + <xsl:text>var jumps_need_update = false; + <xsl:text>var jump_history = [[default_page, undefined]]; + <xsl:text>function dispatch_value_to_widget(widget, index, value, oldval) { + <xsl:text> let idx = widget.offset ? index - widget.offset : index; + <xsl:text> let idxidx = widget.indexes.indexOf(idx); + <xsl:text> let d = widget.dispatch; + <xsl:text> if(typeof(d) == "function" && idxidx == 0){ + <xsl:text> d.call(widget, value, oldval); + <xsl:text> else if(typeof(d) == "object" && d.length >= idxidx){ + <xsl:text> d[idxidx].call(widget, value, oldval); + <xsl:text> /* else dispatch_0, ..., dispatch_n ? */ + <xsl:text> throw new Error("Dunno how to dispatch to widget at index = " + index); + <xsl:text> } catch(err) { + <xsl:text> console.log(err); + <xsl:text>function dispatch_value(index, value) { + <xsl:text> let widgets = subscribers[index]; + <xsl:text> let oldval = cache[index]; + <xsl:text> cache[index] = value; + <xsl:text> if(widgets.size > 0) { + <xsl:text> for(let widget of widgets){ + <xsl:text> dispatch_value_to_widget(widget, index, value, oldval); + <xsl:text>function init_widgets() { + <xsl:text> Object.keys(hmi_widgets).forEach(function(id) { + <xsl:text> let widget = hmi_widgets[id]; + <xsl:text> let init = widget.init; + <xsl:text> if(typeof(init) == "function"){ + <xsl:text> init.call(widget); + <xsl:text> } catch(err) { + <xsl:text> console.log(err); + <xsl:text>// Open WebSocket to relative "/ws" address + <xsl:text>var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')); + <xsl:text>ws.binaryType = 'arraybuffer'; + <xsl:text>const dvgetters = { + <xsl:text> INT: (dv,offset) => [dv.getInt16(offset, true), 2], + <xsl:text> BOOL: (dv,offset) => [dv.getInt8(offset, true), 1], + <xsl:text> NODE: (dv,offset) => [dv.getInt8(offset, true), 1], + <xsl:text> STRING: (dv, offset) => { + <xsl:text> size = dv.getInt8(offset); + <xsl:text> String.fromCharCode.apply(null, new Uint8Array( + <xsl:text> dv.buffer, /* original buffer */ + <xsl:text> offset + 1, /* string starts after size*/ + <xsl:text> size /* size of string */ + <xsl:text> )), size + 1]; /* total increment */ + <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets + <xsl:text>function apply_updates() { + <xsl:text> for(let index in updates){ + <xsl:text> // serving as a key, index becomes a string + <xsl:text> // -> pass Number(index) instead + <xsl:text> dispatch_value(Number(index), updates[index]); + <xsl:text> delete updates[index]; + <xsl:text>// Called on requestAnimationFrame, modifies DOM + <xsl:text>var requestAnimationFrameID = null; + <xsl:text>function animate() { + <xsl:text> // Do the page swith if any one pending + <xsl:text> if(current_subscribed_page != current_visible_page){ + <xsl:text> switch_visible_page(current_subscribed_page); + <xsl:text> while(widget = need_cache_apply.pop()){ + <xsl:text> widget.apply_cache(); + <xsl:text> if(jumps_need_update) update_jumps(); + <xsl:text> apply_updates(); + <xsl:text> requestAnimationFrameID = null; + <xsl:text>function requestHMIAnimation() { + <xsl:text> if(requestAnimationFrameID == null){ + <xsl:text> requestAnimationFrameID = window.requestAnimationFrame(animate); + <xsl:text>// Message reception handler + <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing + <xsl:text>// are stored until browser can compute next frame, DOM is left untouched + <xsl:text>ws.onmessage = function (evt) { + <xsl:text> let data = evt.data; + <xsl:text> let dv = new DataView(data); + <xsl:text> for(let hash_int of hmi_hash) { + <xsl:text> if(hash_int != dv.getUint8(i)){ + <xsl:text> throw new Error("Hash doesn't match"); + <xsl:text> while(i < data.byteLength){ + <xsl:text> let index = dv.getUint32(i, true); + <xsl:text> let iectype = hmitree_types[index]; + <xsl:text> if(iectype != undefined){ + <xsl:text> let dvgetter = dvgetters[iectype]; + <xsl:text> let [value, bytesize] = dvgetter(dv,i); + <xsl:text> updates[index] = value; + <xsl:text> i += bytesize; + <xsl:text> throw new Error("Unknown index "+index); + <xsl:text> // register for rendering on next frame, since there are updates + <xsl:text> requestHMIAnimation(); + <xsl:text> } catch(err) { + <xsl:text> // 1003 is for "Unsupported Data" + <xsl:text> // ws.close(1003, err.message); + <xsl:text> // TODO : remove debug alert ? + <xsl:text> alert("Error : "+err.message+"\nHMI will be reloaded."); + <xsl:text> // force reload ignoring cache + <xsl:text> location.reload(true); + <xsl:text>function send_blob(data) { + <xsl:text> if(data.length > 0) { + <xsl:text> ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data))); + <xsl:text>const typedarray_types = { + <xsl:text> INT: (number) => new Int16Array([number]), + <xsl:text> BOOL: (truth) => new Int16Array([truth]), + <xsl:text> NODE: (truth) => new Int16Array([truth]), + <xsl:text> STRING: (str) => { + <xsl:text> // beremiz default string max size is 128 + <xsl:text> str = str.slice(0,128); + <xsl:text> binary = new Uint8Array(str.length + 1); + <xsl:text> binary[0] = str.length; + <xsl:text> for(var i = 0; i < str.length; i++){ + <xsl:text> binary[i+1] = str.charCodeAt(i); + <xsl:text> return binary; + <xsl:text>function send_reset() { + <xsl:text> send_blob(new Uint8Array([1])); /* reset = 1 */ + <xsl:text>// subscription state, as it should be in hmi server + <xsl:text>// hmitree indexed array of integers + <xsl:text>var subscriptions = hmitree_types.map(_ignored => 0); + <xsl:text>// subscription state as needed by widget now + <xsl:text>// hmitree indexed array of Sets of widgets objects + <xsl:text>var subscribers = hmitree_types.map(_ignored => new Set()); + <xsl:text>// artificially subscribe the watchdog widget to "/heartbeat" hmi variable + <xsl:text>// Since dispatch directly calls change_hmi_value, + <xsl:text>// PLC will periodically send variable at given frequency + <xsl:text>subscribers[heartbeat_index].add({ + <xsl:text> /* type: "Watchdog", */ + <xsl:text> frequency: 1, + <xsl:text> indexes: [heartbeat_index], + <xsl:text> dispatch: function(value) { + <xsl:text> change_hmi_value(heartbeat_index, "+1"); + <xsl:text>function update_subscriptions() { + <xsl:text> let delta = []; + <xsl:text> for(let index = 0; index < subscribers.length; index++){ + <xsl:text> let widgets = subscribers[index]; + <xsl:text> // periods are in ms + <xsl:text> let previous_period = subscriptions[index]; + <xsl:text> // subscribing with a zero period is unsubscribing + <xsl:text> let new_period = 0; + <xsl:text> if(widgets.size > 0) { + <xsl:text> let maxfreq = 0; + <xsl:text> for(let widget of widgets) + <xsl:text> if(maxfreq < widget.frequency) + <xsl:text> maxfreq = widget.frequency; + <xsl:text> if(maxfreq != 0) + <xsl:text> new_period = 1000/maxfreq; + <xsl:text> if(previous_period != new_period) { + <xsl:text> subscriptions[index] = new_period; + <xsl:text> new Uint8Array([2]), /* subscribe = 2 */ + <xsl:text> new Uint32Array([index]), + <xsl:text> new Uint16Array([new_period])); + <xsl:text> send_blob(delta); + <xsl:text>function send_hmi_value(index, value) { + <xsl:text> let iectype = hmitree_types[index]; + <xsl:text> let tobinary = typedarray_types[iectype]; + <xsl:text> new Uint8Array([0]), /* setval = 0 */ + <xsl:text> new Uint32Array([index]), + <xsl:text> tobinary(value)]); + <xsl:text> // DON'T DO THAT unless read_iterator in svghmi.c modifies wbuf as well, not only rbuf + <xsl:text> // cache[index] = value; + <xsl:text>function apply_hmi_value(index, new_val) { + <xsl:text> let old_val = cache[index] + <xsl:text> if(new_val != undefined && old_val != new_val) + <xsl:text> send_hmi_value(index, new_val); + <xsl:text> return new_val; + <xsl:text>function change_hmi_value(index, opstr) { + <xsl:text> let op = opstr[0]; + <xsl:text> let given_val = opstr.slice(1); + <xsl:text> let old_val = cache[index] + <xsl:text> let new_val; + <xsl:text> eval("new_val"+opstr); + <xsl:text> if(old_val != undefined) + <xsl:text> new_val = eval("old_val"+opstr); + <xsl:text> if(new_val != undefined && old_val != new_val) + <xsl:text> send_hmi_value(index, new_val); + <xsl:text> return new_val; + <xsl:text>var current_visible_page; + <xsl:text>var current_subscribed_page; + <xsl:text>var current_page_index; + <xsl:text>function prepare_svg() { + <xsl:text> for(let eltid in detachable_elements){ + <xsl:text> let [element,parent] = detachable_elements[eltid]; + <xsl:text> parent.removeChild(element); + <xsl:text>function switch_page(page_name, page_index) { + <xsl:text> if(current_subscribed_page != current_visible_page){ + <xsl:text> /* page switch already going */ + <xsl:text> /* TODO LOG ERROR */ + <xsl:text> return false; + <xsl:text> if(page_name == undefined) + <xsl:text> page_name = current_subscribed_page; + <xsl:text> let old_desc = page_desc[current_subscribed_page]; + <xsl:text> let new_desc = page_desc[page_name]; + <xsl:text> if(new_desc == undefined){ + <xsl:text> /* TODO LOG ERROR */ + <xsl:text> return false; + <xsl:text> if(page_index == undefined){ + <xsl:text> page_index = new_desc.page_index; + <xsl:text> if(old_desc){ + <xsl:text> old_desc.absolute_widgets.map(w=>w.unsub()); + <xsl:text> old_desc.relative_widgets.map(w=>w.unsub()); + <xsl:text> new_desc.absolute_widgets.map(w=>w.sub()); + <xsl:text> var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index; + <xsl:text> new_desc.relative_widgets.map(w=>w.sub(new_offset)); + <xsl:text> update_subscriptions(); + <xsl:text> current_subscribed_page = page_name; + <xsl:text> current_page_index = page_index; + <xsl:text> jumps_need_update = true; + <xsl:text> requestHMIAnimation(); + <xsl:text> jump_history.push([page_name, page_index]); + <xsl:text> if(jump_history.length > 42) + <xsl:text> jump_history.shift(); + <xsl:text> return true; + <xsl:text>function* chain(a,b){ + <xsl:text>function unsubscribe(){ + <xsl:text> /* remove subsribers */ + <xsl:text> for(let index of this.indexes){ + <xsl:text> let idx = index + this.offset; + <xsl:text> subscribers[idx].delete(this); + <xsl:text> this.offset = 0; + <xsl:text>function subscribe(new_offset=0){ + <xsl:text> /* set the offset because relative */ + <xsl:text> this.offset = new_offset; + <xsl:text> /* add this's subsribers */ + <xsl:text> for(let index of this.indexes){ + <xsl:text> subscribers[index + new_offset].add(this); + <xsl:text> need_cache_apply.push(this); + <xsl:text>function foreach_unsubscribe(){ + <xsl:text> for(let item of this.items){ + <xsl:text> for(let widget of item) { + <xsl:text> unsubscribe.call(widget); + <xsl:text> this.offset = 0; + <xsl:text>function foreach_widgets_do(new_offset, todo){ + <xsl:text> this.offset = new_offset; + <xsl:text> for(let i = 0; i < this.items.length; i++) { + <xsl:text> let item = this.items[i]; + <xsl:text> let orig_item_index = this.index_pool[i]; + <xsl:text> let item_index = this.index_pool[i+this.item_offset]; + <xsl:text> let item_index_offset = item_index - orig_item_index; + <xsl:text> for(let widget of item) { + <xsl:text> todo.call(widget, new_offset + item_index_offset); + <xsl:text>function foreach_subscribe(new_offset=0){ + <xsl:text> foreach_widgets_do.call(this, new_offset, subscribe); + <xsl:text>function widget_apply_cache() { + <xsl:text> for(let index of this.indexes){ + <xsl:text> /* dispatch current cache in newly opened page widgets */ + <xsl:text> let realindex = index+this.offset; + <xsl:text> let cached_val = cache[realindex]; + <xsl:text> if(cached_val != undefined) + <xsl:text> dispatch_value_to_widget(this, realindex, cached_val, cached_val); + <xsl:text>function foreach_apply_cache() { + <xsl:text> foreach_widgets_do.call(this, this.offset, widget_apply_cache); + <xsl:text>function foreach_onclick(opstr, evt) { + <xsl:text> new_item_offset = eval(String(this.item_offset)+opstr) + <xsl:text> if(new_item_offset + this.items.length > this.index_pool.length) { + <xsl:text> if(this.item_offset + this.items.length == this.index_pool.length) + <xsl:text> new_item_offset = 0; + <xsl:text> new_item_offset = this.index_pool.length - this.items.length; + <xsl:text> } else if(new_item_offset < 0) { + <xsl:text> if(this.item_offset == 0) + <xsl:text> new_item_offset = this.index_pool.length - this.items.length; + <xsl:text> new_item_offset = 0; + <xsl:text> this.item_offset = new_item_offset; + <xsl:text> off = this.offset; + <xsl:text> foreach_unsubscribe.call(this); + <xsl:text> foreach_subscribe.call(this,off); + <xsl:text> update_subscriptions(); + <xsl:text> need_cache_apply.push(this); + <xsl:text> jumps_need_update = true; + <xsl:text> requestHMIAnimation(); + <xsl:text>function switch_visible_page(page_name) { + <xsl:text> let old_desc = page_desc[current_visible_page]; + <xsl:text> let new_desc = page_desc[page_name]; + <xsl:text> if(old_desc){ + <xsl:text> for(let eltid in old_desc.required_detachables){ + <xsl:text> if(!(eltid in new_desc.required_detachables)){ + <xsl:text> let [element, parent] = old_desc.required_detachables[eltid]; + <xsl:text> parent.removeChild(element); + <xsl:text> for(let eltid in new_desc.required_detachables){ + <xsl:text> if(!(eltid in old_desc.required_detachables)){ + <xsl:text> let [element, parent] = new_desc.required_detachables[eltid]; + <xsl:text> parent.appendChild(element); + <xsl:text> for(let eltid in new_desc.required_detachables){ + <xsl:text> let [element, parent] = new_desc.required_detachables[eltid]; + <xsl:text> parent.appendChild(element); + <xsl:text> svg_root.setAttribute('viewBox',new_desc.bbox.join(" ")); + <xsl:text> current_visible_page = page_name; + <xsl:text>function update_jumps() { + <xsl:text> page_desc[current_visible_page].jumps.map(w=>w.notify_page_change(current_visible_page,current_page_index)); + <xsl:text> jumps_need_update = false; + <xsl:text>// Once connection established + <xsl:text>ws.onopen = function (evt) { + <xsl:text> init_widgets(); + <xsl:text> send_reset(); + <xsl:text> // show main page + <xsl:text> prepare_svg(); + <xsl:text> switch_page(default_page); + <xsl:text>ws.onclose = function (evt) { + <xsl:text> // TODO : add visible notification while waiting for reload + <xsl:text> console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s."); + <xsl:text> // TODO : re-enable auto reload when not in debug + <xsl:text> //window.setTimeout(() => location.reload(true), 10000); + <xsl:text> alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+"."); + <xsl:text>var xmlns = "http://www.w3.org/2000/svg"; + <xsl:text>var edit_callback; + <xsl:text>function edit_value(path, valuetype, callback, initial) { + <xsl:text> let [keypadid, xcoord, ycoord] = keypads[valuetype]; + <xsl:text> console.log('XXX TODO : Edit value', path, valuetype, callback, initial, keypadid); + <xsl:text> edit_callback = callback; + <xsl:text> let widget = hmi_widgets[keypadid]; + <xsl:text> widget.start_edit(path, valuetype, callback, initial); + <xsl:text>var current_modal; /* TODO stack ?*/ + <xsl:text>function show_modal() { + <xsl:text> let [element, parent] = detachable_elements[this.element.id]; + <xsl:text> tmpgrp = document.createElementNS(xmlns,"g"); + <xsl:text> tmpgrpattr = document.createAttribute("transform"); + <xsl:text> let [xcoord,ycoord] = this.coordinates; + <xsl:text> let [xdest,ydest] = page_desc[current_visible_page].bbox; + <xsl:text> tmpgrpattr.value = "translate("+String(xdest-xcoord)+","+String(ydest-ycoord)+")"; + <xsl:text> tmpgrp.setAttributeNode(tmpgrpattr); + <xsl:text> tmpgrp.appendChild(element); + <xsl:text> parent.appendChild(tmpgrp); + <xsl:text> current_modal = [this.element.id, tmpgrp]; + <xsl:text>function end_modal() { + <xsl:text> let [eltid, tmpgrp] = current_modal; + <xsl:text> let [element, parent] = detachable_elements[this.element.id]; + <xsl:text> parent.removeChild(tmpgrp); + <xsl:text> current_modal = undefined; + <xsl:text>function widget_active_activable(eltsub) { + <xsl:text> if(eltsub.inactive_style === undefined) + <xsl:text> eltsub.inactive_style = eltsub.inactive.getAttribute("style"); + <xsl:text> eltsub.inactive.setAttribute("style", "display:none"); + <xsl:text> if(eltsub.active_style !== undefined) + <xsl:text> eltsub.active.setAttribute("style", eltsub.active_style); + <xsl:text> console.log("active", eltsub); + <xsl:text>function widget_inactive_activable(eltsub) { + <xsl:text> if(eltsub.active_style === undefined) + <xsl:text> eltsub.active_style = eltsub.active.getAttribute("style"); + <xsl:text> eltsub.active.setAttribute("style", "display:none"); + <xsl:text> if(eltsub.inactive_style !== undefined) + <xsl:text> eltsub.inactive.setAttribute("style", eltsub.inactive_style); + <xsl:text> console.log("inactive", eltsub); --- a/svghmi/gen_index_xhtml.ysl2 Sun Apr 19 10:48:34 2020 +0200
+++ b/svghmi/gen_index_xhtml.ysl2 Sun Apr 19 22:01:12 2020 +0200
@@ -33,8 +33,10 @@
extension-element-prefixes="ns func exsl regexp str dyn"
exclude-result-prefixes="ns func exsl regexp str dyn debug preamble epilogue" {
+ const "svg", "/svg:svg"; const "hmi_elements", "//svg:*[starts-with(@inkscape:label, 'HMI:')]";
@@ -47,7 +49,6 @@
comment > Made with SVGHMI. https://beremiz.org
@@ -68,7 +69,12 @@
+ apply "document('')/*/preamble:*"; + apply "document('')/*/epilogue:*"; --- a/svghmi/hmi_tree.ysl2 Sun Apr 19 10:48:34 2020 +0200
+++ b/svghmi/hmi_tree.ysl2 Sun Apr 19 22:01:12 2020 +0200
@@ -14,6 +14,20 @@
const "_indexed_hmitree" apply "$hmitree", mode="index";
const "indexed_hmitree", "exsl:node-set($_indexed_hmitree)";
+emit "preamble:hmi-tree" { + | var hmi_hash = [«$hmitree/@hash»]; + | var heartbeat_index = «$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index»; + | var hmitree_types = [ + foreach "$indexed_hmitree/*" + | /* «@index» «@hmipath» */ "«substring(local-name(), 5)»"`if "position()!=last()" > ,` template "*", mode="index" {
param "parentpath", "''";
@@ -110,7 +124,6 @@
result "$parsed_widgets/widget[@id = $id]";
def "func:is_descendant_path" {
@@ -136,6 +149,7 @@
with "indent" value "concat($indent,'>')"
apply "$hmitree", mode="testtree";
--- a/svghmi/inline_svg.ysl2 Sun Apr 19 10:48:34 2020 +0200
+++ b/svghmi/inline_svg.ysl2 Sun Apr 19 22:01:12 2020 +0200
@@ -34,9 +34,8 @@
error > All units must be set to "px" in Inkscape's document properties
-//////////////// Clone Unlinking
// svg:use (inkscape's clones) inside a widgets are
// replaced by real elements they refer in order to :
// - allow finding "needle" element in "meter" widget,
@@ -115,7 +114,13 @@
const "result_svg" apply "/", mode="inline_svg";
const "result_svg_ns", "exsl:node-set($result_svg)";
-emit "debug:inline-svg" {
+emit "preamble:inline-svg" { + | let id = document.getElementById.bind(document); + | var svg_root = id("«$svg/@id»"); +emit "debug:clone-unlinking" { --- a/svghmi/widget_keypad.ysl2 Sun Apr 19 10:48:34 2020 +0200
+++ b/svghmi/widget_keypad.ysl2 Sun Apr 19 22:01:12 2020 +0200
@@ -1,5 +1,18 @@
+emit "epilogue:keypad" { + foreach "$keypads_descs"{ + const "keypad_id","@id"; + const "g", "$geometry[@Id = $keypad_id]"; + | "«@value»":["«$keypad_id»", «$g/@x», «$g/@y»], template "widget[@type='Keypad']", mode="widget_defs" {
labels("Esc Enter BackSpace Keys Info Value");
--- a/svghmi/widgets_common.ysl2 Sun Apr 19 10:48:34 2020 +0200
+++ b/svghmi/widgets_common.ysl2 Sun Apr 19 22:01:12 2020 +0200
@@ -49,6 +49,12 @@
| }`if "position()!=last()" > ,`
+emit "preamble:hmi-elements" { + apply "$hmi_elements", mode="hmi_elements"; // default : normal subscribing
template "widget", mode="widget_subscribe" {