svghmi/gen_index_xhtml.xslt
branchsvghmi
changeset 2938 1379cd5c69dd
parent 2936 53fb11263ff1
child 2939 4296ab974d4d
equal deleted inserted replaced
2937:9226a830fbc3 2938:1379cd5c69dd
  2070 </xsl:text>
  2070 </xsl:text>
  2071     </xsl:for-each>
  2071     </xsl:for-each>
  2072     <xsl:text>    ],
  2072     <xsl:text>    ],
  2073 </xsl:text>
  2073 </xsl:text>
  2074   </xsl:template>
  2074   </xsl:template>
       
  2075   <xsl:template name="scripts">
       
  2076     <xsl:text>
       
  2077 </xsl:text>
       
  2078     <xsl:text>id = idstr =&gt; document.getElementById(idstr);
       
  2079 </xsl:text>
       
  2080     <xsl:text>
       
  2081 </xsl:text>
       
  2082     <xsl:text>var hmi_hash = [</xsl:text>
       
  2083     <xsl:value-of select="$hmitree/@hash"/>
       
  2084     <xsl:text>];
       
  2085 </xsl:text>
       
  2086     <xsl:text>var hmi_widgets = {
       
  2087 </xsl:text>
       
  2088     <xsl:apply-templates mode="hmi_elements" select="$hmi_elements"/>
       
  2089     <xsl:text>}
       
  2090 </xsl:text>
       
  2091     <xsl:text>
       
  2092 </xsl:text>
       
  2093     <xsl:text>var heartbeat_index = </xsl:text>
       
  2094     <xsl:value-of select="$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index"/>
       
  2095     <xsl:text>;
       
  2096 </xsl:text>
       
  2097     <xsl:text>
       
  2098 </xsl:text>
       
  2099     <xsl:text>var hmitree_types = [
       
  2100 </xsl:text>
       
  2101     <xsl:for-each select="$indexed_hmitree/*">
       
  2102       <xsl:text>    /* </xsl:text>
       
  2103       <xsl:value-of select="@index"/>
       
  2104       <xsl:text>  </xsl:text>
       
  2105       <xsl:value-of select="@hmipath"/>
       
  2106       <xsl:text> */ "</xsl:text>
       
  2107       <xsl:value-of select="substring(local-name(), 5)"/>
       
  2108       <xsl:text>"</xsl:text>
       
  2109       <xsl:if test="position()!=last()">
       
  2110         <xsl:text>,</xsl:text>
       
  2111       </xsl:if>
       
  2112       <xsl:text>
       
  2113 </xsl:text>
       
  2114     </xsl:for-each>
       
  2115     <xsl:text>]
       
  2116 </xsl:text>
       
  2117     <xsl:text>
       
  2118 </xsl:text>
       
  2119     <xsl:text>var detachable_elements = {
       
  2120 </xsl:text>
       
  2121     <xsl:for-each select="$detachable_elements">
       
  2122       <xsl:text>    "</xsl:text>
       
  2123       <xsl:value-of select="@id"/>
       
  2124       <xsl:text>":[id("</xsl:text>
       
  2125       <xsl:value-of select="@id"/>
       
  2126       <xsl:text>"), id("</xsl:text>
       
  2127       <xsl:value-of select="../@id"/>
       
  2128       <xsl:text>")]</xsl:text>
       
  2129       <xsl:if test="position()!=last()">
       
  2130         <xsl:text>,</xsl:text>
       
  2131       </xsl:if>
       
  2132       <xsl:text>
       
  2133 </xsl:text>
       
  2134     </xsl:for-each>
       
  2135     <xsl:text>}
       
  2136 </xsl:text>
       
  2137     <xsl:text>
       
  2138 </xsl:text>
       
  2139     <xsl:text>var page_desc = {
       
  2140 </xsl:text>
       
  2141     <xsl:apply-templates mode="page_desc" select="$hmi_pages"/>
       
  2142     <xsl:text>}
       
  2143 </xsl:text>
       
  2144     <xsl:text>var keypads = {
       
  2145 </xsl:text>
       
  2146     <xsl:for-each select="$keypads_descs">
       
  2147       <xsl:variable name="keypad_id" select="@id"/>
       
  2148       <xsl:for-each select="arg">
       
  2149         <xsl:variable name="g" select="$geometry[@Id = $keypad_id]"/>
       
  2150         <xsl:text>    "</xsl:text>
       
  2151         <xsl:value-of select="@value"/>
       
  2152         <xsl:text>":["</xsl:text>
       
  2153         <xsl:value-of select="$keypad_id"/>
       
  2154         <xsl:text>", </xsl:text>
       
  2155         <xsl:value-of select="$g/@x"/>
       
  2156         <xsl:text>, </xsl:text>
       
  2157         <xsl:value-of select="$g/@y"/>
       
  2158         <xsl:text>],
       
  2159 </xsl:text>
       
  2160       </xsl:for-each>
       
  2161     </xsl:for-each>
       
  2162     <xsl:text>}
       
  2163 </xsl:text>
       
  2164     <xsl:text>
       
  2165 </xsl:text>
       
  2166     <xsl:text>var default_page = "</xsl:text>
       
  2167     <xsl:value-of select="$default_page"/>
       
  2168     <xsl:text>";
       
  2169 </xsl:text>
       
  2170     <xsl:text>var svg_root = id("</xsl:text>
       
  2171     <xsl:value-of select="/svg:svg/@id"/>
       
  2172     <xsl:text>");
       
  2173 </xsl:text>
       
  2174     <xsl:text>// svghmi.js
       
  2175 </xsl:text>
       
  2176     <xsl:text>
       
  2177 </xsl:text>
       
  2178     <xsl:text>var cache = hmitree_types.map(_ignored =&gt; undefined);
       
  2179 </xsl:text>
       
  2180     <xsl:text>var updates = {};
       
  2181 </xsl:text>
       
  2182     <xsl:text>var need_cache_apply = []; 
       
  2183 </xsl:text>
       
  2184     <xsl:text>var jumps_need_update = false;
       
  2185 </xsl:text>
       
  2186     <xsl:text>var jump_history = [[default_page, undefined]];
       
  2187 </xsl:text>
       
  2188     <xsl:text>
       
  2189 </xsl:text>
       
  2190     <xsl:text>function dispatch_value_to_widget(widget, index, value, oldval) {
       
  2191 </xsl:text>
       
  2192     <xsl:text>    try {
       
  2193 </xsl:text>
       
  2194     <xsl:text>        let idx = widget.offset ? index - widget.offset : index;
       
  2195 </xsl:text>
       
  2196     <xsl:text>        let idxidx = widget.indexes.indexOf(idx);
       
  2197 </xsl:text>
       
  2198     <xsl:text>        let d = widget.dispatch;
       
  2199 </xsl:text>
       
  2200     <xsl:text>        if(typeof(d) == "function" &amp;&amp; idxidx == 0){
       
  2201 </xsl:text>
       
  2202     <xsl:text>            d.call(widget, value, oldval);
       
  2203 </xsl:text>
       
  2204     <xsl:text>        }
       
  2205 </xsl:text>
       
  2206     <xsl:text>        else if(typeof(d) == "object" &amp;&amp; d.length &gt;= idxidx){
       
  2207 </xsl:text>
       
  2208     <xsl:text>            d[idxidx].call(widget, value, oldval);
       
  2209 </xsl:text>
       
  2210     <xsl:text>        }
       
  2211 </xsl:text>
       
  2212     <xsl:text>        /* else dispatch_0, ..., dispatch_n ? */
       
  2213 </xsl:text>
       
  2214     <xsl:text>        /*else {
       
  2215 </xsl:text>
       
  2216     <xsl:text>            throw new Error("Dunno how to dispatch to widget at index = " + index);
       
  2217 </xsl:text>
       
  2218     <xsl:text>        }*/
       
  2219 </xsl:text>
       
  2220     <xsl:text>    } catch(err) {
       
  2221 </xsl:text>
       
  2222     <xsl:text>        console.log(err);
       
  2223 </xsl:text>
       
  2224     <xsl:text>    }
       
  2225 </xsl:text>
       
  2226     <xsl:text>}
       
  2227 </xsl:text>
       
  2228     <xsl:text>
       
  2229 </xsl:text>
       
  2230     <xsl:text>function dispatch_value(index, value) {
       
  2231 </xsl:text>
       
  2232     <xsl:text>    let widgets = subscribers[index];
       
  2233 </xsl:text>
       
  2234     <xsl:text>
       
  2235 </xsl:text>
       
  2236     <xsl:text>    let oldval = cache[index];
       
  2237 </xsl:text>
       
  2238     <xsl:text>    cache[index] = value;
       
  2239 </xsl:text>
       
  2240     <xsl:text>
       
  2241 </xsl:text>
       
  2242     <xsl:text>    if(widgets.size &gt; 0) {
       
  2243 </xsl:text>
       
  2244     <xsl:text>        for(let widget of widgets){
       
  2245 </xsl:text>
       
  2246     <xsl:text>            dispatch_value_to_widget(widget, index, value, oldval);
       
  2247 </xsl:text>
       
  2248     <xsl:text>        }
       
  2249 </xsl:text>
       
  2250     <xsl:text>    }
       
  2251 </xsl:text>
       
  2252     <xsl:text>};
       
  2253 </xsl:text>
       
  2254     <xsl:text>
       
  2255 </xsl:text>
       
  2256     <xsl:text>function init_widgets() {
       
  2257 </xsl:text>
       
  2258     <xsl:text>    Object.keys(hmi_widgets).forEach(function(id) {
       
  2259 </xsl:text>
       
  2260     <xsl:text>        let widget = hmi_widgets[id];
       
  2261 </xsl:text>
       
  2262     <xsl:text>        let init = widget.init;
       
  2263 </xsl:text>
       
  2264     <xsl:text>        if(typeof(init) == "function"){
       
  2265 </xsl:text>
       
  2266     <xsl:text>            try {
       
  2267 </xsl:text>
       
  2268     <xsl:text>                init.call(widget);
       
  2269 </xsl:text>
       
  2270     <xsl:text>            } catch(err) {
       
  2271 </xsl:text>
       
  2272     <xsl:text>                console.log(err);
       
  2273 </xsl:text>
       
  2274     <xsl:text>            }
       
  2275 </xsl:text>
       
  2276     <xsl:text>        }
       
  2277 </xsl:text>
       
  2278     <xsl:text>    });
       
  2279 </xsl:text>
       
  2280     <xsl:text>};
       
  2281 </xsl:text>
       
  2282     <xsl:text>
       
  2283 </xsl:text>
       
  2284     <xsl:text>// Open WebSocket to relative "/ws" address
       
  2285 </xsl:text>
       
  2286     <xsl:text>var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
       
  2287 </xsl:text>
       
  2288     <xsl:text>ws.binaryType = 'arraybuffer';
       
  2289 </xsl:text>
       
  2290     <xsl:text>
       
  2291 </xsl:text>
       
  2292     <xsl:text>const dvgetters = {
       
  2293 </xsl:text>
       
  2294     <xsl:text>    INT: (dv,offset) =&gt; [dv.getInt16(offset, true), 2],
       
  2295 </xsl:text>
       
  2296     <xsl:text>    BOOL: (dv,offset) =&gt; [dv.getInt8(offset, true), 1],
       
  2297 </xsl:text>
       
  2298     <xsl:text>    NODE: (dv,offset) =&gt; [dv.getInt8(offset, true), 1],
       
  2299 </xsl:text>
       
  2300     <xsl:text>    STRING: (dv, offset) =&gt; {
       
  2301 </xsl:text>
       
  2302     <xsl:text>        size = dv.getInt8(offset);
       
  2303 </xsl:text>
       
  2304     <xsl:text>        return [
       
  2305 </xsl:text>
       
  2306     <xsl:text>            String.fromCharCode.apply(null, new Uint8Array(
       
  2307 </xsl:text>
       
  2308     <xsl:text>                dv.buffer, /* original buffer */
       
  2309 </xsl:text>
       
  2310     <xsl:text>                offset + 1, /* string starts after size*/
       
  2311 </xsl:text>
       
  2312     <xsl:text>                size /* size of string */
       
  2313 </xsl:text>
       
  2314     <xsl:text>            )), size + 1]; /* total increment */
       
  2315 </xsl:text>
       
  2316     <xsl:text>    }
       
  2317 </xsl:text>
       
  2318     <xsl:text>};
       
  2319 </xsl:text>
       
  2320     <xsl:text>
       
  2321 </xsl:text>
       
  2322     <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets
       
  2323 </xsl:text>
       
  2324     <xsl:text>function apply_updates() {
       
  2325 </xsl:text>
       
  2326     <xsl:text>    for(let index in updates){
       
  2327 </xsl:text>
       
  2328     <xsl:text>        // serving as a key, index becomes a string
       
  2329 </xsl:text>
       
  2330     <xsl:text>        // -&gt; pass Number(index) instead
       
  2331 </xsl:text>
       
  2332     <xsl:text>        dispatch_value(Number(index), updates[index]);
       
  2333 </xsl:text>
       
  2334     <xsl:text>        delete updates[index];
       
  2335 </xsl:text>
       
  2336     <xsl:text>    }
       
  2337 </xsl:text>
       
  2338     <xsl:text>}
       
  2339 </xsl:text>
       
  2340     <xsl:text>
       
  2341 </xsl:text>
       
  2342     <xsl:text>// Called on requestAnimationFrame, modifies DOM
       
  2343 </xsl:text>
       
  2344     <xsl:text>var requestAnimationFrameID = null;
       
  2345 </xsl:text>
       
  2346     <xsl:text>function animate() {
       
  2347 </xsl:text>
       
  2348     <xsl:text>    // Do the page swith if any one pending
       
  2349 </xsl:text>
       
  2350     <xsl:text>    if(current_subscribed_page != current_visible_page){
       
  2351 </xsl:text>
       
  2352     <xsl:text>        switch_visible_page(current_subscribed_page);
       
  2353 </xsl:text>
       
  2354     <xsl:text>    }
       
  2355 </xsl:text>
       
  2356     <xsl:text>
       
  2357 </xsl:text>
       
  2358     <xsl:text>    while(widget = need_cache_apply.pop()){
       
  2359 </xsl:text>
       
  2360     <xsl:text>        widget.apply_cache();
       
  2361 </xsl:text>
       
  2362     <xsl:text>    }
       
  2363 </xsl:text>
       
  2364     <xsl:text>
       
  2365 </xsl:text>
       
  2366     <xsl:text>    if(jumps_need_update) update_jumps();
       
  2367 </xsl:text>
       
  2368     <xsl:text>
       
  2369 </xsl:text>
       
  2370     <xsl:text>    apply_updates();
       
  2371 </xsl:text>
       
  2372     <xsl:text>    requestAnimationFrameID = null;
       
  2373 </xsl:text>
       
  2374     <xsl:text>}
       
  2375 </xsl:text>
       
  2376     <xsl:text>
       
  2377 </xsl:text>
       
  2378     <xsl:text>function requestHMIAnimation() {
       
  2379 </xsl:text>
       
  2380     <xsl:text>    if(requestAnimationFrameID == null){
       
  2381 </xsl:text>
       
  2382     <xsl:text>        requestAnimationFrameID = window.requestAnimationFrame(animate);
       
  2383 </xsl:text>
       
  2384     <xsl:text>    }
       
  2385 </xsl:text>
       
  2386     <xsl:text>}
       
  2387 </xsl:text>
       
  2388     <xsl:text>
       
  2389 </xsl:text>
       
  2390     <xsl:text>// Message reception handler
       
  2391 </xsl:text>
       
  2392     <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing
       
  2393 </xsl:text>
       
  2394     <xsl:text>// are stored until browser can compute next frame, DOM is left untouched
       
  2395 </xsl:text>
       
  2396     <xsl:text>ws.onmessage = function (evt) {
       
  2397 </xsl:text>
       
  2398     <xsl:text>
       
  2399 </xsl:text>
       
  2400     <xsl:text>    let data = evt.data;
       
  2401 </xsl:text>
       
  2402     <xsl:text>    let dv = new DataView(data);
       
  2403 </xsl:text>
       
  2404     <xsl:text>    let i = 0;
       
  2405 </xsl:text>
       
  2406     <xsl:text>    try {
       
  2407 </xsl:text>
       
  2408     <xsl:text>        for(let hash_int of hmi_hash) {
       
  2409 </xsl:text>
       
  2410     <xsl:text>            if(hash_int != dv.getUint8(i)){
       
  2411 </xsl:text>
       
  2412     <xsl:text>                throw new Error("Hash doesn't match");
       
  2413 </xsl:text>
       
  2414     <xsl:text>            };
       
  2415 </xsl:text>
       
  2416     <xsl:text>            i++;
       
  2417 </xsl:text>
       
  2418     <xsl:text>        };
       
  2419 </xsl:text>
       
  2420     <xsl:text>
       
  2421 </xsl:text>
       
  2422     <xsl:text>        while(i &lt; data.byteLength){
       
  2423 </xsl:text>
       
  2424     <xsl:text>            let index = dv.getUint32(i, true);
       
  2425 </xsl:text>
       
  2426     <xsl:text>            i += 4;
       
  2427 </xsl:text>
       
  2428     <xsl:text>            let iectype = hmitree_types[index];
       
  2429 </xsl:text>
       
  2430     <xsl:text>            if(iectype != undefined){
       
  2431 </xsl:text>
       
  2432     <xsl:text>                let dvgetter = dvgetters[iectype];
       
  2433 </xsl:text>
       
  2434     <xsl:text>                let [value, bytesize] = dvgetter(dv,i);
       
  2435 </xsl:text>
       
  2436     <xsl:text>                updates[index] = value;
       
  2437 </xsl:text>
       
  2438     <xsl:text>                i += bytesize;
       
  2439 </xsl:text>
       
  2440     <xsl:text>            } else {
       
  2441 </xsl:text>
       
  2442     <xsl:text>                throw new Error("Unknown index "+index);
       
  2443 </xsl:text>
       
  2444     <xsl:text>            }
       
  2445 </xsl:text>
       
  2446     <xsl:text>        };
       
  2447 </xsl:text>
       
  2448     <xsl:text>        // register for rendering on next frame, since there are updates
       
  2449 </xsl:text>
       
  2450     <xsl:text>        requestHMIAnimation();
       
  2451 </xsl:text>
       
  2452     <xsl:text>    } catch(err) {
       
  2453 </xsl:text>
       
  2454     <xsl:text>        // 1003 is for "Unsupported Data"
       
  2455 </xsl:text>
       
  2456     <xsl:text>        // ws.close(1003, err.message);
       
  2457 </xsl:text>
       
  2458     <xsl:text>
       
  2459 </xsl:text>
       
  2460     <xsl:text>        // TODO : remove debug alert ?
       
  2461 </xsl:text>
       
  2462     <xsl:text>        alert("Error : "+err.message+"\nHMI will be reloaded.");
       
  2463 </xsl:text>
       
  2464     <xsl:text>
       
  2465 </xsl:text>
       
  2466     <xsl:text>        // force reload ignoring cache
       
  2467 </xsl:text>
       
  2468     <xsl:text>        location.reload(true);
       
  2469 </xsl:text>
       
  2470     <xsl:text>    }
       
  2471 </xsl:text>
       
  2472     <xsl:text>};
       
  2473 </xsl:text>
       
  2474     <xsl:text>
       
  2475 </xsl:text>
       
  2476     <xsl:text>
       
  2477 </xsl:text>
       
  2478     <xsl:text>function send_blob(data) {
       
  2479 </xsl:text>
       
  2480     <xsl:text>    if(data.length &gt; 0) {
       
  2481 </xsl:text>
       
  2482     <xsl:text>        ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data)));
       
  2483 </xsl:text>
       
  2484     <xsl:text>    };
       
  2485 </xsl:text>
       
  2486     <xsl:text>};
       
  2487 </xsl:text>
       
  2488     <xsl:text>
       
  2489 </xsl:text>
       
  2490     <xsl:text>const typedarray_types = {
       
  2491 </xsl:text>
       
  2492     <xsl:text>    INT: (number) =&gt; new Int16Array([number]),
       
  2493 </xsl:text>
       
  2494     <xsl:text>    BOOL: (truth) =&gt; new Int16Array([truth]),
       
  2495 </xsl:text>
       
  2496     <xsl:text>    NODE: (truth) =&gt; new Int16Array([truth]),
       
  2497 </xsl:text>
       
  2498     <xsl:text>    STRING: (str) =&gt; {
       
  2499 </xsl:text>
       
  2500     <xsl:text>        // beremiz default string max size is 128
       
  2501 </xsl:text>
       
  2502     <xsl:text>        str = str.slice(0,128);
       
  2503 </xsl:text>
       
  2504     <xsl:text>        binary = new Uint8Array(str.length + 1);
       
  2505 </xsl:text>
       
  2506     <xsl:text>        binary[0] = str.length;
       
  2507 </xsl:text>
       
  2508     <xsl:text>        for(var i = 0; i &lt; str.length; i++){
       
  2509 </xsl:text>
       
  2510     <xsl:text>            binary[i+1] = str.charCodeAt(i);
       
  2511 </xsl:text>
       
  2512     <xsl:text>        }
       
  2513 </xsl:text>
       
  2514     <xsl:text>        return binary;
       
  2515 </xsl:text>
       
  2516     <xsl:text>    }
       
  2517 </xsl:text>
       
  2518     <xsl:text>    /* TODO */
       
  2519 </xsl:text>
       
  2520     <xsl:text>};
       
  2521 </xsl:text>
       
  2522     <xsl:text>
       
  2523 </xsl:text>
       
  2524     <xsl:text>function send_reset() {
       
  2525 </xsl:text>
       
  2526     <xsl:text>    send_blob(new Uint8Array([1])); /* reset = 1 */
       
  2527 </xsl:text>
       
  2528     <xsl:text>};
       
  2529 </xsl:text>
       
  2530     <xsl:text>
       
  2531 </xsl:text>
       
  2532     <xsl:text>// subscription state, as it should be in hmi server
       
  2533 </xsl:text>
       
  2534     <xsl:text>// hmitree indexed array of integers
       
  2535 </xsl:text>
       
  2536     <xsl:text>var subscriptions =  hmitree_types.map(_ignored =&gt; 0);
       
  2537 </xsl:text>
       
  2538     <xsl:text>
       
  2539 </xsl:text>
       
  2540     <xsl:text>// subscription state as needed by widget now
       
  2541 </xsl:text>
       
  2542     <xsl:text>// hmitree indexed array of Sets of widgets objects
       
  2543 </xsl:text>
       
  2544     <xsl:text>var subscribers = hmitree_types.map(_ignored =&gt; new Set());
       
  2545 </xsl:text>
       
  2546     <xsl:text>
       
  2547 </xsl:text>
       
  2548     <xsl:text>// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
       
  2549 </xsl:text>
       
  2550     <xsl:text>// Since dispatch directly calls change_hmi_value,
       
  2551 </xsl:text>
       
  2552     <xsl:text>// PLC will periodically send variable at given frequency
       
  2553 </xsl:text>
       
  2554     <xsl:text>subscribers[heartbeat_index].add({
       
  2555 </xsl:text>
       
  2556     <xsl:text>    /* type: "Watchdog", */
       
  2557 </xsl:text>
       
  2558     <xsl:text>    frequency: 1,
       
  2559 </xsl:text>
       
  2560     <xsl:text>    indexes: [heartbeat_index],
       
  2561 </xsl:text>
       
  2562     <xsl:text>    dispatch: function(value) {
       
  2563 </xsl:text>
       
  2564     <xsl:text>        change_hmi_value(heartbeat_index, "+1");
       
  2565 </xsl:text>
       
  2566     <xsl:text>    }
       
  2567 </xsl:text>
       
  2568     <xsl:text>});
       
  2569 </xsl:text>
       
  2570     <xsl:text>
       
  2571 </xsl:text>
       
  2572     <xsl:text>function update_subscriptions() {
       
  2573 </xsl:text>
       
  2574     <xsl:text>    let delta = [];
       
  2575 </xsl:text>
       
  2576     <xsl:text>    for(let index = 0; index &lt; subscribers.length; index++){
       
  2577 </xsl:text>
       
  2578     <xsl:text>        let widgets = subscribers[index];
       
  2579 </xsl:text>
       
  2580     <xsl:text>
       
  2581 </xsl:text>
       
  2582     <xsl:text>        // periods are in ms
       
  2583 </xsl:text>
       
  2584     <xsl:text>        let previous_period = subscriptions[index];
       
  2585 </xsl:text>
       
  2586     <xsl:text>
       
  2587 </xsl:text>
       
  2588     <xsl:text>        // subscribing with a zero period is unsubscribing
       
  2589 </xsl:text>
       
  2590     <xsl:text>        let new_period = 0;
       
  2591 </xsl:text>
       
  2592     <xsl:text>        if(widgets.size &gt; 0) {
       
  2593 </xsl:text>
       
  2594     <xsl:text>            let maxfreq = 0;
       
  2595 </xsl:text>
       
  2596     <xsl:text>            for(let widget of widgets)
       
  2597 </xsl:text>
       
  2598     <xsl:text>                if(maxfreq &lt; widget.frequency)
       
  2599 </xsl:text>
       
  2600     <xsl:text>                    maxfreq = widget.frequency;
       
  2601 </xsl:text>
       
  2602     <xsl:text>
       
  2603 </xsl:text>
       
  2604     <xsl:text>            if(maxfreq != 0)
       
  2605 </xsl:text>
       
  2606     <xsl:text>                new_period = 1000/maxfreq;
       
  2607 </xsl:text>
       
  2608     <xsl:text>        }
       
  2609 </xsl:text>
       
  2610     <xsl:text>
       
  2611 </xsl:text>
       
  2612     <xsl:text>        if(previous_period != new_period) {
       
  2613 </xsl:text>
       
  2614     <xsl:text>            subscriptions[index] = new_period;
       
  2615 </xsl:text>
       
  2616     <xsl:text>            delta.push(
       
  2617 </xsl:text>
       
  2618     <xsl:text>                new Uint8Array([2]), /* subscribe = 2 */
       
  2619 </xsl:text>
       
  2620     <xsl:text>                new Uint32Array([index]),
       
  2621 </xsl:text>
       
  2622     <xsl:text>                new Uint16Array([new_period]));
       
  2623 </xsl:text>
       
  2624     <xsl:text>        }
       
  2625 </xsl:text>
       
  2626     <xsl:text>    }
       
  2627 </xsl:text>
       
  2628     <xsl:text>    send_blob(delta);
       
  2629 </xsl:text>
       
  2630     <xsl:text>};
       
  2631 </xsl:text>
       
  2632     <xsl:text>
       
  2633 </xsl:text>
       
  2634     <xsl:text>function send_hmi_value(index, value) {
       
  2635 </xsl:text>
       
  2636     <xsl:text>    let iectype = hmitree_types[index];
       
  2637 </xsl:text>
       
  2638     <xsl:text>    let tobinary = typedarray_types[iectype];
       
  2639 </xsl:text>
       
  2640     <xsl:text>    send_blob([
       
  2641 </xsl:text>
       
  2642     <xsl:text>        new Uint8Array([0]),  /* setval = 0 */
       
  2643 </xsl:text>
       
  2644     <xsl:text>        new Uint32Array([index]),
       
  2645 </xsl:text>
       
  2646     <xsl:text>        tobinary(value)]);
       
  2647 </xsl:text>
       
  2648     <xsl:text>
       
  2649 </xsl:text>
       
  2650     <xsl:text>    // DON'T DO THAT unless read_iterator in svghmi.c modifies wbuf as well, not only rbuf
       
  2651 </xsl:text>
       
  2652     <xsl:text>    // cache[index] = value;
       
  2653 </xsl:text>
       
  2654     <xsl:text>};
       
  2655 </xsl:text>
       
  2656     <xsl:text>
       
  2657 </xsl:text>
       
  2658     <xsl:text>function apply_hmi_value(index, new_val) {
       
  2659 </xsl:text>
       
  2660     <xsl:text>    let old_val = cache[index]
       
  2661 </xsl:text>
       
  2662     <xsl:text>    if(new_val != undefined &amp;&amp; old_val != new_val)
       
  2663 </xsl:text>
       
  2664     <xsl:text>        send_hmi_value(index, new_val);
       
  2665 </xsl:text>
       
  2666     <xsl:text>    return new_val;
       
  2667 </xsl:text>
       
  2668     <xsl:text>}
       
  2669 </xsl:text>
       
  2670     <xsl:text>
       
  2671 </xsl:text>
       
  2672     <xsl:text>function change_hmi_value(index, opstr) {
       
  2673 </xsl:text>
       
  2674     <xsl:text>    let op = opstr[0];
       
  2675 </xsl:text>
       
  2676     <xsl:text>    let given_val = opstr.slice(1);
       
  2677 </xsl:text>
       
  2678     <xsl:text>    let old_val = cache[index]
       
  2679 </xsl:text>
       
  2680     <xsl:text>    let new_val;
       
  2681 </xsl:text>
       
  2682     <xsl:text>    switch(op){
       
  2683 </xsl:text>
       
  2684     <xsl:text>      case "=":
       
  2685 </xsl:text>
       
  2686     <xsl:text>        eval("new_val"+opstr);
       
  2687 </xsl:text>
       
  2688     <xsl:text>        break;
       
  2689 </xsl:text>
       
  2690     <xsl:text>      case "+":
       
  2691 </xsl:text>
       
  2692     <xsl:text>      case "-":
       
  2693 </xsl:text>
       
  2694     <xsl:text>      case "*":
       
  2695 </xsl:text>
       
  2696     <xsl:text>      case "/":
       
  2697 </xsl:text>
       
  2698     <xsl:text>        if(old_val != undefined)
       
  2699 </xsl:text>
       
  2700     <xsl:text>            new_val = eval("old_val"+opstr);
       
  2701 </xsl:text>
       
  2702     <xsl:text>        break;
       
  2703 </xsl:text>
       
  2704     <xsl:text>    }
       
  2705 </xsl:text>
       
  2706     <xsl:text>    if(new_val != undefined &amp;&amp; old_val != new_val)
       
  2707 </xsl:text>
       
  2708     <xsl:text>        send_hmi_value(index, new_val);
       
  2709 </xsl:text>
       
  2710     <xsl:text>    return new_val;
       
  2711 </xsl:text>
       
  2712     <xsl:text>}
       
  2713 </xsl:text>
       
  2714     <xsl:text>
       
  2715 </xsl:text>
       
  2716     <xsl:text>var current_visible_page;
       
  2717 </xsl:text>
       
  2718     <xsl:text>var current_subscribed_page;
       
  2719 </xsl:text>
       
  2720     <xsl:text>var current_page_index;
       
  2721 </xsl:text>
       
  2722     <xsl:text>
       
  2723 </xsl:text>
       
  2724     <xsl:text>function prepare_svg() {
       
  2725 </xsl:text>
       
  2726     <xsl:text>    for(let eltid in detachable_elements){
       
  2727 </xsl:text>
       
  2728     <xsl:text>        let [element,parent] = detachable_elements[eltid];
       
  2729 </xsl:text>
       
  2730     <xsl:text>        parent.removeChild(element);
       
  2731 </xsl:text>
       
  2732     <xsl:text>    }
       
  2733 </xsl:text>
       
  2734     <xsl:text>};
       
  2735 </xsl:text>
       
  2736     <xsl:text>
       
  2737 </xsl:text>
       
  2738     <xsl:text>function switch_page(page_name, page_index) {
       
  2739 </xsl:text>
       
  2740     <xsl:text>    if(current_subscribed_page != current_visible_page){
       
  2741 </xsl:text>
       
  2742     <xsl:text>        /* page switch already going */
       
  2743 </xsl:text>
       
  2744     <xsl:text>        /* TODO LOG ERROR */
       
  2745 </xsl:text>
       
  2746     <xsl:text>        return false;
       
  2747 </xsl:text>
       
  2748     <xsl:text>    }
       
  2749 </xsl:text>
       
  2750     <xsl:text>
       
  2751 </xsl:text>
       
  2752     <xsl:text>    if(page_name == undefined)
       
  2753 </xsl:text>
       
  2754     <xsl:text>        page_name = current_subscribed_page;
       
  2755 </xsl:text>
       
  2756     <xsl:text>
       
  2757 </xsl:text>
       
  2758     <xsl:text>
       
  2759 </xsl:text>
       
  2760     <xsl:text>    let old_desc = page_desc[current_subscribed_page];
       
  2761 </xsl:text>
       
  2762     <xsl:text>    let new_desc = page_desc[page_name];
       
  2763 </xsl:text>
       
  2764     <xsl:text>
       
  2765 </xsl:text>
       
  2766     <xsl:text>    if(new_desc == undefined){
       
  2767 </xsl:text>
       
  2768     <xsl:text>        /* TODO LOG ERROR */
       
  2769 </xsl:text>
       
  2770     <xsl:text>        return false;
       
  2771 </xsl:text>
       
  2772     <xsl:text>    }
       
  2773 </xsl:text>
       
  2774     <xsl:text>
       
  2775 </xsl:text>
       
  2776     <xsl:text>    if(page_index == undefined){
       
  2777 </xsl:text>
       
  2778     <xsl:text>        page_index = new_desc.page_index;
       
  2779 </xsl:text>
       
  2780     <xsl:text>    }
       
  2781 </xsl:text>
       
  2782     <xsl:text>
       
  2783 </xsl:text>
       
  2784     <xsl:text>    if(old_desc){
       
  2785 </xsl:text>
       
  2786     <xsl:text>        old_desc.absolute_widgets.map(w=&gt;w.unsub());
       
  2787 </xsl:text>
       
  2788     <xsl:text>        old_desc.relative_widgets.map(w=&gt;w.unsub());
       
  2789 </xsl:text>
       
  2790     <xsl:text>    }
       
  2791 </xsl:text>
       
  2792     <xsl:text>    new_desc.absolute_widgets.map(w=&gt;w.sub());
       
  2793 </xsl:text>
       
  2794     <xsl:text>    var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
       
  2795 </xsl:text>
       
  2796     <xsl:text>    new_desc.relative_widgets.map(w=&gt;w.sub(new_offset));
       
  2797 </xsl:text>
       
  2798     <xsl:text>
       
  2799 </xsl:text>
       
  2800     <xsl:text>    update_subscriptions();
       
  2801 </xsl:text>
       
  2802     <xsl:text>
       
  2803 </xsl:text>
       
  2804     <xsl:text>    current_subscribed_page = page_name;
       
  2805 </xsl:text>
       
  2806     <xsl:text>    current_page_index = page_index;
       
  2807 </xsl:text>
       
  2808     <xsl:text>
       
  2809 </xsl:text>
       
  2810     <xsl:text>    jumps_need_update = true;
       
  2811 </xsl:text>
       
  2812     <xsl:text>
       
  2813 </xsl:text>
       
  2814     <xsl:text>    requestHMIAnimation();
       
  2815 </xsl:text>
       
  2816     <xsl:text>
       
  2817 </xsl:text>
       
  2818     <xsl:text>    jump_history.push([page_name, page_index]);
       
  2819 </xsl:text>
       
  2820     <xsl:text>    if(jump_history.length &gt; 42)
       
  2821 </xsl:text>
       
  2822     <xsl:text>        jump_history.shift();
       
  2823 </xsl:text>
       
  2824     <xsl:text>
       
  2825 </xsl:text>
       
  2826     <xsl:text>    return true;
       
  2827 </xsl:text>
       
  2828     <xsl:text>};
       
  2829 </xsl:text>
       
  2830     <xsl:text>
       
  2831 </xsl:text>
       
  2832     <xsl:text>function* chain(a,b){
       
  2833 </xsl:text>
       
  2834     <xsl:text>    yield* a;
       
  2835 </xsl:text>
       
  2836     <xsl:text>    yield* b;
       
  2837 </xsl:text>
       
  2838     <xsl:text>};
       
  2839 </xsl:text>
       
  2840     <xsl:text>
       
  2841 </xsl:text>
       
  2842     <xsl:text>function unsubscribe(){
       
  2843 </xsl:text>
       
  2844     <xsl:text>    /* remove subsribers */
       
  2845 </xsl:text>
       
  2846     <xsl:text>    for(let index of this.indexes){
       
  2847 </xsl:text>
       
  2848     <xsl:text>        let idx = index + this.offset;
       
  2849 </xsl:text>
       
  2850     <xsl:text>        subscribers[idx].delete(this);
       
  2851 </xsl:text>
       
  2852     <xsl:text>    }
       
  2853 </xsl:text>
       
  2854     <xsl:text>    this.offset = 0;
       
  2855 </xsl:text>
       
  2856     <xsl:text>}
       
  2857 </xsl:text>
       
  2858     <xsl:text>
       
  2859 </xsl:text>
       
  2860     <xsl:text>function subscribe(new_offset=0){
       
  2861 </xsl:text>
       
  2862     <xsl:text>    /* set the offset because relative */
       
  2863 </xsl:text>
       
  2864     <xsl:text>    this.offset = new_offset;
       
  2865 </xsl:text>
       
  2866     <xsl:text>    /* add this's subsribers */
       
  2867 </xsl:text>
       
  2868     <xsl:text>    for(let index of this.indexes){
       
  2869 </xsl:text>
       
  2870     <xsl:text>        subscribers[index + new_offset].add(this);
       
  2871 </xsl:text>
       
  2872     <xsl:text>    }
       
  2873 </xsl:text>
       
  2874     <xsl:text>    need_cache_apply.push(this); 
       
  2875 </xsl:text>
       
  2876     <xsl:text>}
       
  2877 </xsl:text>
       
  2878     <xsl:text>
       
  2879 </xsl:text>
       
  2880     <xsl:text>function foreach_unsubscribe(){
       
  2881 </xsl:text>
       
  2882     <xsl:text>    for(let item of this.items){
       
  2883 </xsl:text>
       
  2884     <xsl:text>        for(let widget of item) {
       
  2885 </xsl:text>
       
  2886     <xsl:text>            unsubscribe.call(widget);
       
  2887 </xsl:text>
       
  2888     <xsl:text>        }
       
  2889 </xsl:text>
       
  2890     <xsl:text>    }
       
  2891 </xsl:text>
       
  2892     <xsl:text>    this.offset = 0;
       
  2893 </xsl:text>
       
  2894     <xsl:text>}
       
  2895 </xsl:text>
       
  2896     <xsl:text>
       
  2897 </xsl:text>
       
  2898     <xsl:text>function foreach_widgets_do(new_offset, todo){
       
  2899 </xsl:text>
       
  2900     <xsl:text>    this.offset = new_offset;
       
  2901 </xsl:text>
       
  2902     <xsl:text>    for(let i = 0; i &lt; this.items.length; i++) {
       
  2903 </xsl:text>
       
  2904     <xsl:text>        let item = this.items[i];
       
  2905 </xsl:text>
       
  2906     <xsl:text>        let orig_item_index = this.index_pool[i];
       
  2907 </xsl:text>
       
  2908     <xsl:text>        let item_index = this.index_pool[i+this.item_offset];
       
  2909 </xsl:text>
       
  2910     <xsl:text>        let item_index_offset = item_index - orig_item_index;
       
  2911 </xsl:text>
       
  2912     <xsl:text>        for(let widget of item) {
       
  2913 </xsl:text>
       
  2914     <xsl:text>            todo.call(widget, new_offset + item_index_offset);
       
  2915 </xsl:text>
       
  2916     <xsl:text>        }
       
  2917 </xsl:text>
       
  2918     <xsl:text>    }
       
  2919 </xsl:text>
       
  2920     <xsl:text>}
       
  2921 </xsl:text>
       
  2922     <xsl:text>
       
  2923 </xsl:text>
       
  2924     <xsl:text>function foreach_subscribe(new_offset=0){
       
  2925 </xsl:text>
       
  2926     <xsl:text>    foreach_widgets_do.call(this, new_offset, subscribe);
       
  2927 </xsl:text>
       
  2928     <xsl:text>}
       
  2929 </xsl:text>
       
  2930     <xsl:text>
       
  2931 </xsl:text>
       
  2932     <xsl:text>function widget_apply_cache() {
       
  2933 </xsl:text>
       
  2934     <xsl:text>    for(let index of this.indexes){
       
  2935 </xsl:text>
       
  2936     <xsl:text>        /* dispatch current cache in newly opened page widgets */
       
  2937 </xsl:text>
       
  2938     <xsl:text>        let realindex = index+this.offset;
       
  2939 </xsl:text>
       
  2940     <xsl:text>        let cached_val = cache[realindex];
       
  2941 </xsl:text>
       
  2942     <xsl:text>        if(cached_val != undefined)
       
  2943 </xsl:text>
       
  2944     <xsl:text>            dispatch_value_to_widget(this, realindex, cached_val, cached_val);
       
  2945 </xsl:text>
       
  2946     <xsl:text>    }
       
  2947 </xsl:text>
       
  2948     <xsl:text>}
       
  2949 </xsl:text>
       
  2950     <xsl:text>
       
  2951 </xsl:text>
       
  2952     <xsl:text>function foreach_apply_cache() {
       
  2953 </xsl:text>
       
  2954     <xsl:text>    foreach_widgets_do.call(this, this.offset, widget_apply_cache);
       
  2955 </xsl:text>
       
  2956     <xsl:text>}
       
  2957 </xsl:text>
       
  2958     <xsl:text>
       
  2959 </xsl:text>
       
  2960     <xsl:text>function foreach_onclick(opstr, evt) {
       
  2961 </xsl:text>
       
  2962     <xsl:text>    new_item_offset = eval(String(this.item_offset)+opstr)
       
  2963 </xsl:text>
       
  2964     <xsl:text>    if(new_item_offset + this.items.length &gt; this.index_pool.length) {
       
  2965 </xsl:text>
       
  2966     <xsl:text>        if(this.item_offset + this.items.length == this.index_pool.length)
       
  2967 </xsl:text>
       
  2968     <xsl:text>            new_item_offset = 0;
       
  2969 </xsl:text>
       
  2970     <xsl:text>        else
       
  2971 </xsl:text>
       
  2972     <xsl:text>            new_item_offset = this.index_pool.length - this.items.length;
       
  2973 </xsl:text>
       
  2974     <xsl:text>    } else if(new_item_offset &lt; 0) {
       
  2975 </xsl:text>
       
  2976     <xsl:text>        if(this.item_offset == 0)
       
  2977 </xsl:text>
       
  2978     <xsl:text>            new_item_offset = this.index_pool.length - this.items.length;
       
  2979 </xsl:text>
       
  2980     <xsl:text>        else
       
  2981 </xsl:text>
       
  2982     <xsl:text>            new_item_offset = 0;
       
  2983 </xsl:text>
       
  2984     <xsl:text>    }
       
  2985 </xsl:text>
       
  2986     <xsl:text>    this.item_offset = new_item_offset;
       
  2987 </xsl:text>
       
  2988     <xsl:text>    off = this.offset;
       
  2989 </xsl:text>
       
  2990     <xsl:text>    foreach_unsubscribe.call(this);
       
  2991 </xsl:text>
       
  2992     <xsl:text>    foreach_subscribe.call(this,off);
       
  2993 </xsl:text>
       
  2994     <xsl:text>    update_subscriptions();
       
  2995 </xsl:text>
       
  2996     <xsl:text>    need_cache_apply.push(this);
       
  2997 </xsl:text>
       
  2998     <xsl:text>    jumps_need_update = true;
       
  2999 </xsl:text>
       
  3000     <xsl:text>    requestHMIAnimation();
       
  3001 </xsl:text>
       
  3002     <xsl:text>}
       
  3003 </xsl:text>
       
  3004     <xsl:text>
       
  3005 </xsl:text>
       
  3006     <xsl:text>
       
  3007 </xsl:text>
       
  3008     <xsl:text>function switch_visible_page(page_name) {
       
  3009 </xsl:text>
       
  3010     <xsl:text>
       
  3011 </xsl:text>
       
  3012     <xsl:text>    let old_desc = page_desc[current_visible_page];
       
  3013 </xsl:text>
       
  3014     <xsl:text>    let new_desc = page_desc[page_name];
       
  3015 </xsl:text>
       
  3016     <xsl:text>
       
  3017 </xsl:text>
       
  3018     <xsl:text>    if(old_desc){
       
  3019 </xsl:text>
       
  3020     <xsl:text>        for(let eltid in old_desc.required_detachables){
       
  3021 </xsl:text>
       
  3022     <xsl:text>            if(!(eltid in new_desc.required_detachables)){
       
  3023 </xsl:text>
       
  3024     <xsl:text>                let [element, parent] = old_desc.required_detachables[eltid];
       
  3025 </xsl:text>
       
  3026     <xsl:text>                parent.removeChild(element);
       
  3027 </xsl:text>
       
  3028     <xsl:text>            }
       
  3029 </xsl:text>
       
  3030     <xsl:text>        }
       
  3031 </xsl:text>
       
  3032     <xsl:text>        for(let eltid in new_desc.required_detachables){
       
  3033 </xsl:text>
       
  3034     <xsl:text>            if(!(eltid in old_desc.required_detachables)){
       
  3035 </xsl:text>
       
  3036     <xsl:text>                let [element, parent] = new_desc.required_detachables[eltid];
       
  3037 </xsl:text>
       
  3038     <xsl:text>                parent.appendChild(element);
       
  3039 </xsl:text>
       
  3040     <xsl:text>            }
       
  3041 </xsl:text>
       
  3042     <xsl:text>        }
       
  3043 </xsl:text>
       
  3044     <xsl:text>    }else{
       
  3045 </xsl:text>
       
  3046     <xsl:text>        for(let eltid in new_desc.required_detachables){
       
  3047 </xsl:text>
       
  3048     <xsl:text>            let [element, parent] = new_desc.required_detachables[eltid];
       
  3049 </xsl:text>
       
  3050     <xsl:text>            parent.appendChild(element);
       
  3051 </xsl:text>
       
  3052     <xsl:text>        }
       
  3053 </xsl:text>
       
  3054     <xsl:text>    }
       
  3055 </xsl:text>
       
  3056     <xsl:text>
       
  3057 </xsl:text>
       
  3058     <xsl:text>    svg_root.setAttribute('viewBox',new_desc.bbox.join(" "));
       
  3059 </xsl:text>
       
  3060     <xsl:text>    current_visible_page = page_name;
       
  3061 </xsl:text>
       
  3062     <xsl:text>};
       
  3063 </xsl:text>
       
  3064     <xsl:text>
       
  3065 </xsl:text>
       
  3066     <xsl:text>function update_jumps() {
       
  3067 </xsl:text>
       
  3068     <xsl:text>    page_desc[current_visible_page].jumps.map(w=&gt;w.notify_page_change(current_visible_page,current_page_index));
       
  3069 </xsl:text>
       
  3070     <xsl:text>    jumps_need_update = false;
       
  3071 </xsl:text>
       
  3072     <xsl:text>};
       
  3073 </xsl:text>
       
  3074     <xsl:text>
       
  3075 </xsl:text>
       
  3076     <xsl:text>
       
  3077 </xsl:text>
       
  3078     <xsl:text>// Once connection established
       
  3079 </xsl:text>
       
  3080     <xsl:text>ws.onopen = function (evt) {
       
  3081 </xsl:text>
       
  3082     <xsl:text>    init_widgets();
       
  3083 </xsl:text>
       
  3084     <xsl:text>    send_reset();
       
  3085 </xsl:text>
       
  3086     <xsl:text>    // show main page
       
  3087 </xsl:text>
       
  3088     <xsl:text>    prepare_svg();
       
  3089 </xsl:text>
       
  3090     <xsl:text>    switch_page(default_page);
       
  3091 </xsl:text>
       
  3092     <xsl:text>};
       
  3093 </xsl:text>
       
  3094     <xsl:text>
       
  3095 </xsl:text>
       
  3096     <xsl:text>ws.onclose = function (evt) {
       
  3097 </xsl:text>
       
  3098     <xsl:text>    // TODO : add visible notification while waiting for reload
       
  3099 </xsl:text>
       
  3100     <xsl:text>    console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s.");
       
  3101 </xsl:text>
       
  3102     <xsl:text>    // TODO : re-enable auto reload when not in debug
       
  3103 </xsl:text>
       
  3104     <xsl:text>    //window.setTimeout(() =&gt; location.reload(true), 10000);
       
  3105 </xsl:text>
       
  3106     <xsl:text>    alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
       
  3107 </xsl:text>
       
  3108     <xsl:text>
       
  3109 </xsl:text>
       
  3110     <xsl:text>};
       
  3111 </xsl:text>
       
  3112     <xsl:text>
       
  3113 </xsl:text>
       
  3114     <xsl:text>var xmlns = "http://www.w3.org/2000/svg";
       
  3115 </xsl:text>
       
  3116     <xsl:text>var edit_callback;
       
  3117 </xsl:text>
       
  3118     <xsl:text>function edit_value(path, valuetype, callback, initial) {
       
  3119 </xsl:text>
       
  3120     <xsl:text>
       
  3121 </xsl:text>
       
  3122     <xsl:text>    let [keypadid, xcoord, ycoord] = keypads[valuetype];
       
  3123 </xsl:text>
       
  3124     <xsl:text>    console.log('XXX TODO : Edit value', path, valuetype, callback, initial, keypadid);
       
  3125 </xsl:text>
       
  3126     <xsl:text>    edit_callback = callback;
       
  3127 </xsl:text>
       
  3128     <xsl:text>    let widget = hmi_widgets[keypadid];
       
  3129 </xsl:text>
       
  3130     <xsl:text>    widget.start_edit(path, valuetype, callback, initial);
       
  3131 </xsl:text>
       
  3132     <xsl:text>};
       
  3133 </xsl:text>
       
  3134     <xsl:text>
       
  3135 </xsl:text>
       
  3136     <xsl:text>var current_modal; /* TODO stack ?*/
       
  3137 </xsl:text>
       
  3138     <xsl:text>
       
  3139 </xsl:text>
       
  3140     <xsl:text>function show_modal() {
       
  3141 </xsl:text>
       
  3142     <xsl:text>    let [element, parent] = detachable_elements[this.element.id];
       
  3143 </xsl:text>
       
  3144     <xsl:text>
       
  3145 </xsl:text>
       
  3146     <xsl:text>    tmpgrp = document.createElementNS(xmlns,"g");
       
  3147 </xsl:text>
       
  3148     <xsl:text>    tmpgrpattr = document.createAttribute("transform");
       
  3149 </xsl:text>
       
  3150     <xsl:text>
       
  3151 </xsl:text>
       
  3152     <xsl:text>    let [xcoord,ycoord] = this.coordinates;
       
  3153 </xsl:text>
       
  3154     <xsl:text>    let [xdest,ydest] = page_desc[current_visible_page].bbox;
       
  3155 </xsl:text>
       
  3156     <xsl:text>    tmpgrpattr.value = "translate("+String(xdest-xcoord)+","+String(ydest-ycoord)+")";
       
  3157 </xsl:text>
       
  3158     <xsl:text>    tmpgrp.setAttributeNode(tmpgrpattr);
       
  3159 </xsl:text>
       
  3160     <xsl:text>
       
  3161 </xsl:text>
       
  3162     <xsl:text>    tmpgrp.appendChild(element);
       
  3163 </xsl:text>
       
  3164     <xsl:text>    parent.appendChild(tmpgrp);
       
  3165 </xsl:text>
       
  3166     <xsl:text>
       
  3167 </xsl:text>
       
  3168     <xsl:text>    current_modal = [this.element.id, tmpgrp];
       
  3169 </xsl:text>
       
  3170     <xsl:text>};
       
  3171 </xsl:text>
       
  3172     <xsl:text>
       
  3173 </xsl:text>
       
  3174     <xsl:text>function end_modal() {
       
  3175 </xsl:text>
       
  3176     <xsl:text>    let [eltid, tmpgrp] = current_modal;
       
  3177 </xsl:text>
       
  3178     <xsl:text>    let [element, parent] = detachable_elements[this.element.id];
       
  3179 </xsl:text>
       
  3180     <xsl:text>
       
  3181 </xsl:text>
       
  3182     <xsl:text>    parent.removeChild(tmpgrp);
       
  3183 </xsl:text>
       
  3184     <xsl:text>
       
  3185 </xsl:text>
       
  3186     <xsl:text>    current_modal = undefined;
       
  3187 </xsl:text>
       
  3188     <xsl:text>};
       
  3189 </xsl:text>
       
  3190     <xsl:text>
       
  3191 </xsl:text>
       
  3192     <xsl:text>function widget_active_activable(eltsub) {
       
  3193 </xsl:text>
       
  3194     <xsl:text>    if(eltsub.inactive_style === undefined)
       
  3195 </xsl:text>
       
  3196     <xsl:text>        eltsub.inactive_style = eltsub.inactive.getAttribute("style");
       
  3197 </xsl:text>
       
  3198     <xsl:text>    eltsub.inactive.setAttribute("style", "display:none");
       
  3199 </xsl:text>
       
  3200     <xsl:text>    if(eltsub.active_style !== undefined)
       
  3201 </xsl:text>
       
  3202     <xsl:text>            eltsub.active.setAttribute("style", eltsub.active_style);
       
  3203 </xsl:text>
       
  3204     <xsl:text>    console.log("active", eltsub);
       
  3205 </xsl:text>
       
  3206     <xsl:text>};
       
  3207 </xsl:text>
       
  3208     <xsl:text>function widget_inactive_activable(eltsub) {
       
  3209 </xsl:text>
       
  3210     <xsl:text>    if(eltsub.active_style === undefined)
       
  3211 </xsl:text>
       
  3212     <xsl:text>        eltsub.active_style = eltsub.active.getAttribute("style");
       
  3213 </xsl:text>
       
  3214     <xsl:text>    eltsub.active.setAttribute("style", "display:none");
       
  3215 </xsl:text>
       
  3216     <xsl:text>    if(eltsub.inactive_style !== undefined)
       
  3217 </xsl:text>
       
  3218     <xsl:text>            eltsub.inactive.setAttribute("style", eltsub.inactive_style);
       
  3219 </xsl:text>
       
  3220     <xsl:text>    console.log("inactive", eltsub);
       
  3221 </xsl:text>
       
  3222     <xsl:text>};
       
  3223 </xsl:text>
       
  3224   </xsl:template>
  2075   <xsl:template mode="debug_as_comment" match="*[namespace-uri()='reflect']">
  3225   <xsl:template mode="debug_as_comment" match="*[namespace-uri()='reflect']">
  2076     <xsl:comment>
  3226     <xsl:comment>
  2077       <xsl:value-of select="local-name()"/>
  3227       <xsl:value-of select="local-name()"/>
  2078       <xsl:text> :
  3228       <xsl:text> :
  2079 </xsl:text>
  3229 </xsl:text>
  2092           <xsl:call-template name="scripts"/>
  3242           <xsl:call-template name="scripts"/>
  2093         </script>
  3243         </script>
  2094       </body>
  3244       </body>
  2095     </html>
  3245     </html>
  2096   </xsl:template>
  3246   </xsl:template>
  2097   <xsl:template name="scripts">
       
  2098     <xsl:text>
       
  2099 </xsl:text>
       
  2100     <xsl:text>id = idstr =&gt; document.getElementById(idstr);
       
  2101 </xsl:text>
       
  2102     <xsl:text>
       
  2103 </xsl:text>
       
  2104     <xsl:text>var hmi_hash = [</xsl:text>
       
  2105     <xsl:value-of select="$hmitree/@hash"/>
       
  2106     <xsl:text>]; 
       
  2107 </xsl:text>
       
  2108     <xsl:text>var hmi_widgets = {
       
  2109 </xsl:text>
       
  2110     <xsl:apply-templates mode="hmi_elements" select="$hmi_elements"/>
       
  2111     <xsl:text>}
       
  2112 </xsl:text>
       
  2113     <xsl:text>
       
  2114 </xsl:text>
       
  2115     <xsl:text>var heartbeat_index = </xsl:text>
       
  2116     <xsl:value-of select="$indexed_hmitree/*[@hmipath = '/HEARTBEAT']/@index"/>
       
  2117     <xsl:text>;
       
  2118 </xsl:text>
       
  2119     <xsl:text>
       
  2120 </xsl:text>
       
  2121     <xsl:text>var hmitree_types = [
       
  2122 </xsl:text>
       
  2123     <xsl:for-each select="$indexed_hmitree/*">
       
  2124       <xsl:text>    /* </xsl:text>
       
  2125       <xsl:value-of select="@index"/>
       
  2126       <xsl:text>  </xsl:text>
       
  2127       <xsl:value-of select="@hmipath"/>
       
  2128       <xsl:text> */ "</xsl:text>
       
  2129       <xsl:value-of select="substring(local-name(), 5)"/>
       
  2130       <xsl:text>"</xsl:text>
       
  2131       <xsl:if test="position()!=last()">
       
  2132         <xsl:text>,</xsl:text>
       
  2133       </xsl:if>
       
  2134       <xsl:text>
       
  2135 </xsl:text>
       
  2136     </xsl:for-each>
       
  2137     <xsl:text>]
       
  2138 </xsl:text>
       
  2139     <xsl:text>
       
  2140 </xsl:text>
       
  2141     <xsl:text>var detachable_elements = {
       
  2142 </xsl:text>
       
  2143     <xsl:for-each select="$detachable_elements">
       
  2144       <xsl:text>    "</xsl:text>
       
  2145       <xsl:value-of select="@id"/>
       
  2146       <xsl:text>":[id("</xsl:text>
       
  2147       <xsl:value-of select="@id"/>
       
  2148       <xsl:text>"), id("</xsl:text>
       
  2149       <xsl:value-of select="../@id"/>
       
  2150       <xsl:text>")]</xsl:text>
       
  2151       <xsl:if test="position()!=last()">
       
  2152         <xsl:text>,</xsl:text>
       
  2153       </xsl:if>
       
  2154       <xsl:text>
       
  2155 </xsl:text>
       
  2156     </xsl:for-each>
       
  2157     <xsl:text>}
       
  2158 </xsl:text>
       
  2159     <xsl:text>
       
  2160 </xsl:text>
       
  2161     <xsl:text>var page_desc = {
       
  2162 </xsl:text>
       
  2163     <xsl:apply-templates mode="page_desc" select="$hmi_pages"/>
       
  2164     <xsl:text>}
       
  2165 </xsl:text>
       
  2166     <xsl:text>var keypads = {
       
  2167 </xsl:text>
       
  2168     <xsl:for-each select="$keypads_descs">
       
  2169       <xsl:variable name="keypad_id" select="@id"/>
       
  2170       <xsl:for-each select="arg">
       
  2171         <xsl:variable name="g" select="$geometry[@Id = $keypad_id]"/>
       
  2172         <xsl:text>    "</xsl:text>
       
  2173         <xsl:value-of select="@value"/>
       
  2174         <xsl:text>":["</xsl:text>
       
  2175         <xsl:value-of select="$keypad_id"/>
       
  2176         <xsl:text>", </xsl:text>
       
  2177         <xsl:value-of select="$g/@x"/>
       
  2178         <xsl:text>, </xsl:text>
       
  2179         <xsl:value-of select="$g/@y"/>
       
  2180         <xsl:text>],
       
  2181 </xsl:text>
       
  2182       </xsl:for-each>
       
  2183     </xsl:for-each>
       
  2184     <xsl:text>}
       
  2185 </xsl:text>
       
  2186     <xsl:text>
       
  2187 </xsl:text>
       
  2188     <xsl:text>var default_page = "</xsl:text>
       
  2189     <xsl:value-of select="$default_page"/>
       
  2190     <xsl:text>";
       
  2191 </xsl:text>
       
  2192     <xsl:text>var svg_root = id("</xsl:text>
       
  2193     <xsl:value-of select="/svg:svg/@id"/>
       
  2194     <xsl:text>");
       
  2195 </xsl:text>
       
  2196     <xsl:text>// svghmi.js
       
  2197 </xsl:text>
       
  2198     <xsl:text>
       
  2199 </xsl:text>
       
  2200     <xsl:text>var cache = hmitree_types.map(_ignored =&gt; undefined);
       
  2201 </xsl:text>
       
  2202     <xsl:text>var updates = {};
       
  2203 </xsl:text>
       
  2204     <xsl:text>var need_cache_apply = []; 
       
  2205 </xsl:text>
       
  2206     <xsl:text>var jumps_need_update = false;
       
  2207 </xsl:text>
       
  2208     <xsl:text>var jump_history = [[default_page, undefined]];
       
  2209 </xsl:text>
       
  2210     <xsl:text>
       
  2211 </xsl:text>
       
  2212     <xsl:text>function dispatch_value_to_widget(widget, index, value, oldval) {
       
  2213 </xsl:text>
       
  2214     <xsl:text>    try {
       
  2215 </xsl:text>
       
  2216     <xsl:text>        let idx = widget.offset ? index - widget.offset : index;
       
  2217 </xsl:text>
       
  2218     <xsl:text>        let idxidx = widget.indexes.indexOf(idx);
       
  2219 </xsl:text>
       
  2220     <xsl:text>        let d = widget.dispatch;
       
  2221 </xsl:text>
       
  2222     <xsl:text>        if(typeof(d) == "function" &amp;&amp; idxidx == 0){
       
  2223 </xsl:text>
       
  2224     <xsl:text>            d.call(widget, value, oldval);
       
  2225 </xsl:text>
       
  2226     <xsl:text>        }
       
  2227 </xsl:text>
       
  2228     <xsl:text>        else if(typeof(d) == "object" &amp;&amp; d.length &gt;= idxidx){
       
  2229 </xsl:text>
       
  2230     <xsl:text>            d[idxidx].call(widget, value, oldval);
       
  2231 </xsl:text>
       
  2232     <xsl:text>        }
       
  2233 </xsl:text>
       
  2234     <xsl:text>        /* else dispatch_0, ..., dispatch_n ? */
       
  2235 </xsl:text>
       
  2236     <xsl:text>        /*else {
       
  2237 </xsl:text>
       
  2238     <xsl:text>            throw new Error("Dunno how to dispatch to widget at index = " + index);
       
  2239 </xsl:text>
       
  2240     <xsl:text>        }*/
       
  2241 </xsl:text>
       
  2242     <xsl:text>    } catch(err) {
       
  2243 </xsl:text>
       
  2244     <xsl:text>        console.log(err);
       
  2245 </xsl:text>
       
  2246     <xsl:text>    }
       
  2247 </xsl:text>
       
  2248     <xsl:text>}
       
  2249 </xsl:text>
       
  2250     <xsl:text>
       
  2251 </xsl:text>
       
  2252     <xsl:text>function dispatch_value(index, value) {
       
  2253 </xsl:text>
       
  2254     <xsl:text>    let widgets = subscribers[index];
       
  2255 </xsl:text>
       
  2256     <xsl:text>
       
  2257 </xsl:text>
       
  2258     <xsl:text>    let oldval = cache[index];
       
  2259 </xsl:text>
       
  2260     <xsl:text>    cache[index] = value;
       
  2261 </xsl:text>
       
  2262     <xsl:text>
       
  2263 </xsl:text>
       
  2264     <xsl:text>    if(widgets.size &gt; 0) {
       
  2265 </xsl:text>
       
  2266     <xsl:text>        for(let widget of widgets){
       
  2267 </xsl:text>
       
  2268     <xsl:text>            dispatch_value_to_widget(widget, index, value, oldval);
       
  2269 </xsl:text>
       
  2270     <xsl:text>        }
       
  2271 </xsl:text>
       
  2272     <xsl:text>    }
       
  2273 </xsl:text>
       
  2274     <xsl:text>};
       
  2275 </xsl:text>
       
  2276     <xsl:text>
       
  2277 </xsl:text>
       
  2278     <xsl:text>function init_widgets() {
       
  2279 </xsl:text>
       
  2280     <xsl:text>    Object.keys(hmi_widgets).forEach(function(id) {
       
  2281 </xsl:text>
       
  2282     <xsl:text>        let widget = hmi_widgets[id];
       
  2283 </xsl:text>
       
  2284     <xsl:text>        let init = widget.init;
       
  2285 </xsl:text>
       
  2286     <xsl:text>        if(typeof(init) == "function"){
       
  2287 </xsl:text>
       
  2288     <xsl:text>            try {
       
  2289 </xsl:text>
       
  2290     <xsl:text>                init.call(widget);
       
  2291 </xsl:text>
       
  2292     <xsl:text>            } catch(err) {
       
  2293 </xsl:text>
       
  2294     <xsl:text>                console.log(err);
       
  2295 </xsl:text>
       
  2296     <xsl:text>            }
       
  2297 </xsl:text>
       
  2298     <xsl:text>        }
       
  2299 </xsl:text>
       
  2300     <xsl:text>    });
       
  2301 </xsl:text>
       
  2302     <xsl:text>};
       
  2303 </xsl:text>
       
  2304     <xsl:text>
       
  2305 </xsl:text>
       
  2306     <xsl:text>// Open WebSocket to relative "/ws" address
       
  2307 </xsl:text>
       
  2308     <xsl:text>var ws = new WebSocket(window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws'));
       
  2309 </xsl:text>
       
  2310     <xsl:text>ws.binaryType = 'arraybuffer';
       
  2311 </xsl:text>
       
  2312     <xsl:text>
       
  2313 </xsl:text>
       
  2314     <xsl:text>const dvgetters = {
       
  2315 </xsl:text>
       
  2316     <xsl:text>    INT: (dv,offset) =&gt; [dv.getInt16(offset, true), 2],
       
  2317 </xsl:text>
       
  2318     <xsl:text>    BOOL: (dv,offset) =&gt; [dv.getInt8(offset, true), 1],
       
  2319 </xsl:text>
       
  2320     <xsl:text>    NODE: (dv,offset) =&gt; [dv.getInt8(offset, true), 1],
       
  2321 </xsl:text>
       
  2322     <xsl:text>    STRING: (dv, offset) =&gt; {
       
  2323 </xsl:text>
       
  2324     <xsl:text>        size = dv.getInt8(offset);
       
  2325 </xsl:text>
       
  2326     <xsl:text>        return [
       
  2327 </xsl:text>
       
  2328     <xsl:text>            String.fromCharCode.apply(null, new Uint8Array(
       
  2329 </xsl:text>
       
  2330     <xsl:text>                dv.buffer, /* original buffer */
       
  2331 </xsl:text>
       
  2332     <xsl:text>                offset + 1, /* string starts after size*/
       
  2333 </xsl:text>
       
  2334     <xsl:text>                size /* size of string */
       
  2335 </xsl:text>
       
  2336     <xsl:text>            )), size + 1]; /* total increment */
       
  2337 </xsl:text>
       
  2338     <xsl:text>    }
       
  2339 </xsl:text>
       
  2340     <xsl:text>};
       
  2341 </xsl:text>
       
  2342     <xsl:text>
       
  2343 </xsl:text>
       
  2344     <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets
       
  2345 </xsl:text>
       
  2346     <xsl:text>function apply_updates() {
       
  2347 </xsl:text>
       
  2348     <xsl:text>    for(let index in updates){
       
  2349 </xsl:text>
       
  2350     <xsl:text>        // serving as a key, index becomes a string
       
  2351 </xsl:text>
       
  2352     <xsl:text>        // -&gt; pass Number(index) instead
       
  2353 </xsl:text>
       
  2354     <xsl:text>        dispatch_value(Number(index), updates[index]);
       
  2355 </xsl:text>
       
  2356     <xsl:text>        delete updates[index];
       
  2357 </xsl:text>
       
  2358     <xsl:text>    }
       
  2359 </xsl:text>
       
  2360     <xsl:text>}
       
  2361 </xsl:text>
       
  2362     <xsl:text>
       
  2363 </xsl:text>
       
  2364     <xsl:text>// Called on requestAnimationFrame, modifies DOM
       
  2365 </xsl:text>
       
  2366     <xsl:text>var requestAnimationFrameID = null;
       
  2367 </xsl:text>
       
  2368     <xsl:text>function animate() {
       
  2369 </xsl:text>
       
  2370     <xsl:text>    // Do the page swith if any one pending
       
  2371 </xsl:text>
       
  2372     <xsl:text>    if(current_subscribed_page != current_visible_page){
       
  2373 </xsl:text>
       
  2374     <xsl:text>        switch_visible_page(current_subscribed_page);
       
  2375 </xsl:text>
       
  2376     <xsl:text>    }
       
  2377 </xsl:text>
       
  2378     <xsl:text>
       
  2379 </xsl:text>
       
  2380     <xsl:text>    while(widget = need_cache_apply.pop()){
       
  2381 </xsl:text>
       
  2382     <xsl:text>        widget.apply_cache();
       
  2383 </xsl:text>
       
  2384     <xsl:text>    }
       
  2385 </xsl:text>
       
  2386     <xsl:text>
       
  2387 </xsl:text>
       
  2388     <xsl:text>    if(jumps_need_update) update_jumps();
       
  2389 </xsl:text>
       
  2390     <xsl:text>
       
  2391 </xsl:text>
       
  2392     <xsl:text>    apply_updates();
       
  2393 </xsl:text>
       
  2394     <xsl:text>    requestAnimationFrameID = null;
       
  2395 </xsl:text>
       
  2396     <xsl:text>}
       
  2397 </xsl:text>
       
  2398     <xsl:text>
       
  2399 </xsl:text>
       
  2400     <xsl:text>function requestHMIAnimation() {
       
  2401 </xsl:text>
       
  2402     <xsl:text>    if(requestAnimationFrameID == null){
       
  2403 </xsl:text>
       
  2404     <xsl:text>        requestAnimationFrameID = window.requestAnimationFrame(animate);
       
  2405 </xsl:text>
       
  2406     <xsl:text>    }
       
  2407 </xsl:text>
       
  2408     <xsl:text>}
       
  2409 </xsl:text>
       
  2410     <xsl:text>
       
  2411 </xsl:text>
       
  2412     <xsl:text>// Message reception handler
       
  2413 </xsl:text>
       
  2414     <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing
       
  2415 </xsl:text>
       
  2416     <xsl:text>// are stored until browser can compute next frame, DOM is left untouched
       
  2417 </xsl:text>
       
  2418     <xsl:text>ws.onmessage = function (evt) {
       
  2419 </xsl:text>
       
  2420     <xsl:text>
       
  2421 </xsl:text>
       
  2422     <xsl:text>    let data = evt.data;
       
  2423 </xsl:text>
       
  2424     <xsl:text>    let dv = new DataView(data);
       
  2425 </xsl:text>
       
  2426     <xsl:text>    let i = 0;
       
  2427 </xsl:text>
       
  2428     <xsl:text>    try {
       
  2429 </xsl:text>
       
  2430     <xsl:text>        for(let hash_int of hmi_hash) {
       
  2431 </xsl:text>
       
  2432     <xsl:text>            if(hash_int != dv.getUint8(i)){
       
  2433 </xsl:text>
       
  2434     <xsl:text>                throw new Error("Hash doesn't match");
       
  2435 </xsl:text>
       
  2436     <xsl:text>            };
       
  2437 </xsl:text>
       
  2438     <xsl:text>            i++;
       
  2439 </xsl:text>
       
  2440     <xsl:text>        };
       
  2441 </xsl:text>
       
  2442     <xsl:text>
       
  2443 </xsl:text>
       
  2444     <xsl:text>        while(i &lt; data.byteLength){
       
  2445 </xsl:text>
       
  2446     <xsl:text>            let index = dv.getUint32(i, true);
       
  2447 </xsl:text>
       
  2448     <xsl:text>            i += 4;
       
  2449 </xsl:text>
       
  2450     <xsl:text>            let iectype = hmitree_types[index];
       
  2451 </xsl:text>
       
  2452     <xsl:text>            if(iectype != undefined){
       
  2453 </xsl:text>
       
  2454     <xsl:text>                let dvgetter = dvgetters[iectype];
       
  2455 </xsl:text>
       
  2456     <xsl:text>                let [value, bytesize] = dvgetter(dv,i);
       
  2457 </xsl:text>
       
  2458     <xsl:text>                updates[index] = value;
       
  2459 </xsl:text>
       
  2460     <xsl:text>                i += bytesize;
       
  2461 </xsl:text>
       
  2462     <xsl:text>            } else {
       
  2463 </xsl:text>
       
  2464     <xsl:text>                throw new Error("Unknown index "+index);
       
  2465 </xsl:text>
       
  2466     <xsl:text>            }
       
  2467 </xsl:text>
       
  2468     <xsl:text>        };
       
  2469 </xsl:text>
       
  2470     <xsl:text>        // register for rendering on next frame, since there are updates
       
  2471 </xsl:text>
       
  2472     <xsl:text>        requestHMIAnimation();
       
  2473 </xsl:text>
       
  2474     <xsl:text>    } catch(err) {
       
  2475 </xsl:text>
       
  2476     <xsl:text>        // 1003 is for "Unsupported Data"
       
  2477 </xsl:text>
       
  2478     <xsl:text>        // ws.close(1003, err.message);
       
  2479 </xsl:text>
       
  2480     <xsl:text>
       
  2481 </xsl:text>
       
  2482     <xsl:text>        // TODO : remove debug alert ?
       
  2483 </xsl:text>
       
  2484     <xsl:text>        alert("Error : "+err.message+"\nHMI will be reloaded.");
       
  2485 </xsl:text>
       
  2486     <xsl:text>
       
  2487 </xsl:text>
       
  2488     <xsl:text>        // force reload ignoring cache
       
  2489 </xsl:text>
       
  2490     <xsl:text>        location.reload(true);
       
  2491 </xsl:text>
       
  2492     <xsl:text>    }
       
  2493 </xsl:text>
       
  2494     <xsl:text>};
       
  2495 </xsl:text>
       
  2496     <xsl:text>
       
  2497 </xsl:text>
       
  2498     <xsl:text>
       
  2499 </xsl:text>
       
  2500     <xsl:text>function send_blob(data) {
       
  2501 </xsl:text>
       
  2502     <xsl:text>    if(data.length &gt; 0) {
       
  2503 </xsl:text>
       
  2504     <xsl:text>        ws.send(new Blob([new Uint8Array(hmi_hash)].concat(data)));
       
  2505 </xsl:text>
       
  2506     <xsl:text>    };
       
  2507 </xsl:text>
       
  2508     <xsl:text>};
       
  2509 </xsl:text>
       
  2510     <xsl:text>
       
  2511 </xsl:text>
       
  2512     <xsl:text>const typedarray_types = {
       
  2513 </xsl:text>
       
  2514     <xsl:text>    INT: (number) =&gt; new Int16Array([number]),
       
  2515 </xsl:text>
       
  2516     <xsl:text>    BOOL: (truth) =&gt; new Int16Array([truth]),
       
  2517 </xsl:text>
       
  2518     <xsl:text>    NODE: (truth) =&gt; new Int16Array([truth]),
       
  2519 </xsl:text>
       
  2520     <xsl:text>    STRING: (str) =&gt; {
       
  2521 </xsl:text>
       
  2522     <xsl:text>        // beremiz default string max size is 128
       
  2523 </xsl:text>
       
  2524     <xsl:text>        str = str.slice(0,128);
       
  2525 </xsl:text>
       
  2526     <xsl:text>        binary = new Uint8Array(str.length + 1);
       
  2527 </xsl:text>
       
  2528     <xsl:text>        binary[0] = str.length;
       
  2529 </xsl:text>
       
  2530     <xsl:text>        for(var i = 0; i &lt; str.length; i++){
       
  2531 </xsl:text>
       
  2532     <xsl:text>            binary[i+1] = str.charCodeAt(i);
       
  2533 </xsl:text>
       
  2534     <xsl:text>        }
       
  2535 </xsl:text>
       
  2536     <xsl:text>        return binary;
       
  2537 </xsl:text>
       
  2538     <xsl:text>    }
       
  2539 </xsl:text>
       
  2540     <xsl:text>    /* TODO */
       
  2541 </xsl:text>
       
  2542     <xsl:text>};
       
  2543 </xsl:text>
       
  2544     <xsl:text>
       
  2545 </xsl:text>
       
  2546     <xsl:text>function send_reset() {
       
  2547 </xsl:text>
       
  2548     <xsl:text>    send_blob(new Uint8Array([1])); /* reset = 1 */
       
  2549 </xsl:text>
       
  2550     <xsl:text>};
       
  2551 </xsl:text>
       
  2552     <xsl:text>
       
  2553 </xsl:text>
       
  2554     <xsl:text>// subscription state, as it should be in hmi server
       
  2555 </xsl:text>
       
  2556     <xsl:text>// hmitree indexed array of integers
       
  2557 </xsl:text>
       
  2558     <xsl:text>var subscriptions =  hmitree_types.map(_ignored =&gt; 0);
       
  2559 </xsl:text>
       
  2560     <xsl:text>
       
  2561 </xsl:text>
       
  2562     <xsl:text>// subscription state as needed by widget now
       
  2563 </xsl:text>
       
  2564     <xsl:text>// hmitree indexed array of Sets of widgets objects
       
  2565 </xsl:text>
       
  2566     <xsl:text>var subscribers = hmitree_types.map(_ignored =&gt; new Set());
       
  2567 </xsl:text>
       
  2568     <xsl:text>
       
  2569 </xsl:text>
       
  2570     <xsl:text>// artificially subscribe the watchdog widget to "/heartbeat" hmi variable
       
  2571 </xsl:text>
       
  2572     <xsl:text>// Since dispatch directly calls change_hmi_value,
       
  2573 </xsl:text>
       
  2574     <xsl:text>// PLC will periodically send variable at given frequency
       
  2575 </xsl:text>
       
  2576     <xsl:text>subscribers[heartbeat_index].add({
       
  2577 </xsl:text>
       
  2578     <xsl:text>    /* type: "Watchdog", */
       
  2579 </xsl:text>
       
  2580     <xsl:text>    frequency: 1,
       
  2581 </xsl:text>
       
  2582     <xsl:text>    indexes: [heartbeat_index],
       
  2583 </xsl:text>
       
  2584     <xsl:text>    dispatch: function(value) {
       
  2585 </xsl:text>
       
  2586     <xsl:text>        change_hmi_value(heartbeat_index, "+1");
       
  2587 </xsl:text>
       
  2588     <xsl:text>    }
       
  2589 </xsl:text>
       
  2590     <xsl:text>});
       
  2591 </xsl:text>
       
  2592     <xsl:text>
       
  2593 </xsl:text>
       
  2594     <xsl:text>function update_subscriptions() {
       
  2595 </xsl:text>
       
  2596     <xsl:text>    let delta = [];
       
  2597 </xsl:text>
       
  2598     <xsl:text>    for(let index = 0; index &lt; subscribers.length; index++){
       
  2599 </xsl:text>
       
  2600     <xsl:text>        let widgets = subscribers[index];
       
  2601 </xsl:text>
       
  2602     <xsl:text>
       
  2603 </xsl:text>
       
  2604     <xsl:text>        // periods are in ms
       
  2605 </xsl:text>
       
  2606     <xsl:text>        let previous_period = subscriptions[index];
       
  2607 </xsl:text>
       
  2608     <xsl:text>
       
  2609 </xsl:text>
       
  2610     <xsl:text>        // subscribing with a zero period is unsubscribing
       
  2611 </xsl:text>
       
  2612     <xsl:text>        let new_period = 0;
       
  2613 </xsl:text>
       
  2614     <xsl:text>        if(widgets.size &gt; 0) {
       
  2615 </xsl:text>
       
  2616     <xsl:text>            let maxfreq = 0;
       
  2617 </xsl:text>
       
  2618     <xsl:text>            for(let widget of widgets)
       
  2619 </xsl:text>
       
  2620     <xsl:text>                if(maxfreq &lt; widget.frequency)
       
  2621 </xsl:text>
       
  2622     <xsl:text>                    maxfreq = widget.frequency;
       
  2623 </xsl:text>
       
  2624     <xsl:text>
       
  2625 </xsl:text>
       
  2626     <xsl:text>            if(maxfreq != 0)
       
  2627 </xsl:text>
       
  2628     <xsl:text>                new_period = 1000/maxfreq;
       
  2629 </xsl:text>
       
  2630     <xsl:text>        }
       
  2631 </xsl:text>
       
  2632     <xsl:text>
       
  2633 </xsl:text>
       
  2634     <xsl:text>        if(previous_period != new_period) {
       
  2635 </xsl:text>
       
  2636     <xsl:text>            subscriptions[index] = new_period;
       
  2637 </xsl:text>
       
  2638     <xsl:text>            delta.push(
       
  2639 </xsl:text>
       
  2640     <xsl:text>                new Uint8Array([2]), /* subscribe = 2 */
       
  2641 </xsl:text>
       
  2642     <xsl:text>                new Uint32Array([index]),
       
  2643 </xsl:text>
       
  2644     <xsl:text>                new Uint16Array([new_period]));
       
  2645 </xsl:text>
       
  2646     <xsl:text>        }
       
  2647 </xsl:text>
       
  2648     <xsl:text>    }
       
  2649 </xsl:text>
       
  2650     <xsl:text>    send_blob(delta);
       
  2651 </xsl:text>
       
  2652     <xsl:text>};
       
  2653 </xsl:text>
       
  2654     <xsl:text>
       
  2655 </xsl:text>
       
  2656     <xsl:text>function send_hmi_value(index, value) {
       
  2657 </xsl:text>
       
  2658     <xsl:text>    let iectype = hmitree_types[index];
       
  2659 </xsl:text>
       
  2660     <xsl:text>    let tobinary = typedarray_types[iectype];
       
  2661 </xsl:text>
       
  2662     <xsl:text>    send_blob([
       
  2663 </xsl:text>
       
  2664     <xsl:text>        new Uint8Array([0]),  /* setval = 0 */
       
  2665 </xsl:text>
       
  2666     <xsl:text>        new Uint32Array([index]),
       
  2667 </xsl:text>
       
  2668     <xsl:text>        tobinary(value)]);
       
  2669 </xsl:text>
       
  2670     <xsl:text>
       
  2671 </xsl:text>
       
  2672     <xsl:text>    // DON'T DO THAT unless read_iterator in svghmi.c modifies wbuf as well, not only rbuf
       
  2673 </xsl:text>
       
  2674     <xsl:text>    // cache[index] = value;
       
  2675 </xsl:text>
       
  2676     <xsl:text>};
       
  2677 </xsl:text>
       
  2678     <xsl:text>
       
  2679 </xsl:text>
       
  2680     <xsl:text>function apply_hmi_value(index, new_val) {
       
  2681 </xsl:text>
       
  2682     <xsl:text>    let old_val = cache[index]
       
  2683 </xsl:text>
       
  2684     <xsl:text>    if(new_val != undefined &amp;&amp; old_val != new_val)
       
  2685 </xsl:text>
       
  2686     <xsl:text>        send_hmi_value(index, new_val);
       
  2687 </xsl:text>
       
  2688     <xsl:text>    return new_val;
       
  2689 </xsl:text>
       
  2690     <xsl:text>}
       
  2691 </xsl:text>
       
  2692     <xsl:text>
       
  2693 </xsl:text>
       
  2694     <xsl:text>function change_hmi_value(index, opstr) {
       
  2695 </xsl:text>
       
  2696     <xsl:text>    let op = opstr[0];
       
  2697 </xsl:text>
       
  2698     <xsl:text>    let given_val = opstr.slice(1);
       
  2699 </xsl:text>
       
  2700     <xsl:text>    let old_val = cache[index]
       
  2701 </xsl:text>
       
  2702     <xsl:text>    let new_val;
       
  2703 </xsl:text>
       
  2704     <xsl:text>    switch(op){
       
  2705 </xsl:text>
       
  2706     <xsl:text>      case "=":
       
  2707 </xsl:text>
       
  2708     <xsl:text>        eval("new_val"+opstr);
       
  2709 </xsl:text>
       
  2710     <xsl:text>        break;
       
  2711 </xsl:text>
       
  2712     <xsl:text>      case "+":
       
  2713 </xsl:text>
       
  2714     <xsl:text>      case "-":
       
  2715 </xsl:text>
       
  2716     <xsl:text>      case "*":
       
  2717 </xsl:text>
       
  2718     <xsl:text>      case "/":
       
  2719 </xsl:text>
       
  2720     <xsl:text>        if(old_val != undefined)
       
  2721 </xsl:text>
       
  2722     <xsl:text>            new_val = eval("old_val"+opstr);
       
  2723 </xsl:text>
       
  2724     <xsl:text>        break;
       
  2725 </xsl:text>
       
  2726     <xsl:text>    }
       
  2727 </xsl:text>
       
  2728     <xsl:text>    if(new_val != undefined &amp;&amp; old_val != new_val)
       
  2729 </xsl:text>
       
  2730     <xsl:text>        send_hmi_value(index, new_val);
       
  2731 </xsl:text>
       
  2732     <xsl:text>    return new_val;
       
  2733 </xsl:text>
       
  2734     <xsl:text>}
       
  2735 </xsl:text>
       
  2736     <xsl:text>
       
  2737 </xsl:text>
       
  2738     <xsl:text>var current_visible_page;
       
  2739 </xsl:text>
       
  2740     <xsl:text>var current_subscribed_page;
       
  2741 </xsl:text>
       
  2742     <xsl:text>var current_page_index;
       
  2743 </xsl:text>
       
  2744     <xsl:text>
       
  2745 </xsl:text>
       
  2746     <xsl:text>function prepare_svg() {
       
  2747 </xsl:text>
       
  2748     <xsl:text>    for(let eltid in detachable_elements){
       
  2749 </xsl:text>
       
  2750     <xsl:text>        let [element,parent] = detachable_elements[eltid];
       
  2751 </xsl:text>
       
  2752     <xsl:text>        parent.removeChild(element);
       
  2753 </xsl:text>
       
  2754     <xsl:text>    }
       
  2755 </xsl:text>
       
  2756     <xsl:text>};
       
  2757 </xsl:text>
       
  2758     <xsl:text>
       
  2759 </xsl:text>
       
  2760     <xsl:text>function switch_page(page_name, page_index) {
       
  2761 </xsl:text>
       
  2762     <xsl:text>    if(current_subscribed_page != current_visible_page){
       
  2763 </xsl:text>
       
  2764     <xsl:text>        /* page switch already going */
       
  2765 </xsl:text>
       
  2766     <xsl:text>        /* TODO LOG ERROR */
       
  2767 </xsl:text>
       
  2768     <xsl:text>        return false;
       
  2769 </xsl:text>
       
  2770     <xsl:text>    }
       
  2771 </xsl:text>
       
  2772     <xsl:text>
       
  2773 </xsl:text>
       
  2774     <xsl:text>    if(page_name == undefined)
       
  2775 </xsl:text>
       
  2776     <xsl:text>        page_name = current_subscribed_page;
       
  2777 </xsl:text>
       
  2778     <xsl:text>
       
  2779 </xsl:text>
       
  2780     <xsl:text>
       
  2781 </xsl:text>
       
  2782     <xsl:text>    let old_desc = page_desc[current_subscribed_page];
       
  2783 </xsl:text>
       
  2784     <xsl:text>    let new_desc = page_desc[page_name];
       
  2785 </xsl:text>
       
  2786     <xsl:text>
       
  2787 </xsl:text>
       
  2788     <xsl:text>    if(new_desc == undefined){
       
  2789 </xsl:text>
       
  2790     <xsl:text>        /* TODO LOG ERROR */
       
  2791 </xsl:text>
       
  2792     <xsl:text>        return false;
       
  2793 </xsl:text>
       
  2794     <xsl:text>    }
       
  2795 </xsl:text>
       
  2796     <xsl:text>
       
  2797 </xsl:text>
       
  2798     <xsl:text>    if(page_index == undefined){
       
  2799 </xsl:text>
       
  2800     <xsl:text>        page_index = new_desc.page_index;
       
  2801 </xsl:text>
       
  2802     <xsl:text>    }
       
  2803 </xsl:text>
       
  2804     <xsl:text>
       
  2805 </xsl:text>
       
  2806     <xsl:text>    if(old_desc){
       
  2807 </xsl:text>
       
  2808     <xsl:text>        old_desc.absolute_widgets.map(w=&gt;w.unsub());
       
  2809 </xsl:text>
       
  2810     <xsl:text>        old_desc.relative_widgets.map(w=&gt;w.unsub());
       
  2811 </xsl:text>
       
  2812     <xsl:text>    }
       
  2813 </xsl:text>
       
  2814     <xsl:text>    new_desc.absolute_widgets.map(w=&gt;w.sub());
       
  2815 </xsl:text>
       
  2816     <xsl:text>    var new_offset = page_index == undefined ? 0 : page_index - new_desc.page_index;
       
  2817 </xsl:text>
       
  2818     <xsl:text>    new_desc.relative_widgets.map(w=&gt;w.sub(new_offset));
       
  2819 </xsl:text>
       
  2820     <xsl:text>
       
  2821 </xsl:text>
       
  2822     <xsl:text>    update_subscriptions();
       
  2823 </xsl:text>
       
  2824     <xsl:text>
       
  2825 </xsl:text>
       
  2826     <xsl:text>    current_subscribed_page = page_name;
       
  2827 </xsl:text>
       
  2828     <xsl:text>    current_page_index = page_index;
       
  2829 </xsl:text>
       
  2830     <xsl:text>
       
  2831 </xsl:text>
       
  2832     <xsl:text>    jumps_need_update = true;
       
  2833 </xsl:text>
       
  2834     <xsl:text>
       
  2835 </xsl:text>
       
  2836     <xsl:text>    requestHMIAnimation();
       
  2837 </xsl:text>
       
  2838     <xsl:text>
       
  2839 </xsl:text>
       
  2840     <xsl:text>    jump_history.push([page_name, page_index]);
       
  2841 </xsl:text>
       
  2842     <xsl:text>    if(jump_history.length &gt; 42)
       
  2843 </xsl:text>
       
  2844     <xsl:text>        jump_history.shift();
       
  2845 </xsl:text>
       
  2846     <xsl:text>
       
  2847 </xsl:text>
       
  2848     <xsl:text>    return true;
       
  2849 </xsl:text>
       
  2850     <xsl:text>};
       
  2851 </xsl:text>
       
  2852     <xsl:text>
       
  2853 </xsl:text>
       
  2854     <xsl:text>function* chain(a,b){
       
  2855 </xsl:text>
       
  2856     <xsl:text>    yield* a;
       
  2857 </xsl:text>
       
  2858     <xsl:text>    yield* b;
       
  2859 </xsl:text>
       
  2860     <xsl:text>};
       
  2861 </xsl:text>
       
  2862     <xsl:text>
       
  2863 </xsl:text>
       
  2864     <xsl:text>function unsubscribe(){
       
  2865 </xsl:text>
       
  2866     <xsl:text>    /* remove subsribers */
       
  2867 </xsl:text>
       
  2868     <xsl:text>    for(let index of this.indexes){
       
  2869 </xsl:text>
       
  2870     <xsl:text>        let idx = index + this.offset;
       
  2871 </xsl:text>
       
  2872     <xsl:text>        subscribers[idx].delete(this);
       
  2873 </xsl:text>
       
  2874     <xsl:text>    }
       
  2875 </xsl:text>
       
  2876     <xsl:text>    this.offset = 0;
       
  2877 </xsl:text>
       
  2878     <xsl:text>}
       
  2879 </xsl:text>
       
  2880     <xsl:text>
       
  2881 </xsl:text>
       
  2882     <xsl:text>function subscribe(new_offset=0){
       
  2883 </xsl:text>
       
  2884     <xsl:text>    /* set the offset because relative */
       
  2885 </xsl:text>
       
  2886     <xsl:text>    this.offset = new_offset;
       
  2887 </xsl:text>
       
  2888     <xsl:text>    /* add this's subsribers */
       
  2889 </xsl:text>
       
  2890     <xsl:text>    for(let index of this.indexes){
       
  2891 </xsl:text>
       
  2892     <xsl:text>        subscribers[index + new_offset].add(this);
       
  2893 </xsl:text>
       
  2894     <xsl:text>    }
       
  2895 </xsl:text>
       
  2896     <xsl:text>    need_cache_apply.push(this); 
       
  2897 </xsl:text>
       
  2898     <xsl:text>}
       
  2899 </xsl:text>
       
  2900     <xsl:text>
       
  2901 </xsl:text>
       
  2902     <xsl:text>function foreach_unsubscribe(){
       
  2903 </xsl:text>
       
  2904     <xsl:text>    for(let item of this.items){
       
  2905 </xsl:text>
       
  2906     <xsl:text>        for(let widget of item) {
       
  2907 </xsl:text>
       
  2908     <xsl:text>            unsubscribe.call(widget);
       
  2909 </xsl:text>
       
  2910     <xsl:text>        }
       
  2911 </xsl:text>
       
  2912     <xsl:text>    }
       
  2913 </xsl:text>
       
  2914     <xsl:text>    this.offset = 0;
       
  2915 </xsl:text>
       
  2916     <xsl:text>}
       
  2917 </xsl:text>
       
  2918     <xsl:text>
       
  2919 </xsl:text>
       
  2920     <xsl:text>function foreach_widgets_do(new_offset, todo){
       
  2921 </xsl:text>
       
  2922     <xsl:text>    this.offset = new_offset;
       
  2923 </xsl:text>
       
  2924     <xsl:text>    for(let i = 0; i &lt; this.items.length; i++) {
       
  2925 </xsl:text>
       
  2926     <xsl:text>        let item = this.items[i];
       
  2927 </xsl:text>
       
  2928     <xsl:text>        let orig_item_index = this.index_pool[i];
       
  2929 </xsl:text>
       
  2930     <xsl:text>        let item_index = this.index_pool[i+this.item_offset];
       
  2931 </xsl:text>
       
  2932     <xsl:text>        let item_index_offset = item_index - orig_item_index;
       
  2933 </xsl:text>
       
  2934     <xsl:text>        for(let widget of item) {
       
  2935 </xsl:text>
       
  2936     <xsl:text>            todo.call(widget, new_offset + item_index_offset);
       
  2937 </xsl:text>
       
  2938     <xsl:text>        }
       
  2939 </xsl:text>
       
  2940     <xsl:text>    }
       
  2941 </xsl:text>
       
  2942     <xsl:text>}
       
  2943 </xsl:text>
       
  2944     <xsl:text>
       
  2945 </xsl:text>
       
  2946     <xsl:text>function foreach_subscribe(new_offset=0){
       
  2947 </xsl:text>
       
  2948     <xsl:text>    foreach_widgets_do.call(this, new_offset, subscribe);
       
  2949 </xsl:text>
       
  2950     <xsl:text>}
       
  2951 </xsl:text>
       
  2952     <xsl:text>
       
  2953 </xsl:text>
       
  2954     <xsl:text>function widget_apply_cache() {
       
  2955 </xsl:text>
       
  2956     <xsl:text>    for(let index of this.indexes){
       
  2957 </xsl:text>
       
  2958     <xsl:text>        /* dispatch current cache in newly opened page widgets */
       
  2959 </xsl:text>
       
  2960     <xsl:text>        let realindex = index+this.offset;
       
  2961 </xsl:text>
       
  2962     <xsl:text>        let cached_val = cache[realindex];
       
  2963 </xsl:text>
       
  2964     <xsl:text>        if(cached_val != undefined)
       
  2965 </xsl:text>
       
  2966     <xsl:text>            dispatch_value_to_widget(this, realindex, cached_val, cached_val);
       
  2967 </xsl:text>
       
  2968     <xsl:text>    }
       
  2969 </xsl:text>
       
  2970     <xsl:text>}
       
  2971 </xsl:text>
       
  2972     <xsl:text>
       
  2973 </xsl:text>
       
  2974     <xsl:text>function foreach_apply_cache() {
       
  2975 </xsl:text>
       
  2976     <xsl:text>    foreach_widgets_do.call(this, this.offset, widget_apply_cache);
       
  2977 </xsl:text>
       
  2978     <xsl:text>}
       
  2979 </xsl:text>
       
  2980     <xsl:text>
       
  2981 </xsl:text>
       
  2982     <xsl:text>function foreach_onclick(opstr, evt) {
       
  2983 </xsl:text>
       
  2984     <xsl:text>    new_item_offset = eval(String(this.item_offset)+opstr)
       
  2985 </xsl:text>
       
  2986     <xsl:text>    if(new_item_offset + this.items.length &gt; this.index_pool.length) {
       
  2987 </xsl:text>
       
  2988     <xsl:text>        if(this.item_offset + this.items.length == this.index_pool.length)
       
  2989 </xsl:text>
       
  2990     <xsl:text>            new_item_offset = 0;
       
  2991 </xsl:text>
       
  2992     <xsl:text>        else
       
  2993 </xsl:text>
       
  2994     <xsl:text>            new_item_offset = this.index_pool.length - this.items.length;
       
  2995 </xsl:text>
       
  2996     <xsl:text>    } else if(new_item_offset &lt; 0) {
       
  2997 </xsl:text>
       
  2998     <xsl:text>        if(this.item_offset == 0)
       
  2999 </xsl:text>
       
  3000     <xsl:text>            new_item_offset = this.index_pool.length - this.items.length;
       
  3001 </xsl:text>
       
  3002     <xsl:text>        else
       
  3003 </xsl:text>
       
  3004     <xsl:text>            new_item_offset = 0;
       
  3005 </xsl:text>
       
  3006     <xsl:text>    }
       
  3007 </xsl:text>
       
  3008     <xsl:text>    this.item_offset = new_item_offset;
       
  3009 </xsl:text>
       
  3010     <xsl:text>    off = this.offset;
       
  3011 </xsl:text>
       
  3012     <xsl:text>    foreach_unsubscribe.call(this);
       
  3013 </xsl:text>
       
  3014     <xsl:text>    foreach_subscribe.call(this,off);
       
  3015 </xsl:text>
       
  3016     <xsl:text>    update_subscriptions();
       
  3017 </xsl:text>
       
  3018     <xsl:text>    need_cache_apply.push(this);
       
  3019 </xsl:text>
       
  3020     <xsl:text>    jumps_need_update = true;
       
  3021 </xsl:text>
       
  3022     <xsl:text>    requestHMIAnimation();
       
  3023 </xsl:text>
       
  3024     <xsl:text>}
       
  3025 </xsl:text>
       
  3026     <xsl:text>
       
  3027 </xsl:text>
       
  3028     <xsl:text>
       
  3029 </xsl:text>
       
  3030     <xsl:text>function switch_visible_page(page_name) {
       
  3031 </xsl:text>
       
  3032     <xsl:text>
       
  3033 </xsl:text>
       
  3034     <xsl:text>    let old_desc = page_desc[current_visible_page];
       
  3035 </xsl:text>
       
  3036     <xsl:text>    let new_desc = page_desc[page_name];
       
  3037 </xsl:text>
       
  3038     <xsl:text>
       
  3039 </xsl:text>
       
  3040     <xsl:text>    if(old_desc){
       
  3041 </xsl:text>
       
  3042     <xsl:text>        for(let eltid in old_desc.required_detachables){
       
  3043 </xsl:text>
       
  3044     <xsl:text>            if(!(eltid in new_desc.required_detachables)){
       
  3045 </xsl:text>
       
  3046     <xsl:text>                let [element, parent] = old_desc.required_detachables[eltid];
       
  3047 </xsl:text>
       
  3048     <xsl:text>                parent.removeChild(element);
       
  3049 </xsl:text>
       
  3050     <xsl:text>            }
       
  3051 </xsl:text>
       
  3052     <xsl:text>        }
       
  3053 </xsl:text>
       
  3054     <xsl:text>        for(let eltid in new_desc.required_detachables){
       
  3055 </xsl:text>
       
  3056     <xsl:text>            if(!(eltid in old_desc.required_detachables)){
       
  3057 </xsl:text>
       
  3058     <xsl:text>                let [element, parent] = new_desc.required_detachables[eltid];
       
  3059 </xsl:text>
       
  3060     <xsl:text>                parent.appendChild(element);
       
  3061 </xsl:text>
       
  3062     <xsl:text>            }
       
  3063 </xsl:text>
       
  3064     <xsl:text>        }
       
  3065 </xsl:text>
       
  3066     <xsl:text>    }else{
       
  3067 </xsl:text>
       
  3068     <xsl:text>        for(let eltid in new_desc.required_detachables){
       
  3069 </xsl:text>
       
  3070     <xsl:text>            let [element, parent] = new_desc.required_detachables[eltid];
       
  3071 </xsl:text>
       
  3072     <xsl:text>            parent.appendChild(element);
       
  3073 </xsl:text>
       
  3074     <xsl:text>        }
       
  3075 </xsl:text>
       
  3076     <xsl:text>    }
       
  3077 </xsl:text>
       
  3078     <xsl:text>
       
  3079 </xsl:text>
       
  3080     <xsl:text>    svg_root.setAttribute('viewBox',new_desc.bbox.join(" "));
       
  3081 </xsl:text>
       
  3082     <xsl:text>    current_visible_page = page_name;
       
  3083 </xsl:text>
       
  3084     <xsl:text>};
       
  3085 </xsl:text>
       
  3086     <xsl:text>
       
  3087 </xsl:text>
       
  3088     <xsl:text>function update_jumps() {
       
  3089 </xsl:text>
       
  3090     <xsl:text>    page_desc[current_visible_page].jumps.map(w=&gt;w.notify_page_change(current_visible_page,current_page_index));
       
  3091 </xsl:text>
       
  3092     <xsl:text>    jumps_need_update = false;
       
  3093 </xsl:text>
       
  3094     <xsl:text>};
       
  3095 </xsl:text>
       
  3096     <xsl:text>
       
  3097 </xsl:text>
       
  3098     <xsl:text>
       
  3099 </xsl:text>
       
  3100     <xsl:text>// Once connection established
       
  3101 </xsl:text>
       
  3102     <xsl:text>ws.onopen = function (evt) {
       
  3103 </xsl:text>
       
  3104     <xsl:text>    init_widgets();
       
  3105 </xsl:text>
       
  3106     <xsl:text>    send_reset();
       
  3107 </xsl:text>
       
  3108     <xsl:text>    // show main page
       
  3109 </xsl:text>
       
  3110     <xsl:text>    prepare_svg();
       
  3111 </xsl:text>
       
  3112     <xsl:text>    switch_page(default_page);
       
  3113 </xsl:text>
       
  3114     <xsl:text>};
       
  3115 </xsl:text>
       
  3116     <xsl:text>
       
  3117 </xsl:text>
       
  3118     <xsl:text>ws.onclose = function (evt) {
       
  3119 </xsl:text>
       
  3120     <xsl:text>    // TODO : add visible notification while waiting for reload
       
  3121 </xsl:text>
       
  3122     <xsl:text>    console.log("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+" Reload in 10s.");
       
  3123 </xsl:text>
       
  3124     <xsl:text>    // TODO : re-enable auto reload when not in debug
       
  3125 </xsl:text>
       
  3126     <xsl:text>    //window.setTimeout(() =&gt; location.reload(true), 10000);
       
  3127 </xsl:text>
       
  3128     <xsl:text>    alert("Connection closed. code:"+evt.code+" reason:"+evt.reason+" wasClean:"+evt.wasClean+".");
       
  3129 </xsl:text>
       
  3130     <xsl:text>
       
  3131 </xsl:text>
       
  3132     <xsl:text>};
       
  3133 </xsl:text>
       
  3134     <xsl:text>
       
  3135 </xsl:text>
       
  3136     <xsl:text>var xmlns = "http://www.w3.org/2000/svg";
       
  3137 </xsl:text>
       
  3138     <xsl:text>var edit_callback;
       
  3139 </xsl:text>
       
  3140     <xsl:text>function edit_value(path, valuetype, callback, initial) {
       
  3141 </xsl:text>
       
  3142     <xsl:text>
       
  3143 </xsl:text>
       
  3144     <xsl:text>    let [keypadid, xcoord, ycoord] = keypads[valuetype];
       
  3145 </xsl:text>
       
  3146     <xsl:text>    console.log('XXX TODO : Edit value', path, valuetype, callback, initial, keypadid);
       
  3147 </xsl:text>
       
  3148     <xsl:text>    edit_callback = callback;
       
  3149 </xsl:text>
       
  3150     <xsl:text>    let widget = hmi_widgets[keypadid];
       
  3151 </xsl:text>
       
  3152     <xsl:text>    widget.start_edit(path, valuetype, callback, initial);
       
  3153 </xsl:text>
       
  3154     <xsl:text>};
       
  3155 </xsl:text>
       
  3156     <xsl:text>
       
  3157 </xsl:text>
       
  3158     <xsl:text>var current_modal; /* TODO stack ?*/
       
  3159 </xsl:text>
       
  3160     <xsl:text>
       
  3161 </xsl:text>
       
  3162     <xsl:text>function show_modal() {
       
  3163 </xsl:text>
       
  3164     <xsl:text>    let [element, parent] = detachable_elements[this.element.id];
       
  3165 </xsl:text>
       
  3166     <xsl:text>
       
  3167 </xsl:text>
       
  3168     <xsl:text>    tmpgrp = document.createElementNS(xmlns,"g");
       
  3169 </xsl:text>
       
  3170     <xsl:text>    tmpgrpattr = document.createAttribute("transform");
       
  3171 </xsl:text>
       
  3172     <xsl:text>
       
  3173 </xsl:text>
       
  3174     <xsl:text>    let [xcoord,ycoord] = this.coordinates;
       
  3175 </xsl:text>
       
  3176     <xsl:text>    let [xdest,ydest] = page_desc[current_visible_page].bbox;
       
  3177 </xsl:text>
       
  3178     <xsl:text>    tmpgrpattr.value = "translate("+String(xdest-xcoord)+","+String(ydest-ycoord)+")";
       
  3179 </xsl:text>
       
  3180     <xsl:text>    tmpgrp.setAttributeNode(tmpgrpattr);
       
  3181 </xsl:text>
       
  3182     <xsl:text>
       
  3183 </xsl:text>
       
  3184     <xsl:text>    tmpgrp.appendChild(element);
       
  3185 </xsl:text>
       
  3186     <xsl:text>    parent.appendChild(tmpgrp);
       
  3187 </xsl:text>
       
  3188     <xsl:text>
       
  3189 </xsl:text>
       
  3190     <xsl:text>    current_modal = [this.element.id, tmpgrp];
       
  3191 </xsl:text>
       
  3192     <xsl:text>};
       
  3193 </xsl:text>
       
  3194     <xsl:text>
       
  3195 </xsl:text>
       
  3196     <xsl:text>function end_modal() {
       
  3197 </xsl:text>
       
  3198     <xsl:text>    let [eltid, tmpgrp] = current_modal;
       
  3199 </xsl:text>
       
  3200     <xsl:text>    let [element, parent] = detachable_elements[this.element.id];
       
  3201 </xsl:text>
       
  3202     <xsl:text>
       
  3203 </xsl:text>
       
  3204     <xsl:text>    parent.removeChild(tmpgrp);
       
  3205 </xsl:text>
       
  3206     <xsl:text>
       
  3207 </xsl:text>
       
  3208     <xsl:text>    current_modal = undefined;
       
  3209 </xsl:text>
       
  3210     <xsl:text>};
       
  3211 </xsl:text>
       
  3212     <xsl:text>
       
  3213 </xsl:text>
       
  3214     <xsl:text>function widget_active_activable(eltsub) {
       
  3215 </xsl:text>
       
  3216     <xsl:text>    if(eltsub.inactive_style === undefined)
       
  3217 </xsl:text>
       
  3218     <xsl:text>        eltsub.inactive_style = eltsub.inactive.getAttribute("style");
       
  3219 </xsl:text>
       
  3220     <xsl:text>    eltsub.inactive.setAttribute("style", "display:none");
       
  3221 </xsl:text>
       
  3222     <xsl:text>    if(eltsub.active_style !== undefined)
       
  3223 </xsl:text>
       
  3224     <xsl:text>            eltsub.active.setAttribute("style", eltsub.active_style);
       
  3225 </xsl:text>
       
  3226     <xsl:text>    console.log("active", eltsub);
       
  3227 </xsl:text>
       
  3228     <xsl:text>};
       
  3229 </xsl:text>
       
  3230     <xsl:text>function widget_inactive_activable(eltsub) {
       
  3231 </xsl:text>
       
  3232     <xsl:text>    if(eltsub.active_style === undefined)
       
  3233 </xsl:text>
       
  3234     <xsl:text>        eltsub.active_style = eltsub.active.getAttribute("style");
       
  3235 </xsl:text>
       
  3236     <xsl:text>    eltsub.active.setAttribute("style", "display:none");
       
  3237 </xsl:text>
       
  3238     <xsl:text>    if(eltsub.inactive_style !== undefined)
       
  3239 </xsl:text>
       
  3240     <xsl:text>            eltsub.inactive.setAttribute("style", eltsub.inactive_style);
       
  3241 </xsl:text>
       
  3242     <xsl:text>    console.log("inactive", eltsub);
       
  3243 </xsl:text>
       
  3244     <xsl:text>};
       
  3245 </xsl:text>
       
  3246   </xsl:template>
       
  3247 </xsl:stylesheet>
  3247 </xsl:stylesheet>