svghmi/gen_index_xhtml.xslt
changeset 3651 e3a29c5b74c4
parent 3629 f117526d41ba
child 3656 efbc86949467
equal deleted inserted replaced
3650:9256c344c2da 3651:e3a29c5b74c4
 11287 </xsl:text>
 11287 </xsl:text>
 11288           <xsl:text>var has_watchdog = window.location.hash == "#watchdog";
 11288           <xsl:text>var has_watchdog = window.location.hash == "#watchdog";
 11289 </xsl:text>
 11289 </xsl:text>
 11290           <xsl:text>
 11290           <xsl:text>
 11291 </xsl:text>
 11291 </xsl:text>
 11292           <xsl:text>var ws_url = 
 11292           <xsl:text>const dvgetters = {
       
 11293 </xsl:text>
       
 11294           <xsl:text>    INT: (dv,offset) =&gt; [dv.getInt16(offset, true), 2],
       
 11295 </xsl:text>
       
 11296           <xsl:text>    BOOL: (dv,offset) =&gt; [dv.getInt8(offset, true), 1],
       
 11297 </xsl:text>
       
 11298           <xsl:text>    NODE: (dv,offset) =&gt; [dv.getInt8(offset, true), 1],
       
 11299 </xsl:text>
       
 11300           <xsl:text>    REAL: (dv,offset) =&gt; [dv.getFloat32(offset, true), 4],
       
 11301 </xsl:text>
       
 11302           <xsl:text>    STRING: (dv, offset) =&gt; {
       
 11303 </xsl:text>
       
 11304           <xsl:text>        const size = dv.getInt8(offset);
       
 11305 </xsl:text>
       
 11306           <xsl:text>        return [
       
 11307 </xsl:text>
       
 11308           <xsl:text>            String.fromCharCode.apply(null, new Uint8Array(
       
 11309 </xsl:text>
       
 11310           <xsl:text>                dv.buffer, /* original buffer */
       
 11311 </xsl:text>
       
 11312           <xsl:text>                offset + 1, /* string starts after size*/
       
 11313 </xsl:text>
       
 11314           <xsl:text>                size /* size of string */
       
 11315 </xsl:text>
       
 11316           <xsl:text>            )), size + 1]; /* total increment */
       
 11317 </xsl:text>
       
 11318           <xsl:text>    }
       
 11319 </xsl:text>
       
 11320           <xsl:text>};
       
 11321 </xsl:text>
       
 11322           <xsl:text>
       
 11323 </xsl:text>
       
 11324           <xsl:text>// Called on requestAnimationFrame, modifies DOM
       
 11325 </xsl:text>
       
 11326           <xsl:text>var requestAnimationFrameID = null;
       
 11327 </xsl:text>
       
 11328           <xsl:text>function animate() {
       
 11329 </xsl:text>
       
 11330           <xsl:text>    let rearm = true;
       
 11331 </xsl:text>
       
 11332           <xsl:text>    do{
       
 11333 </xsl:text>
       
 11334           <xsl:text>        if(page_fading == "pending" || page_fading == "forced"){
       
 11335 </xsl:text>
       
 11336           <xsl:text>            if(page_fading == "pending")
       
 11337 </xsl:text>
       
 11338           <xsl:text>                svg_root.classList.add("fade-out-page");
       
 11339 </xsl:text>
       
 11340           <xsl:text>            page_fading = "in_progress";
       
 11341 </xsl:text>
       
 11342           <xsl:text>            if(page_fading_args.length)
       
 11343 </xsl:text>
       
 11344           <xsl:text>                setTimeout(function(){
       
 11345 </xsl:text>
       
 11346           <xsl:text>                    switch_page(...page_fading_args);
       
 11347 </xsl:text>
       
 11348           <xsl:text>                },1);
       
 11349 </xsl:text>
       
 11350           <xsl:text>            break;
       
 11351 </xsl:text>
       
 11352           <xsl:text>        }
       
 11353 </xsl:text>
       
 11354           <xsl:text>
       
 11355 </xsl:text>
       
 11356           <xsl:text>        // Do the page swith if pending
       
 11357 </xsl:text>
       
 11358           <xsl:text>        if(page_switch_in_progress){
       
 11359 </xsl:text>
       
 11360           <xsl:text>            if(current_subscribed_page != current_visible_page){
       
 11361 </xsl:text>
       
 11362           <xsl:text>                switch_visible_page(current_subscribed_page);
       
 11363 </xsl:text>
       
 11364           <xsl:text>            }
       
 11365 </xsl:text>
       
 11366           <xsl:text>
       
 11367 </xsl:text>
       
 11368           <xsl:text>            page_switch_in_progress = false;
       
 11369 </xsl:text>
       
 11370           <xsl:text>
       
 11371 </xsl:text>
       
 11372           <xsl:text>            if(page_fading == "in_progress"){
       
 11373 </xsl:text>
       
 11374           <xsl:text>                svg_root.classList.remove("fade-out-page");
       
 11375 </xsl:text>
       
 11376           <xsl:text>                page_fading = "off";
       
 11377 </xsl:text>
       
 11378           <xsl:text>            }
       
 11379 </xsl:text>
       
 11380           <xsl:text>        }
       
 11381 </xsl:text>
       
 11382           <xsl:text>
       
 11383 </xsl:text>
       
 11384           <xsl:text>        if(jumps_need_update) update_jumps();
       
 11385 </xsl:text>
       
 11386           <xsl:text>
       
 11387 </xsl:text>
       
 11388           <xsl:text>
       
 11389 </xsl:text>
       
 11390           <xsl:text>        pending_widget_animates.forEach(widget =&gt; widget._animate());
       
 11391 </xsl:text>
       
 11392           <xsl:text>        pending_widget_animates = [];
       
 11393 </xsl:text>
       
 11394           <xsl:text>        rearm = false;
       
 11395 </xsl:text>
       
 11396           <xsl:text>    } while(0);
       
 11397 </xsl:text>
       
 11398           <xsl:text>
       
 11399 </xsl:text>
       
 11400           <xsl:text>    requestAnimationFrameID = null;
       
 11401 </xsl:text>
       
 11402           <xsl:text>
       
 11403 </xsl:text>
       
 11404           <xsl:text>    if(rearm) requestHMIAnimation();
       
 11405 </xsl:text>
       
 11406           <xsl:text>}
       
 11407 </xsl:text>
       
 11408           <xsl:text>
       
 11409 </xsl:text>
       
 11410           <xsl:text>function requestHMIAnimation() {
       
 11411 </xsl:text>
       
 11412           <xsl:text>    if(requestAnimationFrameID == null){
       
 11413 </xsl:text>
       
 11414           <xsl:text>        requestAnimationFrameID = window.requestAnimationFrame(animate);
       
 11415 </xsl:text>
       
 11416           <xsl:text>    }
       
 11417 </xsl:text>
       
 11418           <xsl:text>}
       
 11419 </xsl:text>
       
 11420           <xsl:text>
       
 11421 </xsl:text>
       
 11422           <xsl:text>// Message reception handler
       
 11423 </xsl:text>
       
 11424           <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing
       
 11425 </xsl:text>
       
 11426           <xsl:text>// are stored until browser can compute next frame, DOM is left untouched
       
 11427 </xsl:text>
       
 11428           <xsl:text>function ws_onmessage(evt) {
       
 11429 </xsl:text>
       
 11430           <xsl:text>
       
 11431 </xsl:text>
       
 11432           <xsl:text>    let data = evt.data;
       
 11433 </xsl:text>
       
 11434           <xsl:text>    let dv = new DataView(data);
       
 11435 </xsl:text>
       
 11436           <xsl:text>    let i = 0;
       
 11437 </xsl:text>
       
 11438           <xsl:text>    try {
       
 11439 </xsl:text>
       
 11440           <xsl:text>        for(let hash_int of hmi_hash) {
       
 11441 </xsl:text>
       
 11442           <xsl:text>            if(hash_int != dv.getUint8(i)){
       
 11443 </xsl:text>
       
 11444           <xsl:text>                throw new Error("Hash doesn't match");
       
 11445 </xsl:text>
       
 11446           <xsl:text>            };
       
 11447 </xsl:text>
       
 11448           <xsl:text>            i++;
       
 11449 </xsl:text>
       
 11450           <xsl:text>        };
       
 11451 </xsl:text>
       
 11452           <xsl:text>
       
 11453 </xsl:text>
       
 11454           <xsl:text>        while(i &lt; data.byteLength){
       
 11455 </xsl:text>
       
 11456           <xsl:text>            let index = dv.getUint32(i, true);
       
 11457 </xsl:text>
       
 11458           <xsl:text>            i += 4;
       
 11459 </xsl:text>
       
 11460           <xsl:text>            let iectype = hmitree_types[index];
       
 11461 </xsl:text>
       
 11462           <xsl:text>            if(iectype != undefined){
       
 11463 </xsl:text>
       
 11464           <xsl:text>                let dvgetter = dvgetters[iectype];
       
 11465 </xsl:text>
       
 11466           <xsl:text>                let [value, bytesize] = dvgetter(dv,i);
       
 11467 </xsl:text>
       
 11468           <xsl:text>                dispatch_value(index, value);
       
 11469 </xsl:text>
       
 11470           <xsl:text>                i += bytesize;
       
 11471 </xsl:text>
       
 11472           <xsl:text>            } else {
       
 11473 </xsl:text>
       
 11474           <xsl:text>                throw new Error("Unknown index "+index);
       
 11475 </xsl:text>
       
 11476           <xsl:text>            }
       
 11477 </xsl:text>
       
 11478           <xsl:text>        };
       
 11479 </xsl:text>
       
 11480           <xsl:text>
       
 11481 </xsl:text>
       
 11482           <xsl:text>        // register for rendering on next frame, since there are updates
       
 11483 </xsl:text>
       
 11484           <xsl:text>    } catch(err) {
       
 11485 </xsl:text>
       
 11486           <xsl:text>        // 1003 is for "Unsupported Data"
       
 11487 </xsl:text>
       
 11488           <xsl:text>        // ws.close(1003, err.message);
       
 11489 </xsl:text>
       
 11490           <xsl:text>
       
 11491 </xsl:text>
       
 11492           <xsl:text>        // TODO : remove debug alert ?
       
 11493 </xsl:text>
       
 11494           <xsl:text>        alert("Error : "+err.message+"\nHMI will be reloaded.");
       
 11495 </xsl:text>
       
 11496           <xsl:text>
       
 11497 </xsl:text>
       
 11498           <xsl:text>        // force reload ignoring cache
       
 11499 </xsl:text>
       
 11500           <xsl:text>        location.reload(true);
       
 11501 </xsl:text>
       
 11502           <xsl:text>    }
       
 11503 </xsl:text>
       
 11504           <xsl:text>};
       
 11505 </xsl:text>
       
 11506           <xsl:text>
       
 11507 </xsl:text>
       
 11508           <xsl:text>hmi_hash_u8 = new Uint8Array(hmi_hash);
       
 11509 </xsl:text>
       
 11510           <xsl:text>
       
 11511 </xsl:text>
       
 11512           <xsl:text>var ws = null;
       
 11513 </xsl:text>
       
 11514           <xsl:text>
       
 11515 </xsl:text>
       
 11516           <xsl:text>function send_blob(data) {
       
 11517 </xsl:text>
       
 11518           <xsl:text>    if(ws &amp;&amp; data.length &gt; 0) {
       
 11519 </xsl:text>
       
 11520           <xsl:text>        ws.send(new Blob([hmi_hash_u8].concat(data)));
       
 11521 </xsl:text>
       
 11522           <xsl:text>    };
       
 11523 </xsl:text>
       
 11524           <xsl:text>};
       
 11525 </xsl:text>
       
 11526           <xsl:text>
       
 11527 </xsl:text>
       
 11528           <xsl:text>const typedarray_types = {
       
 11529 </xsl:text>
       
 11530           <xsl:text>    INT: (number) =&gt; new Int16Array([number]),
       
 11531 </xsl:text>
       
 11532           <xsl:text>    BOOL: (truth) =&gt; new Int8Array([truth]),
       
 11533 </xsl:text>
       
 11534           <xsl:text>    NODE: (truth) =&gt; new Int8Array([truth]),
       
 11535 </xsl:text>
       
 11536           <xsl:text>    REAL: (number) =&gt; new Float32Array([number]),
       
 11537 </xsl:text>
       
 11538           <xsl:text>    STRING: (str) =&gt; {
       
 11539 </xsl:text>
       
 11540           <xsl:text>        // beremiz default string max size is 128
       
 11541 </xsl:text>
       
 11542           <xsl:text>        str = str.slice(0,128);
       
 11543 </xsl:text>
       
 11544           <xsl:text>        binary = new Uint8Array(str.length + 1);
       
 11545 </xsl:text>
       
 11546           <xsl:text>        binary[0] = str.length;
       
 11547 </xsl:text>
       
 11548           <xsl:text>        for(let i = 0; i &lt; str.length; i++){
       
 11549 </xsl:text>
       
 11550           <xsl:text>            binary[i+1] = str.charCodeAt(i);
       
 11551 </xsl:text>
       
 11552           <xsl:text>        }
       
 11553 </xsl:text>
       
 11554           <xsl:text>        return binary;
       
 11555 </xsl:text>
       
 11556           <xsl:text>    }
       
 11557 </xsl:text>
       
 11558           <xsl:text>    /* TODO */
       
 11559 </xsl:text>
       
 11560           <xsl:text>};
       
 11561 </xsl:text>
       
 11562           <xsl:text>
       
 11563 </xsl:text>
       
 11564           <xsl:text>function send_reset() {
       
 11565 </xsl:text>
       
 11566           <xsl:text>    send_blob(new Uint8Array([1])); /* reset = 1 */
       
 11567 </xsl:text>
       
 11568           <xsl:text>};
       
 11569 </xsl:text>
       
 11570           <xsl:text>
       
 11571 </xsl:text>
       
 11572           <xsl:text>var subscriptions = [];
       
 11573 </xsl:text>
       
 11574           <xsl:text>
       
 11575 </xsl:text>
       
 11576           <xsl:text>function subscribers(index) {
       
 11577 </xsl:text>
       
 11578           <xsl:text>    let entry = subscriptions[index];
       
 11579 </xsl:text>
       
 11580           <xsl:text>    let res;
       
 11581 </xsl:text>
       
 11582           <xsl:text>    if(entry == undefined){
       
 11583 </xsl:text>
       
 11584           <xsl:text>        res = new Set();
       
 11585 </xsl:text>
       
 11586           <xsl:text>        subscriptions[index] = [res,0];
       
 11587 </xsl:text>
       
 11588           <xsl:text>    }else{
       
 11589 </xsl:text>
       
 11590           <xsl:text>        [res, _ign] = entry;
       
 11591 </xsl:text>
       
 11592           <xsl:text>    }
       
 11593 </xsl:text>
       
 11594           <xsl:text>    return res
       
 11595 </xsl:text>
       
 11596           <xsl:text>}
       
 11597 </xsl:text>
       
 11598           <xsl:text>
       
 11599 </xsl:text>
       
 11600           <xsl:text>function get_subscription_period(index) {
       
 11601 </xsl:text>
       
 11602           <xsl:text>    let entry = subscriptions[index];
       
 11603 </xsl:text>
       
 11604           <xsl:text>    if(entry == undefined)
       
 11605 </xsl:text>
       
 11606           <xsl:text>        return 0;
       
 11607 </xsl:text>
       
 11608           <xsl:text>    let [_ign, period] = entry;
       
 11609 </xsl:text>
       
 11610           <xsl:text>    return period;
       
 11611 </xsl:text>
       
 11612           <xsl:text>}
       
 11613 </xsl:text>
       
 11614           <xsl:text>
       
 11615 </xsl:text>
       
 11616           <xsl:text>function set_subscription_period(index, period) {
       
 11617 </xsl:text>
       
 11618           <xsl:text>    let entry = subscriptions[index];
       
 11619 </xsl:text>
       
 11620           <xsl:text>    if(entry == undefined){
       
 11621 </xsl:text>
       
 11622           <xsl:text>        subscriptions[index] = [new Set(), period];
       
 11623 </xsl:text>
       
 11624           <xsl:text>    } else {
       
 11625 </xsl:text>
       
 11626           <xsl:text>        entry[1] = period;
       
 11627 </xsl:text>
       
 11628           <xsl:text>    }
       
 11629 </xsl:text>
       
 11630           <xsl:text>}
       
 11631 </xsl:text>
       
 11632           <xsl:text>
       
 11633 </xsl:text>
       
 11634           <xsl:text>function reset_subscription_periods() {
       
 11635 </xsl:text>
       
 11636           <xsl:text>    for(let index in subscriptions)
       
 11637 </xsl:text>
       
 11638           <xsl:text>        subscriptions[index][1] = 0;
       
 11639 </xsl:text>
       
 11640           <xsl:text>}
       
 11641 </xsl:text>
       
 11642           <xsl:text>
       
 11643 </xsl:text>
       
 11644           <xsl:text>if(has_watchdog){
       
 11645 </xsl:text>
       
 11646           <xsl:text>    // artificially subscribe the watchdog widget to "/heartbeat" hmi variable
       
 11647 </xsl:text>
       
 11648           <xsl:text>    // Since dispatch directly calls change_hmi_value,
       
 11649 </xsl:text>
       
 11650           <xsl:text>    // PLC will periodically send variable at given frequency
       
 11651 </xsl:text>
       
 11652           <xsl:text>    subscribers(heartbeat_index).add({
       
 11653 </xsl:text>
       
 11654           <xsl:text>        /* type: "Watchdog", */
       
 11655 </xsl:text>
       
 11656           <xsl:text>        frequency: 1,
       
 11657 </xsl:text>
       
 11658           <xsl:text>        indexes: [heartbeat_index],
       
 11659 </xsl:text>
       
 11660           <xsl:text>        new_hmi_value: function(index, value, oldval) {
       
 11661 </xsl:text>
       
 11662           <xsl:text>            apply_hmi_value(heartbeat_index, value+1);
       
 11663 </xsl:text>
       
 11664           <xsl:text>        }
       
 11665 </xsl:text>
       
 11666           <xsl:text>    });
       
 11667 </xsl:text>
       
 11668           <xsl:text>}
       
 11669 </xsl:text>
       
 11670           <xsl:text>
       
 11671 </xsl:text>
       
 11672           <xsl:text>
       
 11673 </xsl:text>
       
 11674           <xsl:text>var page_fading = "off";
       
 11675 </xsl:text>
       
 11676           <xsl:text>var page_fading_args = "off";
       
 11677 </xsl:text>
       
 11678           <xsl:text>function fading_page_switch(...args){
       
 11679 </xsl:text>
       
 11680           <xsl:text>    if(page_fading == "in_progress")
       
 11681 </xsl:text>
       
 11682           <xsl:text>        page_fading = "forced";
       
 11683 </xsl:text>
       
 11684           <xsl:text>    else
       
 11685 </xsl:text>
       
 11686           <xsl:text>        page_fading = "pending";
       
 11687 </xsl:text>
       
 11688           <xsl:text>    page_fading_args = args;
       
 11689 </xsl:text>
       
 11690           <xsl:text>
       
 11691 </xsl:text>
       
 11692           <xsl:text>    requestHMIAnimation();
       
 11693 </xsl:text>
       
 11694           <xsl:text>
       
 11695 </xsl:text>
       
 11696           <xsl:text>}
       
 11697 </xsl:text>
       
 11698           <xsl:text>document.body.style.backgroundColor = "black";
       
 11699 </xsl:text>
       
 11700           <xsl:text>
       
 11701 </xsl:text>
       
 11702           <xsl:text>// subscribe to per instance current page hmi variable
       
 11703 </xsl:text>
       
 11704           <xsl:text>// PLC must prefix page name with "!" for page switch to happen
       
 11705 </xsl:text>
       
 11706           <xsl:text>subscribers(current_page_var_index).add({
       
 11707 </xsl:text>
       
 11708           <xsl:text>    frequency: 1,
       
 11709 </xsl:text>
       
 11710           <xsl:text>    indexes: [current_page_var_index],
       
 11711 </xsl:text>
       
 11712           <xsl:text>    new_hmi_value: function(index, value, oldval) {
       
 11713 </xsl:text>
       
 11714           <xsl:text>        if(value.startsWith("!"))
       
 11715 </xsl:text>
       
 11716           <xsl:text>            fading_page_switch(value.slice(1));
       
 11717 </xsl:text>
       
 11718           <xsl:text>    }
       
 11719 </xsl:text>
       
 11720           <xsl:text>});
       
 11721 </xsl:text>
       
 11722           <xsl:text>
       
 11723 </xsl:text>
       
 11724           <xsl:text>function svg_text_to_multiline(elt) {
       
 11725 </xsl:text>
       
 11726           <xsl:text>    return(Array.prototype.map.call(elt.children, x=&gt;x.textContent).join("\n")); 
       
 11727 </xsl:text>
       
 11728           <xsl:text>}
       
 11729 </xsl:text>
       
 11730           <xsl:text>
       
 11731 </xsl:text>
       
 11732           <xsl:text>function multiline_to_svg_text(elt, str, blank) {
       
 11733 </xsl:text>
       
 11734           <xsl:text>    str.split('\n').map((line,i) =&gt; {elt.children[i].textContent = blank?"":line;});
       
 11735 </xsl:text>
       
 11736           <xsl:text>}
       
 11737 </xsl:text>
       
 11738           <xsl:text>
       
 11739 </xsl:text>
       
 11740           <xsl:text>function switch_langnum(langnum) {
       
 11741 </xsl:text>
       
 11742           <xsl:text>    langnum = Math.max(0, Math.min(langs.length - 1, langnum));
       
 11743 </xsl:text>
       
 11744           <xsl:text>
       
 11745 </xsl:text>
       
 11746           <xsl:text>    for (let translation of translations) {
       
 11747 </xsl:text>
       
 11748           <xsl:text>        let [objs, msgs] = translation;
       
 11749 </xsl:text>
       
 11750           <xsl:text>        let msg = msgs[langnum];
       
 11751 </xsl:text>
       
 11752           <xsl:text>        for (let obj of objs) {
       
 11753 </xsl:text>
       
 11754           <xsl:text>            multiline_to_svg_text(obj, msg);
       
 11755 </xsl:text>
       
 11756           <xsl:text>            obj.setAttribute("lang",langnum);
       
 11757 </xsl:text>
       
 11758           <xsl:text>        }
       
 11759 </xsl:text>
       
 11760           <xsl:text>    }
       
 11761 </xsl:text>
       
 11762           <xsl:text>    return langnum;
       
 11763 </xsl:text>
       
 11764           <xsl:text>}
       
 11765 </xsl:text>
       
 11766           <xsl:text>
       
 11767 </xsl:text>
       
 11768           <xsl:text>// backup original texts
       
 11769 </xsl:text>
       
 11770           <xsl:text>for (let translation of translations) {
       
 11771 </xsl:text>
       
 11772           <xsl:text>    let [objs, msgs] = translation;
       
 11773 </xsl:text>
       
 11774           <xsl:text>    msgs.unshift(svg_text_to_multiline(objs[0])); 
       
 11775 </xsl:text>
       
 11776           <xsl:text>}
       
 11777 </xsl:text>
       
 11778           <xsl:text>
       
 11779 </xsl:text>
       
 11780           <xsl:text>var lang_local_index = hmi_local_index("lang");
       
 11781 </xsl:text>
       
 11782           <xsl:text>var langcode_local_index = hmi_local_index("lang_code");
       
 11783 </xsl:text>
       
 11784           <xsl:text>var langname_local_index = hmi_local_index("lang_name");
       
 11785 </xsl:text>
       
 11786           <xsl:text>subscribers(lang_local_index).add({
       
 11787 </xsl:text>
       
 11788           <xsl:text>    indexes: [lang_local_index],
       
 11789 </xsl:text>
       
 11790           <xsl:text>    new_hmi_value: function(index, value, oldval) {
       
 11791 </xsl:text>
       
 11792           <xsl:text>        let current_lang =  switch_langnum(value);
       
 11793 </xsl:text>
       
 11794           <xsl:text>        let [langname,langcode] = langs[current_lang];
       
 11795 </xsl:text>
       
 11796           <xsl:text>        apply_hmi_value(langcode_local_index, langcode);
       
 11797 </xsl:text>
       
 11798           <xsl:text>        apply_hmi_value(langname_local_index, langname);
       
 11799 </xsl:text>
       
 11800           <xsl:text>        switch_page();
       
 11801 </xsl:text>
       
 11802           <xsl:text>    }
       
 11803 </xsl:text>
       
 11804           <xsl:text>});
       
 11805 </xsl:text>
       
 11806           <xsl:text>
       
 11807 </xsl:text>
       
 11808           <xsl:text>// returns en_US, fr_FR or en_UK depending on selected language
       
 11809 </xsl:text>
       
 11810           <xsl:text>function get_current_lang_code(){
       
 11811 </xsl:text>
       
 11812           <xsl:text>    return cache[langcode_local_index];
       
 11813 </xsl:text>
       
 11814           <xsl:text>}
       
 11815 </xsl:text>
       
 11816           <xsl:text>
       
 11817 </xsl:text>
       
 11818           <xsl:text>function setup_lang(){
       
 11819 </xsl:text>
       
 11820           <xsl:text>    let current_lang = cache[lang_local_index];
       
 11821 </xsl:text>
       
 11822           <xsl:text>    let new_lang = switch_langnum(current_lang);
       
 11823 </xsl:text>
       
 11824           <xsl:text>    if(current_lang != new_lang){
       
 11825 </xsl:text>
       
 11826           <xsl:text>        apply_hmi_value(lang_local_index, new_lang);
       
 11827 </xsl:text>
       
 11828           <xsl:text>    }
       
 11829 </xsl:text>
       
 11830           <xsl:text>}
       
 11831 </xsl:text>
       
 11832           <xsl:text>
       
 11833 </xsl:text>
       
 11834           <xsl:text>setup_lang();
       
 11835 </xsl:text>
       
 11836           <xsl:text>
       
 11837 </xsl:text>
       
 11838           <xsl:text>function update_subscriptions() {
       
 11839 </xsl:text>
       
 11840           <xsl:text>    let delta = [];
       
 11841 </xsl:text>
       
 11842           <xsl:text>    if(!ws)
       
 11843 </xsl:text>
       
 11844           <xsl:text>        // dont' change subscriptions if not connected
       
 11845 </xsl:text>
       
 11846           <xsl:text>        return;
       
 11847 </xsl:text>
       
 11848           <xsl:text>
       
 11849 </xsl:text>
       
 11850           <xsl:text>    for(let index in subscriptions){
       
 11851 </xsl:text>
       
 11852           <xsl:text>        let widgets = subscribers(index);
       
 11853 </xsl:text>
       
 11854           <xsl:text>
       
 11855 </xsl:text>
       
 11856           <xsl:text>        // periods are in ms
       
 11857 </xsl:text>
       
 11858           <xsl:text>        let previous_period = get_subscription_period(index);
       
 11859 </xsl:text>
       
 11860           <xsl:text>
       
 11861 </xsl:text>
       
 11862           <xsl:text>        // subscribing with a zero period is unsubscribing
       
 11863 </xsl:text>
       
 11864           <xsl:text>        let new_period = 0;
       
 11865 </xsl:text>
       
 11866           <xsl:text>        if(widgets.size &gt; 0) {
       
 11867 </xsl:text>
       
 11868           <xsl:text>            let maxfreq = 0;
       
 11869 </xsl:text>
       
 11870           <xsl:text>            for(let widget of widgets){
       
 11871 </xsl:text>
       
 11872           <xsl:text>                let wf = widget.frequency;
       
 11873 </xsl:text>
       
 11874           <xsl:text>                if(wf != undefined &amp;&amp; maxfreq &lt; wf)
       
 11875 </xsl:text>
       
 11876           <xsl:text>                    maxfreq = wf;
       
 11877 </xsl:text>
       
 11878           <xsl:text>            }
       
 11879 </xsl:text>
       
 11880           <xsl:text>
       
 11881 </xsl:text>
       
 11882           <xsl:text>            if(maxfreq != 0)
       
 11883 </xsl:text>
       
 11884           <xsl:text>                new_period = 1000/maxfreq;
       
 11885 </xsl:text>
       
 11886           <xsl:text>        }
       
 11887 </xsl:text>
       
 11888           <xsl:text>
       
 11889 </xsl:text>
       
 11890           <xsl:text>        if(previous_period != new_period) {
       
 11891 </xsl:text>
       
 11892           <xsl:text>            set_subscription_period(index, new_period);
       
 11893 </xsl:text>
       
 11894           <xsl:text>            if(index &lt;= last_remote_index){
       
 11895 </xsl:text>
       
 11896           <xsl:text>                delta.push(
       
 11897 </xsl:text>
       
 11898           <xsl:text>                    new Uint8Array([2]), /* subscribe = 2 */
       
 11899 </xsl:text>
       
 11900           <xsl:text>                    new Uint32Array([index]),
       
 11901 </xsl:text>
       
 11902           <xsl:text>                    new Uint16Array([new_period]));
       
 11903 </xsl:text>
       
 11904           <xsl:text>            }
       
 11905 </xsl:text>
       
 11906           <xsl:text>        }
       
 11907 </xsl:text>
       
 11908           <xsl:text>    }
       
 11909 </xsl:text>
       
 11910           <xsl:text>    send_blob(delta);
       
 11911 </xsl:text>
       
 11912           <xsl:text>};
       
 11913 </xsl:text>
       
 11914           <xsl:text>
       
 11915 </xsl:text>
       
 11916           <xsl:text>function send_hmi_value(index, value) {
       
 11917 </xsl:text>
       
 11918           <xsl:text>    if(index &gt; last_remote_index){
       
 11919 </xsl:text>
       
 11920           <xsl:text>        dispatch_value(index, value);
       
 11921 </xsl:text>
       
 11922           <xsl:text>
       
 11923 </xsl:text>
       
 11924           <xsl:text>        if(persistent_indexes.has(index)){
       
 11925 </xsl:text>
       
 11926           <xsl:text>            let varname = persistent_indexes.get(index);
       
 11927 </xsl:text>
       
 11928           <xsl:text>            document.cookie = varname+"="+value+"; max-age=3153600000";
       
 11929 </xsl:text>
       
 11930           <xsl:text>        }
       
 11931 </xsl:text>
       
 11932           <xsl:text>
       
 11933 </xsl:text>
       
 11934           <xsl:text>        return;
       
 11935 </xsl:text>
       
 11936           <xsl:text>    }
       
 11937 </xsl:text>
       
 11938           <xsl:text>
       
 11939 </xsl:text>
       
 11940           <xsl:text>    let iectype = hmitree_types[index];
       
 11941 </xsl:text>
       
 11942           <xsl:text>    let tobinary = typedarray_types[iectype];
       
 11943 </xsl:text>
       
 11944           <xsl:text>    send_blob([
       
 11945 </xsl:text>
       
 11946           <xsl:text>        new Uint8Array([0]),  /* setval = 0 */
       
 11947 </xsl:text>
       
 11948           <xsl:text>        new Uint32Array([index]),
       
 11949 </xsl:text>
       
 11950           <xsl:text>        tobinary(value)]);
       
 11951 </xsl:text>
       
 11952           <xsl:text>
       
 11953 </xsl:text>
       
 11954           <xsl:text>    // DON'T DO THAT unless read_iterator in svghmi.c modifies wbuf as well, not only rbuf
       
 11955 </xsl:text>
       
 11956           <xsl:text>    // cache[index] = value;
       
 11957 </xsl:text>
       
 11958           <xsl:text>};
       
 11959 </xsl:text>
       
 11960           <xsl:text>
       
 11961 </xsl:text>
       
 11962           <xsl:text>function apply_hmi_value(index, new_val) {
       
 11963 </xsl:text>
       
 11964           <xsl:text>    // Similarly to previous comment, taking decision to update based 
       
 11965 </xsl:text>
       
 11966           <xsl:text>    // on cache content is bad and can lead to inconsistency
       
 11967 </xsl:text>
       
 11968           <xsl:text>    /*let old_val = cache[index];*/
       
 11969 </xsl:text>
       
 11970           <xsl:text>    if(new_val != undefined /*&amp;&amp; old_val != new_val*/)
       
 11971 </xsl:text>
       
 11972           <xsl:text>        send_hmi_value(index, new_val);
       
 11973 </xsl:text>
       
 11974           <xsl:text>    return new_val;
       
 11975 </xsl:text>
       
 11976           <xsl:text>}
       
 11977 </xsl:text>
       
 11978           <xsl:text>
       
 11979 </xsl:text>
       
 11980           <xsl:text>const quotes = {"'":null, '"':null};
       
 11981 </xsl:text>
       
 11982           <xsl:text>
       
 11983 </xsl:text>
       
 11984           <xsl:text>function eval_operation_string(old_val, opstr) {
       
 11985 </xsl:text>
       
 11986           <xsl:text>    let op = opstr[0];
       
 11987 </xsl:text>
       
 11988           <xsl:text>    let given_val;
       
 11989 </xsl:text>
       
 11990           <xsl:text>    if(opstr.length &lt; 2) 
       
 11991 </xsl:text>
       
 11992           <xsl:text>        return undefined;
       
 11993 </xsl:text>
       
 11994           <xsl:text>    if(opstr[1] in quotes){
       
 11995 </xsl:text>
       
 11996           <xsl:text>        if(opstr.length &lt; 3) 
       
 11997 </xsl:text>
       
 11998           <xsl:text>            return undefined;
       
 11999 </xsl:text>
       
 12000           <xsl:text>        if(opstr[opstr.length-1] == opstr[1]){
       
 12001 </xsl:text>
       
 12002           <xsl:text>            given_val = opstr.slice(2,opstr.length-1);
       
 12003 </xsl:text>
       
 12004           <xsl:text>        }
       
 12005 </xsl:text>
       
 12006           <xsl:text>    } else {
       
 12007 </xsl:text>
       
 12008           <xsl:text>        given_val = Number(opstr.slice(1));
       
 12009 </xsl:text>
       
 12010           <xsl:text>    }
       
 12011 </xsl:text>
       
 12012           <xsl:text>    let new_val;
       
 12013 </xsl:text>
       
 12014           <xsl:text>    switch(op){
       
 12015 </xsl:text>
       
 12016           <xsl:text>      case "=":
       
 12017 </xsl:text>
       
 12018           <xsl:text>        new_val = given_val;
       
 12019 </xsl:text>
       
 12020           <xsl:text>        break;
       
 12021 </xsl:text>
       
 12022           <xsl:text>      case "+":
       
 12023 </xsl:text>
       
 12024           <xsl:text>        new_val = old_val + given_val;
       
 12025 </xsl:text>
       
 12026           <xsl:text>        break;
       
 12027 </xsl:text>
       
 12028           <xsl:text>      case "-":
       
 12029 </xsl:text>
       
 12030           <xsl:text>        new_val = old_val - given_val;
       
 12031 </xsl:text>
       
 12032           <xsl:text>        break;
       
 12033 </xsl:text>
       
 12034           <xsl:text>      case "*":
       
 12035 </xsl:text>
       
 12036           <xsl:text>        new_val = old_val * given_val;
       
 12037 </xsl:text>
       
 12038           <xsl:text>        break;
       
 12039 </xsl:text>
       
 12040           <xsl:text>      case "/":
       
 12041 </xsl:text>
       
 12042           <xsl:text>        new_val = old_val / given_val;
       
 12043 </xsl:text>
       
 12044           <xsl:text>        break;
       
 12045 </xsl:text>
       
 12046           <xsl:text>    }
       
 12047 </xsl:text>
       
 12048           <xsl:text>    return new_val;
       
 12049 </xsl:text>
       
 12050           <xsl:text>}
       
 12051 </xsl:text>
       
 12052           <xsl:text>
       
 12053 </xsl:text>
       
 12054           <xsl:text>var current_visible_page;
       
 12055 </xsl:text>
       
 12056           <xsl:text>var current_subscribed_page;
       
 12057 </xsl:text>
       
 12058           <xsl:text>var current_page_index;
       
 12059 </xsl:text>
       
 12060           <xsl:text>var page_node_local_index = hmi_local_index("page_node");
       
 12061 </xsl:text>
       
 12062           <xsl:text>var page_switch_in_progress = false;
       
 12063 </xsl:text>
       
 12064           <xsl:text>
       
 12065 </xsl:text>
       
 12066           <xsl:text>function toggleFullscreen() {
       
 12067 </xsl:text>
       
 12068           <xsl:text>  let elem = document.documentElement;
       
 12069 </xsl:text>
       
 12070           <xsl:text>
       
 12071 </xsl:text>
       
 12072           <xsl:text>  if (!document.fullscreenElement) {
       
 12073 </xsl:text>
       
 12074           <xsl:text>    elem.requestFullscreen().catch(err =&gt; {
       
 12075 </xsl:text>
       
 12076           <xsl:text>      console.log("Error attempting to enable full-screen mode: "+err.message+" ("+err.name+")");
       
 12077 </xsl:text>
       
 12078           <xsl:text>    });
       
 12079 </xsl:text>
       
 12080           <xsl:text>  } else {
       
 12081 </xsl:text>
       
 12082           <xsl:text>    document.exitFullscreen();
       
 12083 </xsl:text>
       
 12084           <xsl:text>  }
       
 12085 </xsl:text>
       
 12086           <xsl:text>}
       
 12087 </xsl:text>
       
 12088           <xsl:text>
       
 12089 </xsl:text>
       
 12090           <xsl:text>// prevents context menu from appearing on right click and long touch
       
 12091 </xsl:text>
       
 12092           <xsl:text>document.body.addEventListener('contextmenu', e =&gt; {
       
 12093 </xsl:text>
       
 12094           <xsl:text>    toggleFullscreen();
       
 12095 </xsl:text>
       
 12096           <xsl:text>    e.preventDefault();
       
 12097 </xsl:text>
       
 12098           <xsl:text>});
       
 12099 </xsl:text>
       
 12100           <xsl:text>
       
 12101 </xsl:text>
       
 12102           <xsl:text>function detach_detachables() {
       
 12103 </xsl:text>
       
 12104           <xsl:text>
       
 12105 </xsl:text>
       
 12106           <xsl:text>    for(let eltid in detachable_elements){
       
 12107 </xsl:text>
       
 12108           <xsl:text>        let [element,parent] = detachable_elements[eltid];
       
 12109 </xsl:text>
       
 12110           <xsl:text>        parent.removeChild(element);
       
 12111 </xsl:text>
       
 12112           <xsl:text>    }
       
 12113 </xsl:text>
       
 12114           <xsl:text>};
       
 12115 </xsl:text>
       
 12116           <xsl:text>
       
 12117 </xsl:text>
       
 12118           <xsl:text>function switch_page(page_name, page_index) {
       
 12119 </xsl:text>
       
 12120           <xsl:text>    if(page_switch_in_progress){
       
 12121 </xsl:text>
       
 12122           <xsl:text>        /* page switch already going */
       
 12123 </xsl:text>
       
 12124           <xsl:text>        /* TODO LOG ERROR */
       
 12125 </xsl:text>
       
 12126           <xsl:text>        return false;
       
 12127 </xsl:text>
       
 12128           <xsl:text>    }
       
 12129 </xsl:text>
       
 12130           <xsl:text>    page_switch_in_progress = true;
       
 12131 </xsl:text>
       
 12132           <xsl:text>
       
 12133 </xsl:text>
       
 12134           <xsl:text>    if(page_name == undefined)
       
 12135 </xsl:text>
       
 12136           <xsl:text>        page_name = current_subscribed_page;
       
 12137 </xsl:text>
       
 12138           <xsl:text>    else if(page_index == undefined){
       
 12139 </xsl:text>
       
 12140           <xsl:text>        [page_name, page_index] = page_name.split('@')
       
 12141 </xsl:text>
       
 12142           <xsl:text>    }
       
 12143 </xsl:text>
       
 12144           <xsl:text>
       
 12145 </xsl:text>
       
 12146           <xsl:text>    let old_desc = page_desc[current_subscribed_page];
       
 12147 </xsl:text>
       
 12148           <xsl:text>    let new_desc = page_desc[page_name];
       
 12149 </xsl:text>
       
 12150           <xsl:text>
       
 12151 </xsl:text>
       
 12152           <xsl:text>    if(new_desc == undefined){
       
 12153 </xsl:text>
       
 12154           <xsl:text>        /* TODO LOG ERROR */
       
 12155 </xsl:text>
       
 12156           <xsl:text>        return false;
       
 12157 </xsl:text>
       
 12158           <xsl:text>    }
       
 12159 </xsl:text>
       
 12160           <xsl:text>
       
 12161 </xsl:text>
       
 12162           <xsl:text>    if(page_index == undefined)
       
 12163 </xsl:text>
       
 12164           <xsl:text>        page_index = new_desc.page_index;
       
 12165 </xsl:text>
       
 12166           <xsl:text>    else if(typeof(page_index) == "string") {
       
 12167 </xsl:text>
       
 12168           <xsl:text>        let hmitree_node = hmitree_nodes[page_index];
       
 12169 </xsl:text>
       
 12170           <xsl:text>        if(hmitree_node !== undefined){
       
 12171 </xsl:text>
       
 12172           <xsl:text>            let [int_index, hmiclass] = hmitree_node;
       
 12173 </xsl:text>
       
 12174           <xsl:text>            if(hmiclass == new_desc.page_class)
       
 12175 </xsl:text>
       
 12176           <xsl:text>                page_index = int_index;
       
 12177 </xsl:text>
       
 12178           <xsl:text>            else
       
 12179 </xsl:text>
       
 12180           <xsl:text>                page_index = new_desc.page_index;
       
 12181 </xsl:text>
       
 12182           <xsl:text>        } else {
       
 12183 </xsl:text>
       
 12184           <xsl:text>            page_index = new_desc.page_index;
       
 12185 </xsl:text>
       
 12186           <xsl:text>        }
       
 12187 </xsl:text>
       
 12188           <xsl:text>    }
       
 12189 </xsl:text>
       
 12190           <xsl:text>
       
 12191 </xsl:text>
       
 12192           <xsl:text>    if(old_desc){
       
 12193 </xsl:text>
       
 12194           <xsl:text>        old_desc.widgets.map(([widget,relativeness])=&gt;widget.unsub());
       
 12195 </xsl:text>
       
 12196           <xsl:text>    }
       
 12197 </xsl:text>
       
 12198           <xsl:text>    const new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
       
 12199 </xsl:text>
       
 12200           <xsl:text>
       
 12201 </xsl:text>
       
 12202           <xsl:text>    const container_id = page_name + (page_index != undefined ? page_index : "");
       
 12203 </xsl:text>
       
 12204           <xsl:text>
       
 12205 </xsl:text>
       
 12206           <xsl:text>    new_desc.widgets.map(([widget,relativeness])=&gt;widget.sub(new_offset,relativeness,container_id));
       
 12207 </xsl:text>
       
 12208           <xsl:text>
       
 12209 </xsl:text>
       
 12210           <xsl:text>    update_subscriptions();
       
 12211 </xsl:text>
       
 12212           <xsl:text>
       
 12213 </xsl:text>
       
 12214           <xsl:text>    current_subscribed_page = page_name;
       
 12215 </xsl:text>
       
 12216           <xsl:text>    current_page_index = page_index;
       
 12217 </xsl:text>
       
 12218           <xsl:text>    let page_node;
       
 12219 </xsl:text>
       
 12220           <xsl:text>    if(page_index != undefined){
       
 12221 </xsl:text>
       
 12222           <xsl:text>        page_node = hmitree_paths[page_index];
       
 12223 </xsl:text>
       
 12224           <xsl:text>    }else{
       
 12225 </xsl:text>
       
 12226           <xsl:text>        page_node = "";
       
 12227 </xsl:text>
       
 12228           <xsl:text>    }
       
 12229 </xsl:text>
       
 12230           <xsl:text>    apply_hmi_value(page_node_local_index, page_node);
       
 12231 </xsl:text>
       
 12232           <xsl:text>
       
 12233 </xsl:text>
       
 12234           <xsl:text>    jumps_need_update = true;
       
 12235 </xsl:text>
       
 12236           <xsl:text>
       
 12237 </xsl:text>
       
 12238           <xsl:text>    requestHMIAnimation();
       
 12239 </xsl:text>
       
 12240           <xsl:text>    jump_history.push([page_name, page_index]);
       
 12241 </xsl:text>
       
 12242           <xsl:text>    if(jump_history.length &gt; 42)
       
 12243 </xsl:text>
       
 12244           <xsl:text>        jump_history.shift();
       
 12245 </xsl:text>
       
 12246           <xsl:text>
       
 12247 </xsl:text>
       
 12248           <xsl:text>    apply_hmi_value(current_page_var_index, page_index == undefined
       
 12249 </xsl:text>
       
 12250           <xsl:text>        ? page_name
       
 12251 </xsl:text>
       
 12252           <xsl:text>        : page_name + "@" + hmitree_paths[page_index]);
       
 12253 </xsl:text>
       
 12254           <xsl:text>
       
 12255 </xsl:text>
       
 12256           <xsl:text>    return true;
       
 12257 </xsl:text>
       
 12258           <xsl:text>};
       
 12259 </xsl:text>
       
 12260           <xsl:text>
       
 12261 </xsl:text>
       
 12262           <xsl:text>function switch_visible_page(page_name) {
       
 12263 </xsl:text>
       
 12264           <xsl:text>
       
 12265 </xsl:text>
       
 12266           <xsl:text>    let old_desc = page_desc[current_visible_page];
       
 12267 </xsl:text>
       
 12268           <xsl:text>    let new_desc = page_desc[page_name];
       
 12269 </xsl:text>
       
 12270           <xsl:text>
       
 12271 </xsl:text>
       
 12272           <xsl:text>    if(old_desc){
       
 12273 </xsl:text>
       
 12274           <xsl:text>        for(let eltid in old_desc.required_detachables){
       
 12275 </xsl:text>
       
 12276           <xsl:text>            if(!(eltid in new_desc.required_detachables)){
       
 12277 </xsl:text>
       
 12278           <xsl:text>                let [element, parent] = old_desc.required_detachables[eltid];
       
 12279 </xsl:text>
       
 12280           <xsl:text>                parent.removeChild(element);
       
 12281 </xsl:text>
       
 12282           <xsl:text>            }
       
 12283 </xsl:text>
       
 12284           <xsl:text>        }
       
 12285 </xsl:text>
       
 12286           <xsl:text>        for(let eltid in new_desc.required_detachables){
       
 12287 </xsl:text>
       
 12288           <xsl:text>            if(!(eltid in old_desc.required_detachables)){
       
 12289 </xsl:text>
       
 12290           <xsl:text>                let [element, parent] = new_desc.required_detachables[eltid];
       
 12291 </xsl:text>
       
 12292           <xsl:text>                parent.appendChild(element);
       
 12293 </xsl:text>
       
 12294           <xsl:text>            }
       
 12295 </xsl:text>
       
 12296           <xsl:text>        }
       
 12297 </xsl:text>
       
 12298           <xsl:text>    }else{
       
 12299 </xsl:text>
       
 12300           <xsl:text>        for(let eltid in new_desc.required_detachables){
       
 12301 </xsl:text>
       
 12302           <xsl:text>            let [element, parent] = new_desc.required_detachables[eltid];
       
 12303 </xsl:text>
       
 12304           <xsl:text>            parent.appendChild(element);
       
 12305 </xsl:text>
       
 12306           <xsl:text>        }
       
 12307 </xsl:text>
       
 12308           <xsl:text>    }
       
 12309 </xsl:text>
       
 12310           <xsl:text>
       
 12311 </xsl:text>
       
 12312           <xsl:text>    svg_root.setAttribute('viewBox',new_desc.bbox.join(" "));
       
 12313 </xsl:text>
       
 12314           <xsl:text>    current_visible_page = page_name;
       
 12315 </xsl:text>
       
 12316           <xsl:text>};
       
 12317 </xsl:text>
       
 12318           <xsl:text>
       
 12319 </xsl:text>
       
 12320           <xsl:text>/* From https://jsfiddle.net/ibowankenobi/1mmh7rs6/6/ */
       
 12321 </xsl:text>
       
 12322           <xsl:text>function getAbsoluteCTM(element){
       
 12323 </xsl:text>
       
 12324           <xsl:text>	var height = svg_root.height.baseVal.value,
       
 12325 </xsl:text>
       
 12326           <xsl:text>		width = svg_root.width.baseVal.value,
       
 12327 </xsl:text>
       
 12328           <xsl:text>		viewBoxRect = svg_root.viewBox.baseVal,
       
 12329 </xsl:text>
       
 12330           <xsl:text>		vHeight = viewBoxRect.height,
       
 12331 </xsl:text>
       
 12332           <xsl:text>		vWidth = viewBoxRect.width;
       
 12333 </xsl:text>
       
 12334           <xsl:text>	if(!vWidth || !vHeight){
       
 12335 </xsl:text>
       
 12336           <xsl:text>		return element.getCTM();
       
 12337 </xsl:text>
       
 12338           <xsl:text>	}
       
 12339 </xsl:text>
       
 12340           <xsl:text>	var sH = height/vHeight,
       
 12341 </xsl:text>
       
 12342           <xsl:text>		sW = width/vWidth,
       
 12343 </xsl:text>
       
 12344           <xsl:text>		matrix = svg_root.createSVGMatrix();
       
 12345 </xsl:text>
       
 12346           <xsl:text>	matrix.a = sW;
       
 12347 </xsl:text>
       
 12348           <xsl:text>	matrix.d = sH
       
 12349 </xsl:text>
       
 12350           <xsl:text>	var realCTM = element.getCTM().multiply(matrix.inverse());
       
 12351 </xsl:text>
       
 12352           <xsl:text>	realCTM.e = realCTM.e/sW + viewBoxRect.x;
       
 12353 </xsl:text>
       
 12354           <xsl:text>	realCTM.f = realCTM.f/sH + viewBoxRect.y;
       
 12355 </xsl:text>
       
 12356           <xsl:text>	return realCTM;
       
 12357 </xsl:text>
       
 12358           <xsl:text>}
       
 12359 </xsl:text>
       
 12360           <xsl:text>
       
 12361 </xsl:text>
       
 12362           <xsl:text>function apply_reference_frames(){
       
 12363 </xsl:text>
       
 12364           <xsl:text>    const matches = svg_root.querySelectorAll("g[svghmi_x_offset]");
       
 12365 </xsl:text>
       
 12366           <xsl:text>    matches.forEach((group) =&gt; {
       
 12367 </xsl:text>
       
 12368           <xsl:text>        let [x,y] = ["x", "y"].map((axis) =&gt; Number(group.getAttribute("svghmi_"+axis+"_offset")));
       
 12369 </xsl:text>
       
 12370           <xsl:text>        let ctm = getAbsoluteCTM(group);
       
 12371 </xsl:text>
       
 12372           <xsl:text>        // zero translation part of CTM
       
 12373 </xsl:text>
       
 12374           <xsl:text>        // to only apply rotation/skewing to offset vector
       
 12375 </xsl:text>
       
 12376           <xsl:text>        ctm.e = 0;
       
 12377 </xsl:text>
       
 12378           <xsl:text>        ctm.f = 0;
       
 12379 </xsl:text>
       
 12380           <xsl:text>        let invctm = ctm.inverse();
       
 12381 </xsl:text>
       
 12382           <xsl:text>        let vect = new DOMPoint(x, y);
       
 12383 </xsl:text>
       
 12384           <xsl:text>        let newvect = vect.matrixTransform(invctm);
       
 12385 </xsl:text>
       
 12386           <xsl:text>        let transform = svg_root.createSVGTransform();
       
 12387 </xsl:text>
       
 12388           <xsl:text>        transform.setTranslate(newvect.x, newvect.y);
       
 12389 </xsl:text>
       
 12390           <xsl:text>        group.transform.baseVal.appendItem(transform);
       
 12391 </xsl:text>
       
 12392           <xsl:text>        ["x", "y"].forEach((axis) =&gt; group.removeAttribute("svghmi_"+axis+"_offset"));
       
 12393 </xsl:text>
       
 12394           <xsl:text>    });
       
 12395 </xsl:text>
       
 12396           <xsl:text>}
       
 12397 </xsl:text>
       
 12398           <xsl:text>
       
 12399 </xsl:text>
       
 12400           <xsl:text>// prepare SVG
       
 12401 </xsl:text>
       
 12402           <xsl:text>apply_reference_frames();
       
 12403 </xsl:text>
       
 12404           <xsl:text>init_widgets();
       
 12405 </xsl:text>
       
 12406           <xsl:text>detach_detachables();
       
 12407 </xsl:text>
       
 12408           <xsl:text>
       
 12409 </xsl:text>
       
 12410           <xsl:text>// show main page
       
 12411 </xsl:text>
       
 12412           <xsl:text>switch_page(default_page);
       
 12413 </xsl:text>
       
 12414           <xsl:text>
       
 12415 </xsl:text>
       
 12416           <xsl:text>var reconnect_delay = 0;
       
 12417 </xsl:text>
       
 12418           <xsl:text>var periodic_reconnect_timer;
       
 12419 </xsl:text>
       
 12420           <xsl:text>
       
 12421 </xsl:text>
       
 12422           <xsl:text>// Once connection established
       
 12423 </xsl:text>
       
 12424           <xsl:text>function ws_onopen(evt) {
       
 12425 </xsl:text>
       
 12426           <xsl:text>    // Work around memory leak with websocket on QtWebEngine
       
 12427 </xsl:text>
       
 12428           <xsl:text>    // reconnect every hour to force deallocate websocket garbage
       
 12429 </xsl:text>
       
 12430           <xsl:text>    if(window.navigator.userAgent.includes("QtWebEngine")){
       
 12431 </xsl:text>
       
 12432           <xsl:text>        if(periodic_reconnect_timer){
       
 12433 </xsl:text>
       
 12434           <xsl:text>            window.clearTimeout(periodic_reconnect_timer);
       
 12435 </xsl:text>
       
 12436           <xsl:text>        }
       
 12437 </xsl:text>
       
 12438           <xsl:text>        periodic_reconnect_timer = window.setTimeout(() =&gt; {
       
 12439 </xsl:text>
       
 12440           <xsl:text>            ws.close();
       
 12441 </xsl:text>
       
 12442           <xsl:text>            periodic_reconnect_timer = null;
       
 12443 </xsl:text>
       
 12444           <xsl:text>        }, 3600000);
       
 12445 </xsl:text>
       
 12446           <xsl:text>    }
       
 12447 </xsl:text>
       
 12448           <xsl:text>
       
 12449 </xsl:text>
       
 12450           <xsl:text>    // forget subscriptions remotely
       
 12451 </xsl:text>
       
 12452           <xsl:text>    send_reset();
       
 12453 </xsl:text>
       
 12454           <xsl:text>
       
 12455 </xsl:text>
       
 12456           <xsl:text>    // forget earlier subscriptions locally
       
 12457 </xsl:text>
       
 12458           <xsl:text>    reset_subscription_periods();
       
 12459 </xsl:text>
       
 12460           <xsl:text>
       
 12461 </xsl:text>
       
 12462           <xsl:text>    // update PLC about subscriptions and current page
       
 12463 </xsl:text>
       
 12464           <xsl:text>    switch_page();
       
 12465 </xsl:text>
       
 12466           <xsl:text>
       
 12467 </xsl:text>
       
 12468           <xsl:text>    // at first try reconnect immediately
       
 12469 </xsl:text>
       
 12470           <xsl:text>    reconnect_delay = 1;
       
 12471 </xsl:text>
       
 12472           <xsl:text>};
       
 12473 </xsl:text>
       
 12474           <xsl:text>
       
 12475 </xsl:text>
       
 12476           <xsl:text>function ws_onclose(evt) {
       
 12477 </xsl:text>
       
 12478           <xsl:text>    console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in "+reconnect_delay+"ms.");
       
 12479 </xsl:text>
       
 12480           <xsl:text>    ws = null;
       
 12481 </xsl:text>
       
 12482           <xsl:text>    // reconect
       
 12483 </xsl:text>
       
 12484           <xsl:text>    // TODO : add visible notification while waiting for reload
       
 12485 </xsl:text>
       
 12486           <xsl:text>    window.setTimeout(create_ws, reconnect_delay);
       
 12487 </xsl:text>
       
 12488           <xsl:text>    reconnect_delay += 500;
       
 12489 </xsl:text>
       
 12490           <xsl:text>};
       
 12491 </xsl:text>
       
 12492           <xsl:text>
       
 12493 </xsl:text>
       
 12494           <xsl:text>var ws_url =
 11293 </xsl:text>
 12495 </xsl:text>
 11294           <xsl:text>    window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')
 12496           <xsl:text>    window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws')
 11295 </xsl:text>
 12497 </xsl:text>
 11296           <xsl:text>    + '?mode=' + (has_watchdog ? "watchdog" : "multiclient");
 12498           <xsl:text>    + '?mode=' + (has_watchdog ? "watchdog" : "multiclient");
 11297 </xsl:text>
 12499 </xsl:text>
 11298           <xsl:text>
 12500           <xsl:text>
 11299 </xsl:text>
 12501 </xsl:text>
 11300           <xsl:text>var ws = new WebSocket(ws_url);
 12502           <xsl:text>function create_ws(){
 11301 </xsl:text>
 12503 </xsl:text>
 11302           <xsl:text>ws.binaryType = 'arraybuffer';
 12504           <xsl:text>    ws = new WebSocket(ws_url);
 11303 </xsl:text>
 12505 </xsl:text>
 11304           <xsl:text>
 12506           <xsl:text>    ws.binaryType = 'arraybuffer';
 11305 </xsl:text>
 12507 </xsl:text>
 11306           <xsl:text>const dvgetters = {
 12508           <xsl:text>    ws.onmessage = ws_onmessage;
 11307 </xsl:text>
 12509 </xsl:text>
 11308           <xsl:text>    INT: (dv,offset) =&gt; [dv.getInt16(offset, true), 2],
 12510           <xsl:text>    ws.onclose = ws_onclose;
 11309 </xsl:text>
 12511 </xsl:text>
 11310           <xsl:text>    BOOL: (dv,offset) =&gt; [dv.getInt8(offset, true), 1],
 12512           <xsl:text>    ws.onopen = ws_onopen;
 11311 </xsl:text>
       
 11312           <xsl:text>    NODE: (dv,offset) =&gt; [dv.getInt8(offset, true), 1],
       
 11313 </xsl:text>
       
 11314           <xsl:text>    REAL: (dv,offset) =&gt; [dv.getFloat32(offset, true), 4],
       
 11315 </xsl:text>
       
 11316           <xsl:text>    STRING: (dv, offset) =&gt; {
       
 11317 </xsl:text>
       
 11318           <xsl:text>        const size = dv.getInt8(offset);
       
 11319 </xsl:text>
       
 11320           <xsl:text>        return [
       
 11321 </xsl:text>
       
 11322           <xsl:text>            String.fromCharCode.apply(null, new Uint8Array(
       
 11323 </xsl:text>
       
 11324           <xsl:text>                dv.buffer, /* original buffer */
       
 11325 </xsl:text>
       
 11326           <xsl:text>                offset + 1, /* string starts after size*/
       
 11327 </xsl:text>
       
 11328           <xsl:text>                size /* size of string */
       
 11329 </xsl:text>
       
 11330           <xsl:text>            )), size + 1]; /* total increment */
       
 11331 </xsl:text>
       
 11332           <xsl:text>    }
       
 11333 </xsl:text>
       
 11334           <xsl:text>};
       
 11335 </xsl:text>
       
 11336           <xsl:text>
       
 11337 </xsl:text>
       
 11338           <xsl:text>// Called on requestAnimationFrame, modifies DOM
       
 11339 </xsl:text>
       
 11340           <xsl:text>var requestAnimationFrameID = null;
       
 11341 </xsl:text>
       
 11342           <xsl:text>function animate() {
       
 11343 </xsl:text>
       
 11344           <xsl:text>    let rearm = true;
       
 11345 </xsl:text>
       
 11346           <xsl:text>    do{
       
 11347 </xsl:text>
       
 11348           <xsl:text>        if(page_fading == "pending" || page_fading == "forced"){
       
 11349 </xsl:text>
       
 11350           <xsl:text>            if(page_fading == "pending")
       
 11351 </xsl:text>
       
 11352           <xsl:text>                svg_root.classList.add("fade-out-page");
       
 11353 </xsl:text>
       
 11354           <xsl:text>            page_fading = "in_progress";
       
 11355 </xsl:text>
       
 11356           <xsl:text>            if(page_fading_args.length)
       
 11357 </xsl:text>
       
 11358           <xsl:text>                setTimeout(function(){
       
 11359 </xsl:text>
       
 11360           <xsl:text>                    switch_page(...page_fading_args);
       
 11361 </xsl:text>
       
 11362           <xsl:text>                },1);
       
 11363 </xsl:text>
       
 11364           <xsl:text>            break;
       
 11365 </xsl:text>
       
 11366           <xsl:text>        }
       
 11367 </xsl:text>
       
 11368           <xsl:text>
       
 11369 </xsl:text>
       
 11370           <xsl:text>        // Do the page swith if pending
       
 11371 </xsl:text>
       
 11372           <xsl:text>        if(page_switch_in_progress){
       
 11373 </xsl:text>
       
 11374           <xsl:text>            if(current_subscribed_page != current_visible_page){
       
 11375 </xsl:text>
       
 11376           <xsl:text>                switch_visible_page(current_subscribed_page);
       
 11377 </xsl:text>
       
 11378           <xsl:text>            }
       
 11379 </xsl:text>
       
 11380           <xsl:text>
       
 11381 </xsl:text>
       
 11382           <xsl:text>            page_switch_in_progress = false;
       
 11383 </xsl:text>
       
 11384           <xsl:text>
       
 11385 </xsl:text>
       
 11386           <xsl:text>            if(page_fading == "in_progress"){
       
 11387 </xsl:text>
       
 11388           <xsl:text>                svg_root.classList.remove("fade-out-page");
       
 11389 </xsl:text>
       
 11390           <xsl:text>                page_fading = "off";
       
 11391 </xsl:text>
       
 11392           <xsl:text>            }
       
 11393 </xsl:text>
       
 11394           <xsl:text>        }
       
 11395 </xsl:text>
       
 11396           <xsl:text>
       
 11397 </xsl:text>
       
 11398           <xsl:text>        if(jumps_need_update) update_jumps();
       
 11399 </xsl:text>
       
 11400           <xsl:text>
       
 11401 </xsl:text>
       
 11402           <xsl:text>
       
 11403 </xsl:text>
       
 11404           <xsl:text>        pending_widget_animates.forEach(widget =&gt; widget._animate());
       
 11405 </xsl:text>
       
 11406           <xsl:text>        pending_widget_animates = [];
       
 11407 </xsl:text>
       
 11408           <xsl:text>        rearm = false;
       
 11409 </xsl:text>
       
 11410           <xsl:text>    } while(0);
       
 11411 </xsl:text>
       
 11412           <xsl:text>
       
 11413 </xsl:text>
       
 11414           <xsl:text>    requestAnimationFrameID = null;
       
 11415 </xsl:text>
       
 11416           <xsl:text>
       
 11417 </xsl:text>
       
 11418           <xsl:text>    if(rearm) requestHMIAnimation();
       
 11419 </xsl:text>
 12513 </xsl:text>
 11420           <xsl:text>}
 12514           <xsl:text>}
 11421 </xsl:text>
 12515 </xsl:text>
 11422           <xsl:text>
 12516           <xsl:text>
 11423 </xsl:text>
 12517 </xsl:text>
 11424           <xsl:text>function requestHMIAnimation() {
 12518           <xsl:text>create_ws()
 11425 </xsl:text>
       
 11426           <xsl:text>    if(requestAnimationFrameID == null){
       
 11427 </xsl:text>
       
 11428           <xsl:text>        requestAnimationFrameID = window.requestAnimationFrame(animate);
       
 11429 </xsl:text>
       
 11430           <xsl:text>    }
       
 11431 </xsl:text>
       
 11432           <xsl:text>}
       
 11433 </xsl:text>
       
 11434           <xsl:text>
       
 11435 </xsl:text>
       
 11436           <xsl:text>// Message reception handler
       
 11437 </xsl:text>
       
 11438           <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing
       
 11439 </xsl:text>
       
 11440           <xsl:text>// are stored until browser can compute next frame, DOM is left untouched
       
 11441 </xsl:text>
       
 11442           <xsl:text>ws.onmessage = function (evt) {
       
 11443 </xsl:text>
       
 11444           <xsl:text>
       
 11445 </xsl:text>
       
 11446           <xsl:text>    let data = evt.data;
       
 11447 </xsl:text>
       
 11448           <xsl:text>    let dv = new DataView(data);
       
 11449 </xsl:text>
       
 11450           <xsl:text>    let i = 0;
       
 11451 </xsl:text>
       
 11452           <xsl:text>    try {
       
 11453 </xsl:text>
       
 11454           <xsl:text>        for(let hash_int of hmi_hash) {
       
 11455 </xsl:text>
       
 11456           <xsl:text>            if(hash_int != dv.getUint8(i)){
       
 11457 </xsl:text>
       
 11458           <xsl:text>                throw new Error("Hash doesn't match");
       
 11459 </xsl:text>
       
 11460           <xsl:text>            };
       
 11461 </xsl:text>
       
 11462           <xsl:text>            i++;
       
 11463 </xsl:text>
       
 11464           <xsl:text>        };
       
 11465 </xsl:text>
       
 11466           <xsl:text>
       
 11467 </xsl:text>
       
 11468           <xsl:text>        while(i &lt; data.byteLength){
       
 11469 </xsl:text>
       
 11470           <xsl:text>            let index = dv.getUint32(i, true);
       
 11471 </xsl:text>
       
 11472           <xsl:text>            i += 4;
       
 11473 </xsl:text>
       
 11474           <xsl:text>            let iectype = hmitree_types[index];
       
 11475 </xsl:text>
       
 11476           <xsl:text>            if(iectype != undefined){
       
 11477 </xsl:text>
       
 11478           <xsl:text>                let dvgetter = dvgetters[iectype];
       
 11479 </xsl:text>
       
 11480           <xsl:text>                let [value, bytesize] = dvgetter(dv,i);
       
 11481 </xsl:text>
       
 11482           <xsl:text>                dispatch_value(index, value);
       
 11483 </xsl:text>
       
 11484           <xsl:text>                i += bytesize;
       
 11485 </xsl:text>
       
 11486           <xsl:text>            } else {
       
 11487 </xsl:text>
       
 11488           <xsl:text>                throw new Error("Unknown index "+index);
       
 11489 </xsl:text>
       
 11490           <xsl:text>            }
       
 11491 </xsl:text>
       
 11492           <xsl:text>        };
       
 11493 </xsl:text>
       
 11494           <xsl:text>
       
 11495 </xsl:text>
       
 11496           <xsl:text>        // register for rendering on next frame, since there are updates
       
 11497 </xsl:text>
       
 11498           <xsl:text>    } catch(err) {
       
 11499 </xsl:text>
       
 11500           <xsl:text>        // 1003 is for "Unsupported Data"
       
 11501 </xsl:text>
       
 11502           <xsl:text>        // ws.close(1003, err.message);
       
 11503 </xsl:text>
       
 11504           <xsl:text>
       
 11505 </xsl:text>
       
 11506           <xsl:text>        // TODO : remove debug alert ?
       
 11507 </xsl:text>
       
 11508           <xsl:text>        alert("Error : "+err.message+"\nHMI will be reloaded.");
       
 11509 </xsl:text>
       
 11510           <xsl:text>
       
 11511 </xsl:text>
       
 11512           <xsl:text>        // force reload ignoring cache
       
 11513 </xsl:text>
       
 11514           <xsl:text>        location.reload(true);
       
 11515 </xsl:text>
       
 11516           <xsl:text>    }
       
 11517 </xsl:text>
       
 11518           <xsl:text>};
       
 11519 </xsl:text>
       
 11520           <xsl:text>
       
 11521 </xsl:text>
       
 11522           <xsl:text>hmi_hash_u8 = new Uint8Array(hmi_hash);
       
 11523 </xsl:text>
       
 11524           <xsl:text>
       
 11525 </xsl:text>
       
 11526           <xsl:text>function send_blob(data) {
       
 11527 </xsl:text>
       
 11528           <xsl:text>    if(data.length &gt; 0) {
       
 11529 </xsl:text>
       
 11530           <xsl:text>        ws.send(new Blob([hmi_hash_u8].concat(data)));
       
 11531 </xsl:text>
       
 11532           <xsl:text>    };
       
 11533 </xsl:text>
       
 11534           <xsl:text>};
       
 11535 </xsl:text>
       
 11536           <xsl:text>
       
 11537 </xsl:text>
       
 11538           <xsl:text>const typedarray_types = {
       
 11539 </xsl:text>
       
 11540           <xsl:text>    INT: (number) =&gt; new Int16Array([number]),
       
 11541 </xsl:text>
       
 11542           <xsl:text>    BOOL: (truth) =&gt; new Int16Array([truth]),
       
 11543 </xsl:text>
       
 11544           <xsl:text>    NODE: (truth) =&gt; new Int16Array([truth]),
       
 11545 </xsl:text>
       
 11546           <xsl:text>    REAL: (number) =&gt; new Float32Array([number]),
       
 11547 </xsl:text>
       
 11548           <xsl:text>    STRING: (str) =&gt; {
       
 11549 </xsl:text>
       
 11550           <xsl:text>        // beremiz default string max size is 128
       
 11551 </xsl:text>
       
 11552           <xsl:text>        str = str.slice(0,128);
       
 11553 </xsl:text>
       
 11554           <xsl:text>        binary = new Uint8Array(str.length + 1);
       
 11555 </xsl:text>
       
 11556           <xsl:text>        binary[0] = str.length;
       
 11557 </xsl:text>
       
 11558           <xsl:text>        for(let i = 0; i &lt; str.length; i++){
       
 11559 </xsl:text>
       
 11560           <xsl:text>            binary[i+1] = str.charCodeAt(i);
       
 11561 </xsl:text>
       
 11562           <xsl:text>        }
       
 11563 </xsl:text>
       
 11564           <xsl:text>        return binary;
       
 11565 </xsl:text>
       
 11566           <xsl:text>    }
       
 11567 </xsl:text>
       
 11568           <xsl:text>    /* TODO */
       
 11569 </xsl:text>
       
 11570           <xsl:text>};
       
 11571 </xsl:text>
       
 11572           <xsl:text>
       
 11573 </xsl:text>
       
 11574           <xsl:text>function send_reset() {
       
 11575 </xsl:text>
       
 11576           <xsl:text>    send_blob(new Uint8Array([1])); /* reset = 1 */
       
 11577 </xsl:text>
       
 11578           <xsl:text>};
       
 11579 </xsl:text>
       
 11580           <xsl:text>
       
 11581 </xsl:text>
       
 11582           <xsl:text>var subscriptions = [];
       
 11583 </xsl:text>
       
 11584           <xsl:text>
       
 11585 </xsl:text>
       
 11586           <xsl:text>function subscribers(index) {
       
 11587 </xsl:text>
       
 11588           <xsl:text>    let entry = subscriptions[index];
       
 11589 </xsl:text>
       
 11590           <xsl:text>    let res;
       
 11591 </xsl:text>
       
 11592           <xsl:text>    if(entry == undefined){
       
 11593 </xsl:text>
       
 11594           <xsl:text>        res = new Set();
       
 11595 </xsl:text>
       
 11596           <xsl:text>        subscriptions[index] = [res,0];
       
 11597 </xsl:text>
       
 11598           <xsl:text>    }else{
       
 11599 </xsl:text>
       
 11600           <xsl:text>        [res, _ign] = entry;
       
 11601 </xsl:text>
       
 11602           <xsl:text>    }
       
 11603 </xsl:text>
       
 11604           <xsl:text>    return res
       
 11605 </xsl:text>
       
 11606           <xsl:text>}
       
 11607 </xsl:text>
       
 11608           <xsl:text>
       
 11609 </xsl:text>
       
 11610           <xsl:text>function get_subscription_period(index) {
       
 11611 </xsl:text>
       
 11612           <xsl:text>    let entry = subscriptions[index];
       
 11613 </xsl:text>
       
 11614           <xsl:text>    if(entry == undefined)
       
 11615 </xsl:text>
       
 11616           <xsl:text>        return 0;
       
 11617 </xsl:text>
       
 11618           <xsl:text>    let [_ign, period] = entry;
       
 11619 </xsl:text>
       
 11620           <xsl:text>    return period;
       
 11621 </xsl:text>
       
 11622           <xsl:text>}
       
 11623 </xsl:text>
       
 11624           <xsl:text>
       
 11625 </xsl:text>
       
 11626           <xsl:text>function set_subscription_period(index, period) {
       
 11627 </xsl:text>
       
 11628           <xsl:text>    let entry = subscriptions[index];
       
 11629 </xsl:text>
       
 11630           <xsl:text>    if(entry == undefined){
       
 11631 </xsl:text>
       
 11632           <xsl:text>        subscriptions[index] = [new Set(), period];
       
 11633 </xsl:text>
       
 11634           <xsl:text>    } else {
       
 11635 </xsl:text>
       
 11636           <xsl:text>        entry[1] = period;
       
 11637 </xsl:text>
       
 11638           <xsl:text>    }
       
 11639 </xsl:text>
       
 11640           <xsl:text>}
       
 11641 </xsl:text>
       
 11642           <xsl:text>
       
 11643 </xsl:text>
       
 11644           <xsl:text>if(has_watchdog){
       
 11645 </xsl:text>
       
 11646           <xsl:text>    // artificially subscribe the watchdog widget to "/heartbeat" hmi variable
       
 11647 </xsl:text>
       
 11648           <xsl:text>    // Since dispatch directly calls change_hmi_value,
       
 11649 </xsl:text>
       
 11650           <xsl:text>    // PLC will periodically send variable at given frequency
       
 11651 </xsl:text>
       
 11652           <xsl:text>    subscribers(heartbeat_index).add({
       
 11653 </xsl:text>
       
 11654           <xsl:text>        /* type: "Watchdog", */
       
 11655 </xsl:text>
       
 11656           <xsl:text>        frequency: 1,
       
 11657 </xsl:text>
       
 11658           <xsl:text>        indexes: [heartbeat_index],
       
 11659 </xsl:text>
       
 11660           <xsl:text>        new_hmi_value: function(index, value, oldval) {
       
 11661 </xsl:text>
       
 11662           <xsl:text>            apply_hmi_value(heartbeat_index, value+1);
       
 11663 </xsl:text>
       
 11664           <xsl:text>        }
       
 11665 </xsl:text>
       
 11666           <xsl:text>    });
       
 11667 </xsl:text>
       
 11668           <xsl:text>}
       
 11669 </xsl:text>
       
 11670           <xsl:text>
       
 11671 </xsl:text>
       
 11672           <xsl:text>
       
 11673 </xsl:text>
       
 11674           <xsl:text>var page_fading = "off";
       
 11675 </xsl:text>
       
 11676           <xsl:text>var page_fading_args = "off";
       
 11677 </xsl:text>
       
 11678           <xsl:text>function fading_page_switch(...args){
       
 11679 </xsl:text>
       
 11680           <xsl:text>    if(page_fading == "in_progress")
       
 11681 </xsl:text>
       
 11682           <xsl:text>        page_fading = "forced";
       
 11683 </xsl:text>
       
 11684           <xsl:text>    else
       
 11685 </xsl:text>
       
 11686           <xsl:text>        page_fading = "pending";
       
 11687 </xsl:text>
       
 11688           <xsl:text>    page_fading_args = args;
       
 11689 </xsl:text>
       
 11690           <xsl:text>
       
 11691 </xsl:text>
       
 11692           <xsl:text>    requestHMIAnimation();
       
 11693 </xsl:text>
       
 11694           <xsl:text>
       
 11695 </xsl:text>
       
 11696           <xsl:text>}
       
 11697 </xsl:text>
       
 11698           <xsl:text>document.body.style.backgroundColor = "black";
       
 11699 </xsl:text>
       
 11700           <xsl:text>
       
 11701 </xsl:text>
       
 11702           <xsl:text>// subscribe to per instance current page hmi variable
       
 11703 </xsl:text>
       
 11704           <xsl:text>// PLC must prefix page name with "!" for page switch to happen
       
 11705 </xsl:text>
       
 11706           <xsl:text>subscribers(current_page_var_index).add({
       
 11707 </xsl:text>
       
 11708           <xsl:text>    frequency: 1,
       
 11709 </xsl:text>
       
 11710           <xsl:text>    indexes: [current_page_var_index],
       
 11711 </xsl:text>
       
 11712           <xsl:text>    new_hmi_value: function(index, value, oldval) {
       
 11713 </xsl:text>
       
 11714           <xsl:text>        if(value.startsWith("!"))
       
 11715 </xsl:text>
       
 11716           <xsl:text>            fading_page_switch(value.slice(1));
       
 11717 </xsl:text>
       
 11718           <xsl:text>    }
       
 11719 </xsl:text>
       
 11720           <xsl:text>});
       
 11721 </xsl:text>
       
 11722           <xsl:text>
       
 11723 </xsl:text>
       
 11724           <xsl:text>function svg_text_to_multiline(elt) {
       
 11725 </xsl:text>
       
 11726           <xsl:text>    return(Array.prototype.map.call(elt.children, x=&gt;x.textContent).join("\n")); 
       
 11727 </xsl:text>
       
 11728           <xsl:text>}
       
 11729 </xsl:text>
       
 11730           <xsl:text>
       
 11731 </xsl:text>
       
 11732           <xsl:text>function multiline_to_svg_text(elt, str, blank) {
       
 11733 </xsl:text>
       
 11734           <xsl:text>    str.split('\n').map((line,i) =&gt; {elt.children[i].textContent = blank?"":line;});
       
 11735 </xsl:text>
       
 11736           <xsl:text>}
       
 11737 </xsl:text>
       
 11738           <xsl:text>
       
 11739 </xsl:text>
       
 11740           <xsl:text>function switch_langnum(langnum) {
       
 11741 </xsl:text>
       
 11742           <xsl:text>    langnum = Math.max(0, Math.min(langs.length - 1, langnum));
       
 11743 </xsl:text>
       
 11744           <xsl:text>
       
 11745 </xsl:text>
       
 11746           <xsl:text>    for (let translation of translations) {
       
 11747 </xsl:text>
       
 11748           <xsl:text>        let [objs, msgs] = translation;
       
 11749 </xsl:text>
       
 11750           <xsl:text>        let msg = msgs[langnum];
       
 11751 </xsl:text>
       
 11752           <xsl:text>        for (let obj of objs) {
       
 11753 </xsl:text>
       
 11754           <xsl:text>            multiline_to_svg_text(obj, msg);
       
 11755 </xsl:text>
       
 11756           <xsl:text>            obj.setAttribute("lang",langnum);
       
 11757 </xsl:text>
       
 11758           <xsl:text>        }
       
 11759 </xsl:text>
       
 11760           <xsl:text>    }
       
 11761 </xsl:text>
       
 11762           <xsl:text>    return langnum;
       
 11763 </xsl:text>
       
 11764           <xsl:text>}
       
 11765 </xsl:text>
       
 11766           <xsl:text>
       
 11767 </xsl:text>
       
 11768           <xsl:text>// backup original texts
       
 11769 </xsl:text>
       
 11770           <xsl:text>for (let translation of translations) {
       
 11771 </xsl:text>
       
 11772           <xsl:text>    let [objs, msgs] = translation;
       
 11773 </xsl:text>
       
 11774           <xsl:text>    msgs.unshift(svg_text_to_multiline(objs[0])); 
       
 11775 </xsl:text>
       
 11776           <xsl:text>}
       
 11777 </xsl:text>
       
 11778           <xsl:text>
       
 11779 </xsl:text>
       
 11780           <xsl:text>var lang_local_index = hmi_local_index("lang");
       
 11781 </xsl:text>
       
 11782           <xsl:text>var langcode_local_index = hmi_local_index("lang_code");
       
 11783 </xsl:text>
       
 11784           <xsl:text>var langname_local_index = hmi_local_index("lang_name");
       
 11785 </xsl:text>
       
 11786           <xsl:text>subscribers(lang_local_index).add({
       
 11787 </xsl:text>
       
 11788           <xsl:text>    indexes: [lang_local_index],
       
 11789 </xsl:text>
       
 11790           <xsl:text>    new_hmi_value: function(index, value, oldval) {
       
 11791 </xsl:text>
       
 11792           <xsl:text>        let current_lang =  switch_langnum(value);
       
 11793 </xsl:text>
       
 11794           <xsl:text>        let [langname,langcode] = langs[current_lang];
       
 11795 </xsl:text>
       
 11796           <xsl:text>        apply_hmi_value(langcode_local_index, langcode);
       
 11797 </xsl:text>
       
 11798           <xsl:text>        apply_hmi_value(langname_local_index, langname);
       
 11799 </xsl:text>
       
 11800           <xsl:text>        switch_page();
       
 11801 </xsl:text>
       
 11802           <xsl:text>    }
       
 11803 </xsl:text>
       
 11804           <xsl:text>});
       
 11805 </xsl:text>
       
 11806           <xsl:text>
       
 11807 </xsl:text>
       
 11808           <xsl:text>// returns en_US, fr_FR or en_UK depending on selected language
       
 11809 </xsl:text>
       
 11810           <xsl:text>function get_current_lang_code(){
       
 11811 </xsl:text>
       
 11812           <xsl:text>    return cache[langcode_local_index];
       
 11813 </xsl:text>
       
 11814           <xsl:text>}
       
 11815 </xsl:text>
       
 11816           <xsl:text>
       
 11817 </xsl:text>
       
 11818           <xsl:text>function setup_lang(){
       
 11819 </xsl:text>
       
 11820           <xsl:text>    let current_lang = cache[lang_local_index];
       
 11821 </xsl:text>
       
 11822           <xsl:text>    let new_lang = switch_langnum(current_lang);
       
 11823 </xsl:text>
       
 11824           <xsl:text>    if(current_lang != new_lang){
       
 11825 </xsl:text>
       
 11826           <xsl:text>        apply_hmi_value(lang_local_index, new_lang);
       
 11827 </xsl:text>
       
 11828           <xsl:text>    }
       
 11829 </xsl:text>
       
 11830           <xsl:text>}
       
 11831 </xsl:text>
       
 11832           <xsl:text>
       
 11833 </xsl:text>
       
 11834           <xsl:text>setup_lang();
       
 11835 </xsl:text>
       
 11836           <xsl:text>
       
 11837 </xsl:text>
       
 11838           <xsl:text>function update_subscriptions() {
       
 11839 </xsl:text>
       
 11840           <xsl:text>    let delta = [];
       
 11841 </xsl:text>
       
 11842           <xsl:text>    for(let index in subscriptions){
       
 11843 </xsl:text>
       
 11844           <xsl:text>        let widgets = subscribers(index);
       
 11845 </xsl:text>
       
 11846           <xsl:text>
       
 11847 </xsl:text>
       
 11848           <xsl:text>        // periods are in ms
       
 11849 </xsl:text>
       
 11850           <xsl:text>        let previous_period = get_subscription_period(index);
       
 11851 </xsl:text>
       
 11852           <xsl:text>
       
 11853 </xsl:text>
       
 11854           <xsl:text>        // subscribing with a zero period is unsubscribing
       
 11855 </xsl:text>
       
 11856           <xsl:text>        let new_period = 0;
       
 11857 </xsl:text>
       
 11858           <xsl:text>        if(widgets.size &gt; 0) {
       
 11859 </xsl:text>
       
 11860           <xsl:text>            let maxfreq = 0;
       
 11861 </xsl:text>
       
 11862           <xsl:text>            for(let widget of widgets){
       
 11863 </xsl:text>
       
 11864           <xsl:text>                let wf = widget.frequency;
       
 11865 </xsl:text>
       
 11866           <xsl:text>                if(wf != undefined &amp;&amp; maxfreq &lt; wf)
       
 11867 </xsl:text>
       
 11868           <xsl:text>                    maxfreq = wf;
       
 11869 </xsl:text>
       
 11870           <xsl:text>            }
       
 11871 </xsl:text>
       
 11872           <xsl:text>
       
 11873 </xsl:text>
       
 11874           <xsl:text>            if(maxfreq != 0)
       
 11875 </xsl:text>
       
 11876           <xsl:text>                new_period = 1000/maxfreq;
       
 11877 </xsl:text>
       
 11878           <xsl:text>        }
       
 11879 </xsl:text>
       
 11880           <xsl:text>
       
 11881 </xsl:text>
       
 11882           <xsl:text>        if(previous_period != new_period) {
       
 11883 </xsl:text>
       
 11884           <xsl:text>            set_subscription_period(index, new_period);
       
 11885 </xsl:text>
       
 11886           <xsl:text>            if(index &lt;= last_remote_index){
       
 11887 </xsl:text>
       
 11888           <xsl:text>                delta.push(
       
 11889 </xsl:text>
       
 11890           <xsl:text>                    new Uint8Array([2]), /* subscribe = 2 */
       
 11891 </xsl:text>
       
 11892           <xsl:text>                    new Uint32Array([index]),
       
 11893 </xsl:text>
       
 11894           <xsl:text>                    new Uint16Array([new_period]));
       
 11895 </xsl:text>
       
 11896           <xsl:text>            }
       
 11897 </xsl:text>
       
 11898           <xsl:text>        }
       
 11899 </xsl:text>
       
 11900           <xsl:text>    }
       
 11901 </xsl:text>
       
 11902           <xsl:text>    send_blob(delta);
       
 11903 </xsl:text>
       
 11904           <xsl:text>};
       
 11905 </xsl:text>
       
 11906           <xsl:text>
       
 11907 </xsl:text>
       
 11908           <xsl:text>function send_hmi_value(index, value) {
       
 11909 </xsl:text>
       
 11910           <xsl:text>    if(index &gt; last_remote_index){
       
 11911 </xsl:text>
       
 11912           <xsl:text>        dispatch_value(index, value);
       
 11913 </xsl:text>
       
 11914           <xsl:text>
       
 11915 </xsl:text>
       
 11916           <xsl:text>        if(persistent_indexes.has(index)){
       
 11917 </xsl:text>
       
 11918           <xsl:text>            let varname = persistent_indexes.get(index);
       
 11919 </xsl:text>
       
 11920           <xsl:text>            document.cookie = varname+"="+value+"; max-age=3153600000";
       
 11921 </xsl:text>
       
 11922           <xsl:text>        }
       
 11923 </xsl:text>
       
 11924           <xsl:text>
       
 11925 </xsl:text>
       
 11926           <xsl:text>        return;
       
 11927 </xsl:text>
       
 11928           <xsl:text>    }
       
 11929 </xsl:text>
       
 11930           <xsl:text>
       
 11931 </xsl:text>
       
 11932           <xsl:text>    let iectype = hmitree_types[index];
       
 11933 </xsl:text>
       
 11934           <xsl:text>    let tobinary = typedarray_types[iectype];
       
 11935 </xsl:text>
       
 11936           <xsl:text>    send_blob([
       
 11937 </xsl:text>
       
 11938           <xsl:text>        new Uint8Array([0]),  /* setval = 0 */
       
 11939 </xsl:text>
       
 11940           <xsl:text>        new Uint32Array([index]),
       
 11941 </xsl:text>
       
 11942           <xsl:text>        tobinary(value)]);
       
 11943 </xsl:text>
       
 11944           <xsl:text>
       
 11945 </xsl:text>
       
 11946           <xsl:text>    // DON'T DO THAT unless read_iterator in svghmi.c modifies wbuf as well, not only rbuf
       
 11947 </xsl:text>
       
 11948           <xsl:text>    // cache[index] = value;
       
 11949 </xsl:text>
       
 11950           <xsl:text>};
       
 11951 </xsl:text>
       
 11952           <xsl:text>
       
 11953 </xsl:text>
       
 11954           <xsl:text>function apply_hmi_value(index, new_val) {
       
 11955 </xsl:text>
       
 11956           <xsl:text>    // Similarly to previous comment, taking decision to update based 
       
 11957 </xsl:text>
       
 11958           <xsl:text>    // on cache content is bad and can lead to inconsistency
       
 11959 </xsl:text>
       
 11960           <xsl:text>    /*let old_val = cache[index];*/
       
 11961 </xsl:text>
       
 11962           <xsl:text>    if(new_val != undefined /*&amp;&amp; old_val != new_val*/)
       
 11963 </xsl:text>
       
 11964           <xsl:text>        send_hmi_value(index, new_val);
       
 11965 </xsl:text>
       
 11966           <xsl:text>    return new_val;
       
 11967 </xsl:text>
       
 11968           <xsl:text>}
       
 11969 </xsl:text>
       
 11970           <xsl:text>
       
 11971 </xsl:text>
       
 11972           <xsl:text>const quotes = {"'":null, '"':null};
       
 11973 </xsl:text>
       
 11974           <xsl:text>
       
 11975 </xsl:text>
       
 11976           <xsl:text>function eval_operation_string(old_val, opstr) {
       
 11977 </xsl:text>
       
 11978           <xsl:text>    let op = opstr[0];
       
 11979 </xsl:text>
       
 11980           <xsl:text>    let given_val;
       
 11981 </xsl:text>
       
 11982           <xsl:text>    if(opstr.length &lt; 2) 
       
 11983 </xsl:text>
       
 11984           <xsl:text>        return undefined;
       
 11985 </xsl:text>
       
 11986           <xsl:text>    if(opstr[1] in quotes){
       
 11987 </xsl:text>
       
 11988           <xsl:text>        if(opstr.length &lt; 3) 
       
 11989 </xsl:text>
       
 11990           <xsl:text>            return undefined;
       
 11991 </xsl:text>
       
 11992           <xsl:text>        if(opstr[opstr.length-1] == opstr[1]){
       
 11993 </xsl:text>
       
 11994           <xsl:text>            given_val = opstr.slice(2,opstr.length-1);
       
 11995 </xsl:text>
       
 11996           <xsl:text>        }
       
 11997 </xsl:text>
       
 11998           <xsl:text>    } else {
       
 11999 </xsl:text>
       
 12000           <xsl:text>        given_val = Number(opstr.slice(1));
       
 12001 </xsl:text>
       
 12002           <xsl:text>    }
       
 12003 </xsl:text>
       
 12004           <xsl:text>    let new_val;
       
 12005 </xsl:text>
       
 12006           <xsl:text>    switch(op){
       
 12007 </xsl:text>
       
 12008           <xsl:text>      case "=":
       
 12009 </xsl:text>
       
 12010           <xsl:text>        new_val = given_val;
       
 12011 </xsl:text>
       
 12012           <xsl:text>        break;
       
 12013 </xsl:text>
       
 12014           <xsl:text>      case "+":
       
 12015 </xsl:text>
       
 12016           <xsl:text>        new_val = old_val + given_val;
       
 12017 </xsl:text>
       
 12018           <xsl:text>        break;
       
 12019 </xsl:text>
       
 12020           <xsl:text>      case "-":
       
 12021 </xsl:text>
       
 12022           <xsl:text>        new_val = old_val - given_val;
       
 12023 </xsl:text>
       
 12024           <xsl:text>        break;
       
 12025 </xsl:text>
       
 12026           <xsl:text>      case "*":
       
 12027 </xsl:text>
       
 12028           <xsl:text>        new_val = old_val * given_val;
       
 12029 </xsl:text>
       
 12030           <xsl:text>        break;
       
 12031 </xsl:text>
       
 12032           <xsl:text>      case "/":
       
 12033 </xsl:text>
       
 12034           <xsl:text>        new_val = old_val / given_val;
       
 12035 </xsl:text>
       
 12036           <xsl:text>        break;
       
 12037 </xsl:text>
       
 12038           <xsl:text>    }
       
 12039 </xsl:text>
       
 12040           <xsl:text>    return new_val;
       
 12041 </xsl:text>
       
 12042           <xsl:text>}
       
 12043 </xsl:text>
       
 12044           <xsl:text>
       
 12045 </xsl:text>
       
 12046           <xsl:text>var current_visible_page;
       
 12047 </xsl:text>
       
 12048           <xsl:text>var current_subscribed_page;
       
 12049 </xsl:text>
       
 12050           <xsl:text>var current_page_index;
       
 12051 </xsl:text>
       
 12052           <xsl:text>var page_node_local_index = hmi_local_index("page_node");
       
 12053 </xsl:text>
       
 12054           <xsl:text>var page_switch_in_progress = false;
       
 12055 </xsl:text>
       
 12056           <xsl:text>
       
 12057 </xsl:text>
       
 12058           <xsl:text>function toggleFullscreen() {
       
 12059 </xsl:text>
       
 12060           <xsl:text>  let elem = document.documentElement;
       
 12061 </xsl:text>
       
 12062           <xsl:text>
       
 12063 </xsl:text>
       
 12064           <xsl:text>  if (!document.fullscreenElement) {
       
 12065 </xsl:text>
       
 12066           <xsl:text>    elem.requestFullscreen().catch(err =&gt; {
       
 12067 </xsl:text>
       
 12068           <xsl:text>      console.log("Error attempting to enable full-screen mode: "+err.message+" ("+err.name+")");
       
 12069 </xsl:text>
       
 12070           <xsl:text>    });
       
 12071 </xsl:text>
       
 12072           <xsl:text>  } else {
       
 12073 </xsl:text>
       
 12074           <xsl:text>    document.exitFullscreen();
       
 12075 </xsl:text>
       
 12076           <xsl:text>  }
       
 12077 </xsl:text>
       
 12078           <xsl:text>}
       
 12079 </xsl:text>
       
 12080           <xsl:text>
       
 12081 </xsl:text>
       
 12082           <xsl:text>function prepare_svg() {
       
 12083 </xsl:text>
       
 12084           <xsl:text>    // prevents context menu from appearing on right click and long touch
       
 12085 </xsl:text>
       
 12086           <xsl:text>    document.body.addEventListener('contextmenu', e =&gt; {
       
 12087 </xsl:text>
       
 12088           <xsl:text>        toggleFullscreen();
       
 12089 </xsl:text>
       
 12090           <xsl:text>        e.preventDefault();
       
 12091 </xsl:text>
       
 12092           <xsl:text>    });
       
 12093 </xsl:text>
       
 12094           <xsl:text>
       
 12095 </xsl:text>
       
 12096           <xsl:text>    for(let eltid in detachable_elements){
       
 12097 </xsl:text>
       
 12098           <xsl:text>        let [element,parent] = detachable_elements[eltid];
       
 12099 </xsl:text>
       
 12100           <xsl:text>        parent.removeChild(element);
       
 12101 </xsl:text>
       
 12102           <xsl:text>    }
       
 12103 </xsl:text>
       
 12104           <xsl:text>};
       
 12105 </xsl:text>
       
 12106           <xsl:text>
       
 12107 </xsl:text>
       
 12108           <xsl:text>function switch_page(page_name, page_index) {
       
 12109 </xsl:text>
       
 12110           <xsl:text>    if(page_switch_in_progress){
       
 12111 </xsl:text>
       
 12112           <xsl:text>        /* page switch already going */
       
 12113 </xsl:text>
       
 12114           <xsl:text>        /* TODO LOG ERROR */
       
 12115 </xsl:text>
       
 12116           <xsl:text>        return false;
       
 12117 </xsl:text>
       
 12118           <xsl:text>    }
       
 12119 </xsl:text>
       
 12120           <xsl:text>    page_switch_in_progress = true;
       
 12121 </xsl:text>
       
 12122           <xsl:text>
       
 12123 </xsl:text>
       
 12124           <xsl:text>    if(page_name == undefined)
       
 12125 </xsl:text>
       
 12126           <xsl:text>        page_name = current_subscribed_page;
       
 12127 </xsl:text>
       
 12128           <xsl:text>    else if(page_index == undefined){
       
 12129 </xsl:text>
       
 12130           <xsl:text>        [page_name, page_index] = page_name.split('@')
       
 12131 </xsl:text>
       
 12132           <xsl:text>    }
       
 12133 </xsl:text>
       
 12134           <xsl:text>
       
 12135 </xsl:text>
       
 12136           <xsl:text>    let old_desc = page_desc[current_subscribed_page];
       
 12137 </xsl:text>
       
 12138           <xsl:text>    let new_desc = page_desc[page_name];
       
 12139 </xsl:text>
       
 12140           <xsl:text>
       
 12141 </xsl:text>
       
 12142           <xsl:text>    if(new_desc == undefined){
       
 12143 </xsl:text>
       
 12144           <xsl:text>        /* TODO LOG ERROR */
       
 12145 </xsl:text>
       
 12146           <xsl:text>        return false;
       
 12147 </xsl:text>
       
 12148           <xsl:text>    }
       
 12149 </xsl:text>
       
 12150           <xsl:text>
       
 12151 </xsl:text>
       
 12152           <xsl:text>    if(page_index == undefined)
       
 12153 </xsl:text>
       
 12154           <xsl:text>        page_index = new_desc.page_index;
       
 12155 </xsl:text>
       
 12156           <xsl:text>    else if(typeof(page_index) == "string") {
       
 12157 </xsl:text>
       
 12158           <xsl:text>        let hmitree_node = hmitree_nodes[page_index];
       
 12159 </xsl:text>
       
 12160           <xsl:text>        if(hmitree_node !== undefined){
       
 12161 </xsl:text>
       
 12162           <xsl:text>            let [int_index, hmiclass] = hmitree_node;
       
 12163 </xsl:text>
       
 12164           <xsl:text>            if(hmiclass == new_desc.page_class)
       
 12165 </xsl:text>
       
 12166           <xsl:text>                page_index = int_index;
       
 12167 </xsl:text>
       
 12168           <xsl:text>            else
       
 12169 </xsl:text>
       
 12170           <xsl:text>                page_index = new_desc.page_index;
       
 12171 </xsl:text>
       
 12172           <xsl:text>        } else {
       
 12173 </xsl:text>
       
 12174           <xsl:text>            page_index = new_desc.page_index;
       
 12175 </xsl:text>
       
 12176           <xsl:text>        }
       
 12177 </xsl:text>
       
 12178           <xsl:text>    }
       
 12179 </xsl:text>
       
 12180           <xsl:text>
       
 12181 </xsl:text>
       
 12182           <xsl:text>    if(old_desc){
       
 12183 </xsl:text>
       
 12184           <xsl:text>        old_desc.widgets.map(([widget,relativeness])=&gt;widget.unsub());
       
 12185 </xsl:text>
       
 12186           <xsl:text>    }
       
 12187 </xsl:text>
       
 12188           <xsl:text>    const new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
       
 12189 </xsl:text>
       
 12190           <xsl:text>
       
 12191 </xsl:text>
       
 12192           <xsl:text>    const container_id = page_name + (page_index != undefined ? page_index : "");
       
 12193 </xsl:text>
       
 12194           <xsl:text>
       
 12195 </xsl:text>
       
 12196           <xsl:text>    new_desc.widgets.map(([widget,relativeness])=&gt;widget.sub(new_offset,relativeness,container_id));
       
 12197 </xsl:text>
       
 12198           <xsl:text>
       
 12199 </xsl:text>
       
 12200           <xsl:text>    update_subscriptions();
       
 12201 </xsl:text>
       
 12202           <xsl:text>
       
 12203 </xsl:text>
       
 12204           <xsl:text>    current_subscribed_page = page_name;
       
 12205 </xsl:text>
       
 12206           <xsl:text>    current_page_index = page_index;
       
 12207 </xsl:text>
       
 12208           <xsl:text>    let page_node;
       
 12209 </xsl:text>
       
 12210           <xsl:text>    if(page_index != undefined){
       
 12211 </xsl:text>
       
 12212           <xsl:text>        page_node = hmitree_paths[page_index];
       
 12213 </xsl:text>
       
 12214           <xsl:text>    }else{
       
 12215 </xsl:text>
       
 12216           <xsl:text>        page_node = "";
       
 12217 </xsl:text>
       
 12218           <xsl:text>    }
       
 12219 </xsl:text>
       
 12220           <xsl:text>    apply_hmi_value(page_node_local_index, page_node);
       
 12221 </xsl:text>
       
 12222           <xsl:text>
       
 12223 </xsl:text>
       
 12224           <xsl:text>    jumps_need_update = true;
       
 12225 </xsl:text>
       
 12226           <xsl:text>
       
 12227 </xsl:text>
       
 12228           <xsl:text>    requestHMIAnimation();
       
 12229 </xsl:text>
       
 12230           <xsl:text>    jump_history.push([page_name, page_index]);
       
 12231 </xsl:text>
       
 12232           <xsl:text>    if(jump_history.length &gt; 42)
       
 12233 </xsl:text>
       
 12234           <xsl:text>        jump_history.shift();
       
 12235 </xsl:text>
       
 12236           <xsl:text>
       
 12237 </xsl:text>
       
 12238           <xsl:text>    apply_hmi_value(current_page_var_index, page_index == undefined
       
 12239 </xsl:text>
       
 12240           <xsl:text>        ? page_name
       
 12241 </xsl:text>
       
 12242           <xsl:text>        : page_name + "@" + hmitree_paths[page_index]);
       
 12243 </xsl:text>
       
 12244           <xsl:text>
       
 12245 </xsl:text>
       
 12246           <xsl:text>    return true;
       
 12247 </xsl:text>
       
 12248           <xsl:text>};
       
 12249 </xsl:text>
       
 12250           <xsl:text>
       
 12251 </xsl:text>
       
 12252           <xsl:text>function switch_visible_page(page_name) {
       
 12253 </xsl:text>
       
 12254           <xsl:text>
       
 12255 </xsl:text>
       
 12256           <xsl:text>    let old_desc = page_desc[current_visible_page];
       
 12257 </xsl:text>
       
 12258           <xsl:text>    let new_desc = page_desc[page_name];
       
 12259 </xsl:text>
       
 12260           <xsl:text>
       
 12261 </xsl:text>
       
 12262           <xsl:text>    if(old_desc){
       
 12263 </xsl:text>
       
 12264           <xsl:text>        for(let eltid in old_desc.required_detachables){
       
 12265 </xsl:text>
       
 12266           <xsl:text>            if(!(eltid in new_desc.required_detachables)){
       
 12267 </xsl:text>
       
 12268           <xsl:text>                let [element, parent] = old_desc.required_detachables[eltid];
       
 12269 </xsl:text>
       
 12270           <xsl:text>                parent.removeChild(element);
       
 12271 </xsl:text>
       
 12272           <xsl:text>            }
       
 12273 </xsl:text>
       
 12274           <xsl:text>        }
       
 12275 </xsl:text>
       
 12276           <xsl:text>        for(let eltid in new_desc.required_detachables){
       
 12277 </xsl:text>
       
 12278           <xsl:text>            if(!(eltid in old_desc.required_detachables)){
       
 12279 </xsl:text>
       
 12280           <xsl:text>                let [element, parent] = new_desc.required_detachables[eltid];
       
 12281 </xsl:text>
       
 12282           <xsl:text>                parent.appendChild(element);
       
 12283 </xsl:text>
       
 12284           <xsl:text>            }
       
 12285 </xsl:text>
       
 12286           <xsl:text>        }
       
 12287 </xsl:text>
       
 12288           <xsl:text>    }else{
       
 12289 </xsl:text>
       
 12290           <xsl:text>        for(let eltid in new_desc.required_detachables){
       
 12291 </xsl:text>
       
 12292           <xsl:text>            let [element, parent] = new_desc.required_detachables[eltid];
       
 12293 </xsl:text>
       
 12294           <xsl:text>            parent.appendChild(element);
       
 12295 </xsl:text>
       
 12296           <xsl:text>        }
       
 12297 </xsl:text>
       
 12298           <xsl:text>    }
       
 12299 </xsl:text>
       
 12300           <xsl:text>
       
 12301 </xsl:text>
       
 12302           <xsl:text>    svg_root.setAttribute('viewBox',new_desc.bbox.join(" "));
       
 12303 </xsl:text>
       
 12304           <xsl:text>    current_visible_page = page_name;
       
 12305 </xsl:text>
       
 12306           <xsl:text>};
       
 12307 </xsl:text>
       
 12308           <xsl:text>
       
 12309 </xsl:text>
       
 12310           <xsl:text>/* From https://jsfiddle.net/ibowankenobi/1mmh7rs6/6/ */
       
 12311 </xsl:text>
       
 12312           <xsl:text>function getAbsoluteCTM(element){
       
 12313 </xsl:text>
       
 12314           <xsl:text>	var height = svg_root.height.baseVal.value,
       
 12315 </xsl:text>
       
 12316           <xsl:text>		width = svg_root.width.baseVal.value,
       
 12317 </xsl:text>
       
 12318           <xsl:text>		viewBoxRect = svg_root.viewBox.baseVal,
       
 12319 </xsl:text>
       
 12320           <xsl:text>		vHeight = viewBoxRect.height,
       
 12321 </xsl:text>
       
 12322           <xsl:text>		vWidth = viewBoxRect.width;
       
 12323 </xsl:text>
       
 12324           <xsl:text>	if(!vWidth || !vHeight){
       
 12325 </xsl:text>
       
 12326           <xsl:text>		return element.getCTM();
       
 12327 </xsl:text>
       
 12328           <xsl:text>	}
       
 12329 </xsl:text>
       
 12330           <xsl:text>	var sH = height/vHeight,
       
 12331 </xsl:text>
       
 12332           <xsl:text>		sW = width/vWidth,
       
 12333 </xsl:text>
       
 12334           <xsl:text>		matrix = svg_root.createSVGMatrix();
       
 12335 </xsl:text>
       
 12336           <xsl:text>	matrix.a = sW;
       
 12337 </xsl:text>
       
 12338           <xsl:text>	matrix.d = sH
       
 12339 </xsl:text>
       
 12340           <xsl:text>	var realCTM = element.getCTM().multiply(matrix.inverse());
       
 12341 </xsl:text>
       
 12342           <xsl:text>	realCTM.e = realCTM.e/sW + viewBoxRect.x;
       
 12343 </xsl:text>
       
 12344           <xsl:text>	realCTM.f = realCTM.f/sH + viewBoxRect.y;
       
 12345 </xsl:text>
       
 12346           <xsl:text>	return realCTM;
       
 12347 </xsl:text>
       
 12348           <xsl:text>}
       
 12349 </xsl:text>
       
 12350           <xsl:text>
       
 12351 </xsl:text>
       
 12352           <xsl:text>function apply_reference_frames(){
       
 12353 </xsl:text>
       
 12354           <xsl:text>    const matches = svg_root.querySelectorAll("g[svghmi_x_offset]");
       
 12355 </xsl:text>
       
 12356           <xsl:text>    matches.forEach((group) =&gt; {
       
 12357 </xsl:text>
       
 12358           <xsl:text>        let [x,y] = ["x", "y"].map((axis) =&gt; Number(group.getAttribute("svghmi_"+axis+"_offset")));
       
 12359 </xsl:text>
       
 12360           <xsl:text>        let ctm = getAbsoluteCTM(group);
       
 12361 </xsl:text>
       
 12362           <xsl:text>        // zero translation part of CTM
       
 12363 </xsl:text>
       
 12364           <xsl:text>        // to only apply rotation/skewing to offset vector
       
 12365 </xsl:text>
       
 12366           <xsl:text>        ctm.e = 0;
       
 12367 </xsl:text>
       
 12368           <xsl:text>        ctm.f = 0;
       
 12369 </xsl:text>
       
 12370           <xsl:text>        let invctm = ctm.inverse();
       
 12371 </xsl:text>
       
 12372           <xsl:text>        let vect = new DOMPoint(x, y);
       
 12373 </xsl:text>
       
 12374           <xsl:text>        let newvect = vect.matrixTransform(invctm);
       
 12375 </xsl:text>
       
 12376           <xsl:text>        let transform = svg_root.createSVGTransform();
       
 12377 </xsl:text>
       
 12378           <xsl:text>        transform.setTranslate(newvect.x, newvect.y);
       
 12379 </xsl:text>
       
 12380           <xsl:text>        group.transform.baseVal.appendItem(transform);
       
 12381 </xsl:text>
       
 12382           <xsl:text>        ["x", "y"].forEach((axis) =&gt; group.removeAttribute("svghmi_"+axis+"_offset"));
       
 12383 </xsl:text>
       
 12384           <xsl:text>    });
       
 12385 </xsl:text>
       
 12386           <xsl:text>}
       
 12387 </xsl:text>
       
 12388           <xsl:text>
       
 12389 </xsl:text>
       
 12390           <xsl:text>// Once connection established
       
 12391 </xsl:text>
       
 12392           <xsl:text>ws.onopen = function (evt) {
       
 12393 </xsl:text>
       
 12394           <xsl:text>    apply_reference_frames();
       
 12395 </xsl:text>
       
 12396           <xsl:text>    init_widgets();
       
 12397 </xsl:text>
       
 12398           <xsl:text>    send_reset();
       
 12399 </xsl:text>
       
 12400           <xsl:text>    // show main page
       
 12401 </xsl:text>
       
 12402           <xsl:text>    prepare_svg();
       
 12403 </xsl:text>
       
 12404           <xsl:text>    switch_page(default_page);
       
 12405 </xsl:text>
       
 12406           <xsl:text>};
       
 12407 </xsl:text>
       
 12408           <xsl:text>
       
 12409 </xsl:text>
       
 12410           <xsl:text>ws.onclose = function (evt) {
       
 12411 </xsl:text>
       
 12412           <xsl:text>    // TODO : add visible notification while waiting for reload
       
 12413 </xsl:text>
       
 12414           <xsl:text>    console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s.");
       
 12415 </xsl:text>
       
 12416           <xsl:text>    // TODO : re-enable auto reload when not in debug
       
 12417 </xsl:text>
       
 12418           <xsl:text>    //window.setTimeout(() =&gt; location.reload(true), 10000);
       
 12419 </xsl:text>
       
 12420           <xsl:text>    alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
       
 12421 </xsl:text>
       
 12422           <xsl:text>
       
 12423 </xsl:text>
       
 12424           <xsl:text>};
       
 12425 </xsl:text>
 12519 </xsl:text>
 12426           <xsl:text>
 12520           <xsl:text>
 12427 </xsl:text>
 12521 </xsl:text>
 12428           <xsl:text>const xmlns = "http://www.w3.org/2000/svg";
 12522           <xsl:text>const xmlns = "http://www.w3.org/2000/svg";
 12429 </xsl:text>
 12523 </xsl:text>