// widgets_common.ysl2 in xsl decl labels(*ptr, name="defs_by_labels") alias call-template { with "hmi_element", "$hmi_element"; with "labels"{text *ptr}; content; }; decl optional_labels(*ptr) alias - { /* TODO add some per label xslt variable to check if exist */ labels(*ptr){ with "mandatory","'no'"; content; } }; decl activable_labels(*ptr) alias - { optional_labels(*ptr) { with "subelements","'active inactive'"; content; } }; in xsl decl widget_desc(%name, match="widget[@type='%name']", mode="widget_desc") alias template { type > «@type» content; }; in xsl decl widget_class(%name, *clsname="%nameWidget", match="widget[@type='%name']", mode="widget_class") alias template { | class `text **clsname` extends Widget{ content; | } }; in xsl decl widget_defs(%name, match="widget[@type='%name']", mode="widget_defs") alias template { param "hmi_element"; content; }; in xsl decl widget_page(%name, match="widget[@type='%name']", mode="widget_page") alias template { param "page_desc"; content; }; decl gen_index_xhtml alias - { content; }; template "svg:*", mode="hmi_widgets" { const "widget", "func:widget(@id)"; const "eltid","@id"; const "args" foreach "$widget/arg" > "«func:escape_quotes(@value)»"`if "position()!=last()" > ,` const "indexes" foreach "$widget/path" { choose { when "not(@index)" { choose { when "not(@type)" { warning > Widget «$widget/@type» id="«$eltid»" : No match for path "«@value»" in HMI tree > undefined } when "@type = 'PAGE_LOCAL'" > "«@value»" when "@type = 'HMI_LOCAL'" > hmi_local_index("«@value»") otherwise error > Internal error while processing widget's non indexed HMI tree path : unknown type } } otherwise { > «@index» } } if "position()!=last()" > , } const "minmaxes" foreach "$widget/path" { choose { when "@min and @max" > [«@min»,«@max»] otherwise > undefined } if "position()!=last()" > , } const "freq" choose { when "$widget/@freq" > "«$widget/@freq»" otherwise > undefined } | "«@id»": new «$widget/@type»Widget ("«@id»",«$freq»,[«$args»],[«$indexes»],[«$minmaxes»],{ apply "$widget", mode="widget_defs" with "hmi_element","."; | })`if "position()!=last()" > ,` } emit "preamble:local-variable-indexes" { || let hmi_locals = {}; var last_remote_index = hmitree_types.length - 1; var next_available_index = hmitree_types.length; let cookies = new Map(document.cookie.split("; ").map(s=>s.split("="))); const local_defaults = { || foreach "$parsed_widgets/widget[starts-with(@type,'VarInit')]"{ if "count(path) != 1" error > VarInit «@id» must have only one variable given. if "path/@type != 'PAGE_LOCAL' and path/@type != 'HMI_LOCAL'" error > VarInit «@id» only applies to HMI variable. > "«path/@value»": choose { when "@type = 'VarInitPersistent'" > cookies.has("«path/@value»")?cookies.get("«path/@value»"):«arg[1]/@value» otherwise > «arg[1]/@value» } > \n if "position()!=last()" > , } || }; const persistent_locals = new Set([ || foreach "$parsed_widgets/widget[@type='VarInitPersistent']"{ | "«path/@value»"`if "position()!=last()" > ,` } || ]); var persistent_indexes = new Map(); var cache = hmitree_types.map(_ignored => undefined); var updates = new Map(); function page_local_index(varname, pagename){ let pagevars = hmi_locals[pagename]; let new_index; if(pagevars == undefined){ new_index = next_available_index++; hmi_locals[pagename] = {[varname]:new_index} } else { let result = pagevars[varname]; if(result != undefined) { return result; } new_index = next_available_index++; pagevars[varname] = new_index; } let defaultval = local_defaults[varname]; if(defaultval != undefined) { cache[new_index] = defaultval; updates.set(new_index, defaultval); if(persistent_locals.has(varname)) persistent_indexes.set(new_index, varname); } return new_index; } function hmi_local_index(varname){ return page_local_index(varname, "HMI_LOCAL"); } || } emit "preamble:widget-base-class" { || var pending_widget_animates = []; class Widget { offset = 0; frequency = 10; /* FIXME arbitrary default max freq. Obtain from config ? */ unsubscribable = false; pending_animate = false; constructor(elt_id, freq, args, indexes, minmaxes, members){ this.element_id = elt_id; this.element = id(elt_id); this.args = args; this.indexes = indexes; this.minmaxes = minmaxes; Object.keys(members).forEach(prop => this[prop]=members[prop]); this.lastapply = indexes.map(() => undefined); this.inhibit = indexes.map(() => undefined); this.pending = indexes.map(() => undefined); this.bound_unhinibit = this.unhinibit.bind(this); this.forced_frequency = freq; this.clip = true; } do_init(){ let forced = this.forced_frequency; if(forced !== undefined){ /* once every 10 seconds : 10s once per minute : 1m once per hour : 1h once per day : 1d */ let unit = forced.slice(-1); let factor = { "s":1, "m":60, "h":3600, "d":86400}[unit]; this.frequency = factor ? 1/(factor * Number(forced.slice(0,-1))) : Number(forced); } let init = this.init; if(typeof(init) == "function"){ try { init.call(this); } catch(err) { console.log(err); } } } unsub(){ /* remove subsribers */ if(!this.unsubscribable) for(let i = 0; i < this.indexes.length; i++) { /* flush updates pending because of inhibition */ let inhibition = this.inhibit[i]; if(inhibition != undefined){ clearTimeout(inhibition); this.lastapply[i] = undefined; this.unhinibit(i); } let index = this.indexes[i]; if(this.relativeness[i]) index += this.offset; subscribers(index).delete(this); } this.offset = 0; this.relativeness = undefined; } sub(new_offset=0, relativeness, container_id){ this.offset = new_offset; this.relativeness = relativeness; this.container_id = container_id ; /* add this's subsribers */ if(!this.unsubscribable) for(let i = 0; i < this.indexes.length; i++) { let index = this.get_variable_index(i); if(index == undefined) continue; subscribers(index).add(this); } need_cache_apply.push(this); } apply_cache() { if(!this.unsubscribable) for(let index in this.indexes){ /* dispatch current cache in newly opened page widgets */ let realindex = this.get_variable_index(index); if(realindex == undefined) continue; let cached_val = cache[realindex]; if(cached_val != undefined) this._dispatch(cached_val, cached_val, index); } } get_variable_index(varnum) { let index = this.indexes[varnum]; if(typeof(index) == "string"){ index = page_local_index(index, this.container_id); } else { if(this.relativeness[varnum]){ index += this.offset; } } return index; } overshot(new_val, max) { } undershot(new_val, min) { } clip_min_max(index, new_val) { let minmax = this.minmaxes[index]; if(minmax !== undefined && typeof new_val == "number") { let [min,max] = minmax; if(new_val < min){ this.undershot(new_val, min); return min; } if(new_val > max){ this.overshot(new_val, max); return max; } } return new_val; } change_hmi_value(index, opstr) { let realindex = this.get_variable_index(index); if(realindex == undefined) return undefined; let old_val = cache[realindex]; let new_val = eval_operation_string(old_val, opstr); if(this.clip) new_val = this.clip_min_max(index, new_val); return apply_hmi_value(realindex, new_val); } _apply_hmi_value(index, new_val) { let realindex = this.get_variable_index(index); if(realindex == undefined) return undefined; if(this.clip) new_val = this.clip_min_max(index, new_val); return apply_hmi_value(realindex, new_val); } unhinibit(index){ this.inhibit[index] = undefined; let new_val = this.pending[index]; this.pending[index] = undefined; return this.apply_hmi_value(index, new_val); } apply_hmi_value(index, new_val) { if(this.inhibit[index] == undefined){ let now = Date.now(); let min_interval = 1000/this.frequency; let lastapply = this.lastapply[index]; if(lastapply == undefined || now > lastapply + min_interval){ this.lastapply[index] = now; return this._apply_hmi_value(index, new_val); } else { let elapsed = now - lastapply; this.pending[index] = new_val; this.inhibit[index] = setTimeout(this.bound_unhinibit, min_interval - elapsed, index); } } else { this.pending[index] = new_val; return new_val; } } new_hmi_value(index, value, oldval) { // TODO avoid searching, store index at sub() for(let i = 0; i < this.indexes.length; i++) { let refindex = this.get_variable_index(i); if(refindex == undefined) continue; if(index == refindex) { this._dispatch(value, oldval, i); break; } } } _dispatch(value, oldval, varnum) { let dispatch = this.dispatch; if(dispatch != undefined){ try { dispatch.call(this, value, oldval, varnum); } catch(err) { console.log(err); } } } _animate(){ this.animate(); this.pending_animate = false; } request_animate(){ if(!this.pending_animate){ pending_widget_animates.push(this); this.pending_animate = true; requestHMIAnimation(); } } activate_activable(eltsub) { eltsub.inactive.style.display = "none"; eltsub.active.style.display = ""; } inactivate_activable(eltsub) { eltsub.active.style.display = "none"; eltsub.inactive.style.display = ""; } } || } const "excluded_types", "str:split('Page VarInit VarInitPersistent')"; // Key to filter unique types key "TypesKey", "widget", "@type"; emit "declarations:hmi-classes" { const "used_widget_types", """$parsed_widgets/widget[ generate-id() = generate-id(key('TypesKey', @type)) and not(@type = $excluded_types)]"""; apply "$used_widget_types", mode="widget_class"; } template "widget", mode="widget_class" || class «@type»Widget extends Widget{ /* empty class, as «@type» widget didn't provide any */ } || const "included_ids","$parsed_widgets/widget[not(@type = $excluded_types) and not(@id = $discardable_elements/@id)]/@id"; const "hmi_widgets","$hmi_elements[@id = $included_ids]"; const "result_widgets","$result_svg_ns//*[@id = $hmi_widgets/@id]"; emit "declarations:hmi-elements" { | var hmi_widgets = { apply "$hmi_widgets", mode="hmi_widgets"; | } } function "defs_by_labels" { param "labels","''"; param "mandatory","'yes'"; param "subelements","/.."; param "hmi_element"; const "widget_type","@type"; foreach "str:split($labels)" { const "absolute", "starts-with(., '/')"; const "name","substring(.,number($absolute)+1)"; const "widget","$result_widgets[@id = $hmi_element/@id]"; const "elt","($widget//*[not($absolute) and @inkscape:label=$name] | $widget/*[$absolute and @inkscape:label=$name])[1]"; choose { when "not($elt/@id)" { if "$mandatory='yes'" { error > «$widget_type» widget must have a «$name» element } // otherwise produce nothing } otherwise { | «$name»_elt: id("«$elt/@id»"), if "$subelements" { | «$name»_sub: { foreach "str:split($subelements)" { const "subname","."; const "subelt","$elt/*[@inkscape:label=$subname][1]"; choose { when "not($subelt/@id)" { if "$mandatory='yes'" { error > «$widget_type» widget must have a «$name»/«$subname» element } | /* missing «$name»/«$subname» element */ } otherwise { | "«$subname»": id("«$subelt/@id»")`if "position()!=last()" > ,` } } } | }, } } } } } def "func:escape_quotes" { param "txt"; // have to use a python string to enter escaped quote // const "frstln", "string-length($frst)"; choose { when !"contains($txt,'\"')"! { result !"concat(substring-before($txt,'\"'),'\\\"',func:escape_quotes(substring-after($txt,'\"')))"!; } otherwise { result "$txt"; } } }