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