2212 lines
199 KiB
JavaScript
2212 lines
199 KiB
JavaScript
|
/*! jQuery v1.12.4 | (c) jQuery Foundation | jquery.org/license */
|
|||
|
!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="1.12.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function A
|
|||
|
}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),"object"!=typeof b&&"function"!=typeof b||(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=n._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}}),function(){var a;l.shrinkWrapBlocks=function(){if(null!=a)return a;a=!1;var b,c,e;return c=d.getElementsByTagName("body")[0],c&&c.style?(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:1px;width:1px;zoom:1",b.appendChild(d.createElement("div")).style.width="5px",a=3!==b.offsetWidth),c.removeChild(e),a):void 0}}();var T=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,U=new RegExp("^(?:([+-])=|)("+T+")([a-z%]*)$","i"),V=["Top","Right","Bottom","Left"],W=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)};function X(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return n.css(a,b,"")},i=h(),j=c&&c[3]||(n.cssNumber[b]?"":"px"),k=(n.cssNum
|
|||
|
marginLeft:0},function(){return a.getBoundingClientRect().left}):0))+"px":void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+V[d]+b]=f[d]||f[d-2]||f[0];return e}},Na.test(a)||(n.cssHooks[a+b].set=db)}),n.fn.extend({css:function(a,b){return Y(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=Ra(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return cb(this,!0)},hide:function(){return cb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){W(this)?n(this).show():n(this).hide()})}});function gb(a,b,c,d,e){return new gb.prototype.init(a,b,c,d,e)}n.Tween=gb,gb.prototype={constructor:gb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||n.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=gb.propHooks[this.prop];return a&&a.get?a.get(this):gb.propHooks._default.get(this)},run:function(a){var b,c=gb.propHooks[this.prop];return this.options.duration?this.pos=b=n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):gb.propHooks._default.set(this),this}},gb.prototype.init.prototype=gb.prototype,gb.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[n.cssProps[a.prop]]&&!n.cssHooks[a.prop]?a.elem[a.prop]=a.now:n.style(a.elem,a.prop,a.now+a.unit)}}},gb.propHooks.scrollTop=gb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},n.fx=gb.prototype.init,n.fx.step={};var hb,ib,jb=/^(?:toggle|show|hide)$/,kb=/queueHooks$/;function lb(){return a.setTimeout(function(){hb=void 0}),hb=n.now()}function mb(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=V[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function nb(a,b,c){for(var d,e=(qb.tweeners[b]||[]).concat(qb.tweeners["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ob(a,b,c){var d,e,f,g,h,i,j,k,m=this,o={},p=a.style,q=a.nodeType&&W(a),r=n._data(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,m.always(function(){m.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=n.css(a,"display"),k="none"===j?n._data(a,"olddisplay")||Ma(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(l.inlineBlockNeedsLayout&&"inline"!==Ma(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",l.shrinkWrapBlocks()||m.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],jb.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(o))"inline"===("none"===j?Ma(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=n._data(a,"fxshow",{}),f&&(r.hidden=!q),q?n(a).show():m.done(function(){n(a).hide()}),m.done(function(){var b;n._removeData(a,"fxshow");for(b in o)n.style(a,b,o[b])});for(d in o)g=nb(q?r[d]:0,d,m),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function pb(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expa
|
|||
|
padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return Y(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.extend({bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var nc=a.jQuery,oc=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=oc),b&&a.jQuery===n&&(a.jQuery=nc),n},b||(a.jQuery=a.$=n),n});
|
|||
|
/*!
|
|||
|
* Bootstrap v3.3.7 (http://getbootstrap.com)
|
|||
|
* Copyright 2011-2016 Twitter, Inc.
|
|||
|
* Licensed under the MIT license
|
|||
|
*/
|
|||
|
if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.D
|
|||
|
this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.7",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e<c&&"top";if("bottom"==this.affixed)return null!=c?!(e+this.unpin<=f.top)&&"bottom":!(e+g<=a-d)&&"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&e<=c?"top":null!=d&&i+j>=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(
|
|||
|
* The MIT License (MIT)
|
|||
|
*
|
|||
|
* Copyright (c) 2014-2016 Patrick Gansterer <paroga@paroga.com>
|
|||
|
*
|
|||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|||
|
* of this software and associated documentation files (the "Software"), to deal
|
|||
|
* in the Software without restriction, including without limitation the rights
|
|||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|||
|
* copies of the Software, and to permit persons to whom the Software is
|
|||
|
* furnished to do so, subject to the following conditions:
|
|||
|
*
|
|||
|
* The above copyright notice and this permission notice shall be included in all
|
|||
|
* copies or substantial portions of the Software.
|
|||
|
*
|
|||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||
|
* SOFTWARE.
|
|||
|
*/
|
|||
|
|
|||
|
(function(global, undefined) { "use strict";
|
|||
|
var POW_2_24 = 5.960464477539063e-8,
|
|||
|
POW_2_32 = 4294967296,
|
|||
|
POW_2_53 = 9007199254740992;
|
|||
|
|
|||
|
function encode(value) {
|
|||
|
var data = new ArrayBuffer(256);
|
|||
|
var dataView = new DataView(data);
|
|||
|
var lastLength;
|
|||
|
var offset = 0;
|
|||
|
|
|||
|
function prepareWrite(length) {
|
|||
|
var newByteLength = data.byteLength;
|
|||
|
var requiredLength = offset + length;
|
|||
|
while (newByteLength < requiredLength)
|
|||
|
newByteLength <<= 1;
|
|||
|
if (newByteLength !== data.byteLength) {
|
|||
|
var oldDataView = dataView;
|
|||
|
data = new ArrayBuffer(newByteLength);
|
|||
|
dataView = new DataView(data);
|
|||
|
var uint32count = (offset + 3) >> 2;
|
|||
|
for (var i = 0; i < uint32count; ++i)
|
|||
|
dataView.setUint32(i << 2, oldDataView.getUint32(i << 2));
|
|||
|
}
|
|||
|
|
|||
|
lastLength = length;
|
|||
|
return dataView;
|
|||
|
}
|
|||
|
function commitWrite() {
|
|||
|
offset += lastLength;
|
|||
|
}
|
|||
|
function writeFloat64(value) {
|
|||
|
commitWrite(prepareWrite(8).setFloat64(offset, value));
|
|||
|
}
|
|||
|
function writeUint8(value) {
|
|||
|
commitWrite(prepareWrite(1).setUint8(offset, value));
|
|||
|
}
|
|||
|
function writeUint8Array(value) {
|
|||
|
var dataView = prepareWrite(value.length);
|
|||
|
for (var i = 0; i < value.length; ++i)
|
|||
|
dataView.setUint8(offset + i, value[i]);
|
|||
|
commitWrite();
|
|||
|
}
|
|||
|
function writeUint16(value) {
|
|||
|
commitWrite(prepareWrite(2).setUint16(offset, value));
|
|||
|
}
|
|||
|
function writeUint32(value) {
|
|||
|
commitWrite(prepareWrite(4).setUint32(offset, value));
|
|||
|
}
|
|||
|
function writeUint64(value) {
|
|||
|
var low = value % POW_2_32;
|
|||
|
var high = (value - low) / POW_2_32;
|
|||
|
var dataView = prepareWrite(8);
|
|||
|
dataView.setUint32(offset, high);
|
|||
|
dataView.setUint32(offset + 4, low);
|
|||
|
commitWrite();
|
|||
|
}
|
|||
|
function writeTypeAndLength(type, length) {
|
|||
|
if (length < 24) {
|
|||
|
writeUint8(type << 5 | length);
|
|||
|
} else if (length < 0x100) {
|
|||
|
writeUint8(type << 5 | 24);
|
|||
|
writeUint8(length);
|
|||
|
} else if (length < 0x10000) {
|
|||
|
writeUint8(type << 5 | 25);
|
|||
|
writeUint16(length);
|
|||
|
} else if (length < 0x100000000) {
|
|||
|
writeUint8(type << 5 | 26);
|
|||
|
writeUint32(length);
|
|||
|
} else {
|
|||
|
writeUint8(type << 5 | 27);
|
|||
|
writeUint64(length);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function encodeItem(value) {
|
|||
|
var i;
|
|||
|
|
|||
|
if (value === false)
|
|||
|
return writeUint8(0xf4);
|
|||
|
if (value === true)
|
|||
|
return writeUint8(0xf5);
|
|||
|
if (value === null)
|
|||
|
return writeUint8(0xf6);
|
|||
|
if (value === undefined)
|
|||
|
return writeUint8(0xf7);
|
|||
|
|
|||
|
switch (typeof value) {
|
|||
|
case "number":
|
|||
|
if (Math.floor(value) === value) {
|
|||
|
if (0 <= value && value <= POW_2_53)
|
|||
|
return writeTypeAndLength(0, value);
|
|||
|
if (-POW_2_53 <= value && value < 0)
|
|||
|
return writeTypeAndLength(1, -(value + 1));
|
|||
|
}
|
|||
|
writeUint8(0xfb);
|
|||
|
return writeFloat64(value);
|
|||
|
|
|||
|
case "string":
|
|||
|
var utf8data = [];
|
|||
|
for (i = 0; i < value.length; ++i) {
|
|||
|
var charCode = value.charCodeAt(i);
|
|||
|
if (charCode < 0x80) {
|
|||
|
utf8data.push(charCode);
|
|||
|
} else if (charCode < 0x800) {
|
|||
|
utf8data.push(0xc0 | charCode >> 6);
|
|||
|
utf8data.push(0x80 | charCode & 0x3f);
|
|||
|
} else if (charCode < 0xd800) {
|
|||
|
utf8data.push(0xe0 | charCode >> 12);
|
|||
|
utf8data.push(0x80 | (charCode >> 6) & 0x3f);
|
|||
|
utf8data.push(0x80 | charCode & 0x3f);
|
|||
|
} else {
|
|||
|
charCode = (charCode & 0x3ff) << 10;
|
|||
|
charCode |= value.charCodeAt(++i) & 0x3ff;
|
|||
|
charCode += 0x10000;
|
|||
|
|
|||
|
utf8data.push(0xf0 | charCode >> 18);
|
|||
|
utf8data.push(0x80 | (charCode >> 12) & 0x3f);
|
|||
|
utf8data.push(0x80 | (charCode >> 6) & 0x3f);
|
|||
|
utf8data.push(0x80 | charCode & 0x3f);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
writeTypeAndLength(3, utf8data.length);
|
|||
|
return writeUint8Array(utf8data);
|
|||
|
|
|||
|
default:
|
|||
|
var length;
|
|||
|
if (Array.isArray(value)) {
|
|||
|
length = value.length;
|
|||
|
writeTypeAndLength(4, length);
|
|||
|
for (i = 0; i < length; ++i)
|
|||
|
encodeItem(value[i]);
|
|||
|
} else if (value instanceof Uint8Array) {
|
|||
|
writeTypeAndLength(2, value.length);
|
|||
|
writeUint8Array(value);
|
|||
|
} else {
|
|||
|
var keys = Object.keys(value);
|
|||
|
length = keys.length;
|
|||
|
writeTypeAndLength(5, length);
|
|||
|
for (i = 0; i < length; ++i) {
|
|||
|
var key = keys[i];
|
|||
|
encodeItem(key);
|
|||
|
encodeItem(value[key]);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
encodeItem(value);
|
|||
|
|
|||
|
if ("slice" in data)
|
|||
|
return data.slice(0, offset);
|
|||
|
|
|||
|
var ret = new ArrayBuffer(offset);
|
|||
|
var retView = new DataView(ret);
|
|||
|
for (var i = 0; i < offset; ++i)
|
|||
|
retView.setUint8(i, dataView.getUint8(i));
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
function decode(data, tagger, simpleValue) {
|
|||
|
|
|||
|
// OVMS extension: if data is passed as a string, assume 8 bit stream,
|
|||
|
// to allow decoding in browser as simple as encoding in Duktape:
|
|||
|
if (typeof(data) == "string") {
|
|||
|
var u8 = new Uint8Array(data.length);
|
|||
|
for (var i = 0; i < data.length; i++)
|
|||
|
u8[i] = data.charCodeAt(i) & 0xff;
|
|||
|
data = u8.buffer;
|
|||
|
}
|
|||
|
|
|||
|
var dataView = new DataView(data);
|
|||
|
var offset = 0;
|
|||
|
|
|||
|
if (typeof tagger !== "function")
|
|||
|
tagger = function(value) { return value; };
|
|||
|
if (typeof simpleValue !== "function")
|
|||
|
simpleValue = function() { return undefined; };
|
|||
|
|
|||
|
function commitRead(length, value) {
|
|||
|
offset += length;
|
|||
|
return value;
|
|||
|
}
|
|||
|
function readArrayBuffer(length) {
|
|||
|
return commitRead(length, new Uint8Array(data, offset, length));
|
|||
|
}
|
|||
|
function readFloat16() {
|
|||
|
var tempArrayBuffer = new ArrayBuffer(4);
|
|||
|
var tempDataView = new DataView(tempArrayBuffer);
|
|||
|
var value = readUint16();
|
|||
|
|
|||
|
var sign = value & 0x8000;
|
|||
|
var exponent = value & 0x7c00;
|
|||
|
var fraction = value & 0x03ff;
|
|||
|
|
|||
|
if (exponent === 0x7c00)
|
|||
|
exponent = 0xff << 10;
|
|||
|
else if (exponent !== 0)
|
|||
|
exponent += (127 - 15) << 10;
|
|||
|
else if (fraction !== 0)
|
|||
|
return (sign ? -1 : 1) * fraction * POW_2_24;
|
|||
|
|
|||
|
tempDataView.setUint32(0, sign << 16 | exponent << 13 | fraction << 13);
|
|||
|
return tempDataView.getFloat32(0);
|
|||
|
}
|
|||
|
function readFloat32() {
|
|||
|
return commitRead(4, dataView.getFloat32(offset));
|
|||
|
}
|
|||
|
function readFloat64() {
|
|||
|
return commitRead(8, dataView.getFloat64(offset));
|
|||
|
}
|
|||
|
function readUint8() {
|
|||
|
return commitRead(1, dataView.getUint8(offset));
|
|||
|
}
|
|||
|
function readUint16() {
|
|||
|
return commitRead(2, dataView.getUint16(offset));
|
|||
|
}
|
|||
|
function readUint32() {
|
|||
|
return commitRead(4, dataView.getUint32(offset));
|
|||
|
}
|
|||
|
function readUint64() {
|
|||
|
return readUint32() * POW_2_32 + readUint32();
|
|||
|
}
|
|||
|
function readBreak() {
|
|||
|
if (dataView.getUint8(offset) !== 0xff)
|
|||
|
return false;
|
|||
|
offset += 1;
|
|||
|
return true;
|
|||
|
}
|
|||
|
function readLength(additionalInformation) {
|
|||
|
if (additionalInformation < 24)
|
|||
|
return additionalInformation;
|
|||
|
if (additionalInformation === 24)
|
|||
|
return readUint8();
|
|||
|
if (additionalInformation === 25)
|
|||
|
return readUint16();
|
|||
|
if (additionalInformation === 26)
|
|||
|
return readUint32();
|
|||
|
if (additionalInformation === 27)
|
|||
|
return readUint64();
|
|||
|
if (additionalInformation === 31)
|
|||
|
return -1;
|
|||
|
throw "Invalid length encoding";
|
|||
|
}
|
|||
|
function readIndefiniteStringLength(majorType) {
|
|||
|
var initialByte = readUint8();
|
|||
|
if (initialByte === 0xff)
|
|||
|
return -1;
|
|||
|
var length = readLength(initialByte & 0x1f);
|
|||
|
if (length < 0 || (initialByte >> 5) !== majorType)
|
|||
|
throw "Invalid indefinite length element";
|
|||
|
return length;
|
|||
|
}
|
|||
|
|
|||
|
function appendUtf16Data(utf16data, length) {
|
|||
|
for (var i = 0; i < length; ++i) {
|
|||
|
var value = readUint8();
|
|||
|
if (value & 0x80) {
|
|||
|
if (value < 0xe0) {
|
|||
|
value = (value & 0x1f) << 6
|
|||
|
| (readUint8() & 0x3f);
|
|||
|
length -= 1;
|
|||
|
} else if (value < 0xf0) {
|
|||
|
value = (value & 0x0f) << 12
|
|||
|
| (readUint8() & 0x3f) << 6
|
|||
|
| (readUint8() & 0x3f);
|
|||
|
length -= 2;
|
|||
|
} else {
|
|||
|
value = (value & 0x0f) << 18
|
|||
|
| (readUint8() & 0x3f) << 12
|
|||
|
| (readUint8() & 0x3f) << 6
|
|||
|
| (readUint8() & 0x3f);
|
|||
|
length -= 3;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (value < 0x10000) {
|
|||
|
utf16data.push(value);
|
|||
|
} else {
|
|||
|
value -= 0x10000;
|
|||
|
utf16data.push(0xd800 | (value >> 10));
|
|||
|
utf16data.push(0xdc00 | (value & 0x3ff));
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function decodeItem() {
|
|||
|
var initialByte = readUint8();
|
|||
|
var majorType = initialByte >> 5;
|
|||
|
var additionalInformation = initialByte & 0x1f;
|
|||
|
var i;
|
|||
|
var length;
|
|||
|
|
|||
|
if (majorType === 7) {
|
|||
|
switch (additionalInformation) {
|
|||
|
case 25:
|
|||
|
return readFloat16();
|
|||
|
case 26:
|
|||
|
return readFloat32();
|
|||
|
case 27:
|
|||
|
return readFloat64();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
length = readLength(additionalInformation);
|
|||
|
if (length < 0 && (majorType < 2 || 6 < majorType))
|
|||
|
throw "Invalid length";
|
|||
|
|
|||
|
switch (majorType) {
|
|||
|
case 0:
|
|||
|
return length;
|
|||
|
case 1:
|
|||
|
return -1 - length;
|
|||
|
case 2:
|
|||
|
if (length < 0) {
|
|||
|
var elements = [];
|
|||
|
var fullArrayLength = 0;
|
|||
|
while ((length = readIndefiniteStringLength(majorType)) >= 0) {
|
|||
|
fullArrayLength += length;
|
|||
|
elements.push(readArrayBuffer(length));
|
|||
|
}
|
|||
|
var fullArray = new Uint8Array(fullArrayLength);
|
|||
|
var fullArrayOffset = 0;
|
|||
|
for (i = 0; i < elements.length; ++i) {
|
|||
|
fullArray.set(elements[i], fullArrayOffset);
|
|||
|
fullArrayOffset += elements[i].length;
|
|||
|
}
|
|||
|
return fullArray;
|
|||
|
}
|
|||
|
return readArrayBuffer(length);
|
|||
|
case 3:
|
|||
|
var utf16data = [];
|
|||
|
if (length < 0) {
|
|||
|
while ((length = readIndefiniteStringLength(majorType)) >= 0)
|
|||
|
appendUtf16Data(utf16data, length);
|
|||
|
} else
|
|||
|
appendUtf16Data(utf16data, length);
|
|||
|
return String.fromCharCode.apply(null, utf16data);
|
|||
|
case 4:
|
|||
|
var retArray;
|
|||
|
if (length < 0) {
|
|||
|
retArray = [];
|
|||
|
while (!readBreak())
|
|||
|
retArray.push(decodeItem());
|
|||
|
} else {
|
|||
|
retArray = new Array(length);
|
|||
|
for (i = 0; i < length; ++i)
|
|||
|
retArray[i] = decodeItem();
|
|||
|
}
|
|||
|
return retArray;
|
|||
|
case 5:
|
|||
|
var retObject = {};
|
|||
|
for (i = 0; i < length || length < 0 && !readBreak(); ++i) {
|
|||
|
var key = decodeItem();
|
|||
|
retObject[key] = decodeItem();
|
|||
|
}
|
|||
|
return retObject;
|
|||
|
case 6:
|
|||
|
return tagger(decodeItem(), length);
|
|||
|
case 7:
|
|||
|
switch (length) {
|
|||
|
case 20:
|
|||
|
return false;
|
|||
|
case 21:
|
|||
|
return true;
|
|||
|
case 22:
|
|||
|
return null;
|
|||
|
case 23:
|
|||
|
return undefined;
|
|||
|
default:
|
|||
|
return simpleValue(length);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var ret = decodeItem();
|
|||
|
if (offset !== data.byteLength)
|
|||
|
throw "Remaining bytes";
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
var obj = { encode: encode, decode: decode };
|
|||
|
|
|||
|
if (typeof define === "function" && define.amd)
|
|||
|
define("cbor/cbor", obj);
|
|||
|
else if (typeof module !== "undefined" && module.exports)
|
|||
|
module.exports = obj;
|
|||
|
else if (!global.CBOR)
|
|||
|
global.CBOR = obj;
|
|||
|
|
|||
|
})(this);
|
|||
|
/* ovms.js | (c) Michael Balzer | https://github.com/openvehicles/Open-Vehicle-Monitoring-System-3 */
|
|||
|
|
|||
|
const monthnames = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
|||
|
const supportsTouch = 'ontouchstart' in window || navigator.msMaxTouchPoints;
|
|||
|
|
|||
|
if (window.loggedin == undefined)
|
|||
|
window.loggedin = false;
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* Utilities
|
|||
|
*/
|
|||
|
|
|||
|
function after(seconds, fn) {
|
|||
|
return window.setTimeout(fn, seconds*1000);
|
|||
|
}
|
|||
|
|
|||
|
function now() {
|
|||
|
return Math.floor((new Date()).getTime() / 1000);
|
|||
|
}
|
|||
|
|
|||
|
function encode_html(s) {
|
|||
|
return String(s)
|
|||
|
.replace(/&/g, '&')
|
|||
|
.replace(/'/g, ''')
|
|||
|
.replace(/"/g, '"')
|
|||
|
.replace(/</g, '<')
|
|||
|
.replace(/>/g, '>');
|
|||
|
}
|
|||
|
|
|||
|
function unwrapLogLine(s) {
|
|||
|
return String(s)
|
|||
|
.replace(/(\S)\|+(.)/g, "$1\n……: $2");
|
|||
|
}
|
|||
|
|
|||
|
function fix_minheight($el) {
|
|||
|
if ($el.css("resize") != "none") return;
|
|||
|
var mh = parseInt($el.css("max-height")), h = $el.outerHeight();
|
|||
|
$el.css("min-height", mh ? Math.min(h, mh) : h);
|
|||
|
}
|
|||
|
|
|||
|
function getPathURL(path) {
|
|||
|
// TODO: use actual http.server config
|
|||
|
if (path.startsWith('/sd/'))
|
|||
|
return path.substr(3);
|
|||
|
else
|
|||
|
return '';
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* Hexadecimal encoding & decoding
|
|||
|
* based on: https://stackoverflow.com/a/54099484 by Aaron Watters
|
|||
|
*/
|
|||
|
|
|||
|
var HEX = {
|
|||
|
to_hex_array: [],
|
|||
|
to_byte_map: {},
|
|||
|
|
|||
|
// Init lookup tables:
|
|||
|
init: function () {
|
|||
|
for (var ord=0; ord<=0xff; ord++) {
|
|||
|
var s = ord.toString(16);
|
|||
|
if (s.length < 2) {
|
|||
|
s = "0" + s;
|
|||
|
}
|
|||
|
this.to_hex_array.push(s);
|
|||
|
this.to_byte_map[s] = ord;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// Encode ArrayBuffer to hexadecimal string:
|
|||
|
// Usage example: HEX.encode(CBOR.encode({a: 42})) = "a16161182a"
|
|||
|
encode: function (arraybuffer) {
|
|||
|
if (!this.to_hex_array.length) this.init();
|
|||
|
var buffer = new Uint8Array(arraybuffer);
|
|||
|
var hex_array = [];
|
|||
|
for (var i=0; i<buffer.length; i++) {
|
|||
|
hex_array.push(this.to_hex_array[buffer[i]]);
|
|||
|
}
|
|||
|
return hex_array.join('');
|
|||
|
},
|
|||
|
|
|||
|
// Decode hexadecimal string into ArrayBuffer:
|
|||
|
// Usage example: CBOR.decode(HEX.decode("a16161182a")) = {a: 42}
|
|||
|
decode: function (s) {
|
|||
|
if (!this.to_hex_array.length) this.init();
|
|||
|
var length2 = s.length;
|
|||
|
if ((length2 % 2) != 0) {
|
|||
|
console.error("HEX.decode: string must have length a multiple of 2");
|
|||
|
return null;
|
|||
|
}
|
|||
|
var length = length2 / 2;
|
|||
|
var buffer = new Uint8Array(length);
|
|||
|
for (var i=0; i<length; i++) {
|
|||
|
var i2 = i * 2;
|
|||
|
var b = s.substring(i2, i2 + 2);
|
|||
|
buffer[i] = this.to_byte_map[b];
|
|||
|
}
|
|||
|
return buffer.buffer;
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* AJAX Pages & Commands
|
|||
|
*/
|
|||
|
|
|||
|
var page = {
|
|||
|
uri: null,
|
|||
|
path: null,
|
|||
|
search: null,
|
|||
|
params: {}
|
|||
|
};
|
|||
|
|
|||
|
function setPage(uri) {
|
|||
|
page.uri = uri;
|
|||
|
var uriparts = uri.split("?");
|
|||
|
page.path = uriparts[0];
|
|||
|
page.search = uriparts[1];
|
|||
|
page.params = {};
|
|||
|
if (page.search) {
|
|||
|
page.search.split("&").map(function(kv) {
|
|||
|
var v = kv.split("=");
|
|||
|
page.params[decodeURIComponent(v[0])] = (v[1] != null) ? decodeURIComponent(v[1]) : true;
|
|||
|
});
|
|||
|
}
|
|||
|
if (page.params["nm"] == 1) $("body").addClass("night");
|
|||
|
else if (page.params["nm"] == 0) $("body").removeClass("night");
|
|||
|
}
|
|||
|
|
|||
|
function updateLocation(reload) {
|
|||
|
page.search = '';
|
|||
|
for (v in page.params)
|
|||
|
page.search += "&" + encodeURIComponent(v) + "=" + encodeURIComponent(page.params[v]);
|
|||
|
page.search = page.search.slice(1);
|
|||
|
var hash = "#" + page.path + (page.search ? "?" + page.search : "");
|
|||
|
if (!reload) $("#main").data("uri", hash.substr(1));
|
|||
|
location.hash = hash;
|
|||
|
}
|
|||
|
|
|||
|
function readLocation() {
|
|||
|
var uri = location.hash.substr(1);
|
|||
|
if (!uri.match("^/?[a-zA-Z0-9_]"))
|
|||
|
uri = "/home";
|
|||
|
return uri;
|
|||
|
}
|
|||
|
|
|||
|
function loadPage(uri) {
|
|||
|
if (typeof uri != "string")
|
|||
|
uri = readLocation();
|
|||
|
if ($("#main").data("uri") != uri) {
|
|||
|
loaduri("#main", "get", uri, {});
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function reloadpage() {
|
|||
|
var uri = readLocation();
|
|||
|
loaduri("#main", "get", uri, {});
|
|||
|
}
|
|||
|
|
|||
|
function reloadmenu() {
|
|||
|
$("#menu").load("/menu");
|
|||
|
}
|
|||
|
|
|||
|
function login(dsturi) {
|
|||
|
if (!dsturi)
|
|||
|
dsturi = page.uri || "/home";
|
|||
|
loadPage("/login?uri=" + encodeURIComponent(dsturi));
|
|||
|
}
|
|||
|
|
|||
|
function logout() {
|
|||
|
loadPage("/logout");
|
|||
|
}
|
|||
|
|
|||
|
function xhrErrorInfo(request, textStatus, errorThrown) {
|
|||
|
var txt = "";
|
|||
|
if (request.status == 401 || request.status == 403)
|
|||
|
txt = "Unauthorized. <a class=\"btn btn-sm btn-default\" href=\"javascript:login()\">Login</a>";
|
|||
|
else if (request.status >= 400)
|
|||
|
txt = "Error " + request.status + " " + request.statusText;
|
|||
|
else if (textStatus)
|
|||
|
txt = "Request " + textStatus + ", please retry";
|
|||
|
else if (errorThrown)
|
|||
|
txt = errorThrown;
|
|||
|
return txt;
|
|||
|
}
|
|||
|
|
|||
|
function setcontent(tgt, uri, text){
|
|||
|
if (!tgt || !tgt.length) return;
|
|||
|
|
|||
|
tgt.find(".receiver").unsubscribe();
|
|||
|
tgt.chart("destroy");
|
|||
|
tgt.table("destroy");
|
|||
|
|
|||
|
if (tgt[0].id == "main") {
|
|||
|
$("#nav .dropdown.open .dropdown-toggle").dropdown("toggle");
|
|||
|
$("#nav .navbar-collapse").collapse("hide");
|
|||
|
$("#nav li").removeClass("active");
|
|||
|
var mi = $("#nav [href='"+uri+"']");
|
|||
|
mi.parents("li").addClass("active");
|
|||
|
if (tgt[0].id == "main")
|
|||
|
window.scrollTo(0,0);
|
|||
|
else
|
|||
|
tgt[0].scrollIntoView();
|
|||
|
tgt.html(text);
|
|||
|
var $p = tgt.find(">.panel");
|
|||
|
if ($p.length == 1) $p.addClass("panel-single");
|
|||
|
var moduleid = $("title").data("moduleid") || "OVMS";
|
|||
|
if (mi.length > 0)
|
|||
|
document.title = moduleid + " " + (mi.attr("title") || mi.text());
|
|||
|
else
|
|||
|
document.title = moduleid + " Console";
|
|||
|
} else {
|
|||
|
if (tgt[0].id == "main")
|
|||
|
window.scrollTo(0,0);
|
|||
|
else
|
|||
|
tgt[0].scrollIntoView();
|
|||
|
tgt.html(text);
|
|||
|
}
|
|||
|
|
|||
|
tgt.find(".get-window-resize").trigger('window-resize');
|
|||
|
tgt.find(".receiver").subscribe().trigger("msg:metrics", metrics);
|
|||
|
tgt.trigger("load");
|
|||
|
}
|
|||
|
|
|||
|
function loaduri(target, method, uri, data){
|
|||
|
var tgt = $(target), cont;
|
|||
|
if (tgt.length != 1)
|
|||
|
return false;
|
|||
|
|
|||
|
tgt.data("uri", uri);
|
|||
|
if (tgt[0].id == "main") {
|
|||
|
cont = $("html");
|
|||
|
location.hash = "#" + uri;
|
|||
|
setPage(uri);
|
|||
|
} else {
|
|||
|
cont = tgt.closest(".modal") || tgt.closest("form") || tgt;
|
|||
|
}
|
|||
|
|
|||
|
$.ajax({ "type": method, "url": uri, "data": data,
|
|||
|
"timeout": 15000,
|
|||
|
"beforeSend": function(){
|
|||
|
cont.addClass("loading");
|
|||
|
},
|
|||
|
"complete": function(){
|
|||
|
cont.removeClass("loading");
|
|||
|
},
|
|||
|
"success": function(response){
|
|||
|
setcontent(tgt, uri, response);
|
|||
|
},
|
|||
|
"error": function(response, xhrerror, httperror){
|
|||
|
var text = response.responseText || httperror+"\n" || xhrerror+"\n";
|
|||
|
if (text.search("alert") == -1) {
|
|||
|
text = '<div id="alert" class="alert alert-danger alert-dismissable">'
|
|||
|
+ '<a href="#" class="close" data-dismiss="alert" aria-label="close">×</a>'
|
|||
|
+ '<strong>' + text + '</strong>'
|
|||
|
+ '</div>';
|
|||
|
}
|
|||
|
setcontent(tgt, uri, text);
|
|||
|
},
|
|||
|
});
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
$.fn.loaduri = function(method, uri, data) {
|
|||
|
return this.each(function() {
|
|||
|
loaduri(this, method, uri, data);
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
function standardTextFilter(msg) {
|
|||
|
if (msg.error)
|
|||
|
return '<div class="bg-danger">'+msg.error+'</div>';
|
|||
|
else
|
|||
|
return $('<div/>').text(msg.text).html();
|
|||
|
}
|
|||
|
|
|||
|
function loadjs(command, target, filter, timeout) {
|
|||
|
if (!command) return null;
|
|||
|
var args = {};
|
|||
|
if (typeof command == "object") {
|
|||
|
args = command;
|
|||
|
} else {
|
|||
|
args.command = command;
|
|||
|
}
|
|||
|
args.type = "js";
|
|||
|
return loadcmd(args, target, filter, timeout);
|
|||
|
}
|
|||
|
|
|||
|
function loadcmd(command, target, filter, timeout) {
|
|||
|
var $output, outmode = "", args = {};
|
|||
|
|
|||
|
if (!command) return null;
|
|||
|
if (typeof command == "object") {
|
|||
|
args = command;
|
|||
|
} else {
|
|||
|
args.command = command;
|
|||
|
}
|
|||
|
|
|||
|
if (typeof filter == "number") {
|
|||
|
timeout = filter; filter = null;
|
|||
|
}
|
|||
|
if (typeof target == "function") {
|
|||
|
filter = target; target = null;
|
|||
|
}
|
|||
|
|
|||
|
if (target == null) {
|
|||
|
$output = $(null);
|
|||
|
}
|
|||
|
else if (typeof target == "object") {
|
|||
|
$output = target;
|
|||
|
}
|
|||
|
else if (target.startsWith("+")) {
|
|||
|
outmode = "+";
|
|||
|
$output = $(target.substr(1));
|
|||
|
} else {
|
|||
|
$output = $(target);
|
|||
|
}
|
|||
|
|
|||
|
if (!filter)
|
|||
|
filter = standardTextFilter;
|
|||
|
|
|||
|
if (!timeout) {
|
|||
|
if (args["type"] != "js" && /^(test |ota |copen .* scan)/.test(args.command))
|
|||
|
timeout = 300;
|
|||
|
else
|
|||
|
timeout = 20;
|
|||
|
}
|
|||
|
|
|||
|
var lastlen = 0, xhr, timeouthd;
|
|||
|
var checkabort = function() {
|
|||
|
if (xhr.readyState != 4)
|
|||
|
xhr.abort("timeout");
|
|||
|
};
|
|||
|
var add_output = function(addhtml) {
|
|||
|
if (addhtml == null || !$output.length) {
|
|||
|
outmode = "+";
|
|||
|
return;
|
|||
|
}
|
|||
|
if (outmode == "") { $output.empty(); outmode = "+"; }
|
|||
|
var autoscroll = ($output.get(0).scrollTop + $output.innerHeight()) >= $output.get(0).scrollHeight;
|
|||
|
$output.append(addhtml);
|
|||
|
$output.closest('.get-window-resize').trigger('window-resize');
|
|||
|
if (autoscroll) $output.scrollTop($output.get(0).scrollHeight);
|
|||
|
};
|
|||
|
|
|||
|
xhr = $.ajax({ "type": "post", "url": "/api/execute", "data": args,
|
|||
|
"timeout": 0,
|
|||
|
"beforeSend": function() {
|
|||
|
if ($output.length) {
|
|||
|
$output.addClass("loading");
|
|||
|
fix_minheight($output);
|
|||
|
}
|
|||
|
timeouthd = window.setTimeout(checkabort, timeout*1000);
|
|||
|
},
|
|||
|
"complete": function() {
|
|||
|
window.clearTimeout(timeouthd);
|
|||
|
if ($output.length) {
|
|||
|
$output.removeClass("loading");
|
|||
|
fix_minheight($output);
|
|||
|
}
|
|||
|
},
|
|||
|
"xhrFields": {
|
|||
|
onprogress: function(ev) {
|
|||
|
var request = ev.currentTarget;
|
|||
|
if (request.status != 200) return;
|
|||
|
var addtext = request.response.substring(lastlen);
|
|||
|
lastlen = request.response.length;
|
|||
|
add_output(filter({ "request": request, "text": addtext }));
|
|||
|
window.clearTimeout(timeouthd);
|
|||
|
timeouthd = window.setTimeout(checkabort, timeout*1000);
|
|||
|
},
|
|||
|
},
|
|||
|
"success": function(response, textStatus, request) {
|
|||
|
var addtext = response.substring(lastlen);
|
|||
|
add_output(filter({ "request": request, "text": addtext }));
|
|||
|
},
|
|||
|
"error": function(request, textStatus, errorThrown) {
|
|||
|
var cmdinfo = (args.command.length > 200) ? args.command.substr(0,200)+"[…]" : args.command;
|
|||
|
console.log("loadcmd '" + cmdinfo + "' ERROR: status=" + textStatus + ", httperror=" + errorThrown);
|
|||
|
var txt = xhrErrorInfo(request, textStatus, errorThrown);
|
|||
|
add_output(filter({ "request": request, "error": txt }));
|
|||
|
},
|
|||
|
});
|
|||
|
|
|||
|
return xhr;
|
|||
|
}
|
|||
|
|
|||
|
$.fn.loadcmd = function(command, filter, timeout) {
|
|||
|
return this.each(function() {
|
|||
|
loadcmd(command, $(this), filter, timeout);
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* WebSocket Connection
|
|||
|
*/
|
|||
|
|
|||
|
var monitorTimer, last_monotonic = 0;
|
|||
|
var ws, ws_inhibit = 0;
|
|||
|
var metrics = {};
|
|||
|
var shellhist = [""], shellhpos = 0;
|
|||
|
var loghist = [];
|
|||
|
const loghist_maxsize = 100;
|
|||
|
|
|||
|
function initSocketConnection(){
|
|||
|
if (location.protocol == "https:") {
|
|||
|
ws = new WebSocket('wss://' + location.host + '/msg');
|
|||
|
} else {
|
|||
|
ws = new WebSocket('ws://' + location.host + '/msg');
|
|||
|
}
|
|||
|
ws.onopen = function(ev) {
|
|||
|
console.log("WebSocket OPENED", ev);
|
|||
|
$(".receiver").subscribe();
|
|||
|
};
|
|||
|
ws.onerror = function(ev) { console.log("WebSocket ERROR", ev); };
|
|||
|
ws.onclose = function(ev) { console.log("WebSocket CLOSED", ev); };
|
|||
|
ws.onmessage = function(ev) {
|
|||
|
var msg;
|
|||
|
try {
|
|||
|
msg = JSON.parse(ev.data);
|
|||
|
} catch (e) {
|
|||
|
console.error("WebSocket msg: " + e + ": " + ev.data);
|
|||
|
return;
|
|||
|
}
|
|||
|
for (msgtype in msg) {
|
|||
|
if (msgtype == "event") {
|
|||
|
$(".receiver").trigger("msg:event", msg.event);
|
|||
|
$(".monitor[data-events]").each(function(){
|
|||
|
var cmd = $(this).data("updcmd");
|
|||
|
var js = $(this).data("updjs");
|
|||
|
var evf = $(this).data("events");
|
|||
|
if ((cmd || js) && evf && msg.event.match(evf)) {
|
|||
|
$(this).data("updlast", now());
|
|||
|
loadcmd({ command: js ? js : cmd, type: js ? "js" : "cmd" }, $(this));
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
else if (msgtype == "metrics") {
|
|||
|
$.extend(metrics, msg.metrics);
|
|||
|
$(".receiver").trigger("msg:metrics", msg.metrics);
|
|||
|
}
|
|||
|
else if (msgtype == "notify") {
|
|||
|
processNotification(msg.notify);
|
|||
|
$(".receiver").trigger("msg:notify", msg.notify);
|
|||
|
}
|
|||
|
else if (msgtype == "log") {
|
|||
|
loghist.push(msg.log);
|
|||
|
if (loghist.length > loghist_maxsize) loghist.shift();
|
|||
|
$(".receiver").trigger("msg:log", msg.log);
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
function monitorInit(force){
|
|||
|
$(".monitor").each(function(){
|
|||
|
var cmd = $(this).data("updcmd");
|
|||
|
var js = $(this).data("updjs");
|
|||
|
var txt = $(this).text();
|
|||
|
if ((cmd || js) && (force || !txt)) {
|
|||
|
$(this).data("updlast", now());
|
|||
|
loadcmd({ command: js ? js : cmd, type: js ? "js" : "cmd" }, $(this));
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
function monitorUpdate(){
|
|||
|
if (!ws || ws.readyState == ws.CLOSED){
|
|||
|
if (ws_inhibit != 0)
|
|||
|
--ws_inhibit;
|
|||
|
if (ws_inhibit == 0)
|
|||
|
initSocketConnection();
|
|||
|
}
|
|||
|
var new_monotonic = parseInt(metrics["m.monotonic"]) || 0;
|
|||
|
if (new_monotonic < last_monotonic)
|
|||
|
location.reload();
|
|||
|
else
|
|||
|
last_monotonic = new_monotonic;
|
|||
|
$(".monitor").each(function(){
|
|||
|
var cnt = $(this).data("updcnt");
|
|||
|
var int = $(this).data("updint");
|
|||
|
var last = $(this).data("updlast");
|
|||
|
var cmd = $(this).data("updcmd");
|
|||
|
var js = $(this).data("updjs");
|
|||
|
if (!cnt || (!cmd && !js) || (now()-last) < int)
|
|||
|
return;
|
|||
|
$(this).data("updcnt", cnt-1);
|
|||
|
$(this).data("updlast", now());
|
|||
|
loadcmd({ command: js ? js : cmd, type: js ? "js" : "cmd" }, $(this));
|
|||
|
});
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
function processNotification(msg) {
|
|||
|
var opts = { timeout: 0 };
|
|||
|
if (msg.type == "info") {
|
|||
|
opts.title = '<span class="lead text-info"><i>ⓘ</i> ' + msg.subtype + ' Info</span>';
|
|||
|
opts.timeout = 60;
|
|||
|
}
|
|||
|
else if (msg.type == "alert") {
|
|||
|
opts.title = '<span class="lead text-danger"><i>⚠</i> ' + msg.subtype + ' Alert</span>';
|
|||
|
}
|
|||
|
else if (msg.type == "error") {
|
|||
|
opts.title = '<span class="lead text-warning"><i>⛍</i> ' + msg.subtype + ' Error</span>';
|
|||
|
}
|
|||
|
else
|
|||
|
return;
|
|||
|
opts.body = '<pre>' + msg.value + '</pre>';
|
|||
|
confirmdialog(opts.title, opts.body, ["OK"], opts.timeout);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
$.fn.subscribe = function(topics) {
|
|||
|
return this.each(function() {
|
|||
|
var subscriptions = $(this).data("subscriptions");
|
|||
|
if (!topics) {
|
|||
|
// init from data attr:
|
|||
|
topics = subscriptions;
|
|||
|
subscriptions = "";
|
|||
|
}
|
|||
|
var subs = subscriptions ? subscriptions.split(' ') : [];
|
|||
|
var tops = topics ? topics.split(' ') : [];
|
|||
|
for (var i = 0; i < tops.length; i++) {
|
|||
|
if (tops[i] && !subs.includes(tops[i])) {
|
|||
|
try {
|
|||
|
console.log("subscribe " + tops[i]);
|
|||
|
if (ws) ws.send("subscribe " + tops[i]);
|
|||
|
subs.push(tops[i]);
|
|||
|
} catch (e) {
|
|||
|
console.log(e);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
$(this).data("subscriptions", subs.join(' '));
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
$.fn.unsubscribe = function(topics) {
|
|||
|
return this.each(function() {
|
|||
|
var subscriptions = $(this).data("subscriptions");
|
|||
|
if (!topics) {
|
|||
|
// cleanup:
|
|||
|
topics = subscriptions;
|
|||
|
}
|
|||
|
var subs = subscriptions ? subscriptions.split(' ') : [];
|
|||
|
var tops = topics ? topics.split(' ') : [];
|
|||
|
var i, j;
|
|||
|
for (i = 0; i < tops.length; i++) {
|
|||
|
if (tops[i] && (j = subs.indexOf(tops[i])) >= 0) {
|
|||
|
try {
|
|||
|
console.log("unsubscribe " + tops[i]);
|
|||
|
if (ws) ws.send("unsubscribe " + tops[i]);
|
|||
|
subs.splice(j, 1);
|
|||
|
} catch (e) {
|
|||
|
console.log(e);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
$(this).data("subscriptions", subs.join(' '));
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
$.fn.reconnectTicker = function(msg) {
|
|||
|
$("html").addClass("loading disabled");
|
|||
|
ws_inhibit = 10;
|
|||
|
if (ws) ws.close();
|
|||
|
window.setInterval(function(){
|
|||
|
if (ws && ws.readyState == ws.OPEN)
|
|||
|
location.reload();
|
|||
|
else
|
|||
|
$("#reconnectTickerDots").append("•");
|
|||
|
}, 1000);
|
|||
|
return this.append(
|
|||
|
(msg ? msg : '<p class="lead">Rebooting now…</p>') +
|
|||
|
'<p>The window will automatically reload when the browser reconnects to the module.</p>' +
|
|||
|
'<p id="reconnectTickerDots">•</p>');
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* UI Widgets
|
|||
|
*/
|
|||
|
|
|||
|
// Plugin Maker
|
|||
|
// credits: https://www.bitovi.com/blog/writing-the-perfect-jquery-plugin
|
|||
|
$.pluginMaker = function(plugin) {
|
|||
|
$.fn[plugin.prototype.cname] = function(options) {
|
|||
|
var args = $.makeArray(arguments), after = args.slice(1);
|
|||
|
var ismethod = (typeof options == "string");
|
|||
|
var result;
|
|||
|
this.each(function() {
|
|||
|
// see if we have an instance
|
|||
|
var instance = $.data(this, plugin.prototype.cname);
|
|||
|
if (instance) {
|
|||
|
if (ismethod) {
|
|||
|
// call a method on the instance
|
|||
|
if ($.isFunction(instance[options]))
|
|||
|
result = instance[options].apply(instance, after);
|
|||
|
else
|
|||
|
throw "UndefinedMethod: " + plugin.prototype.cname + "." + options;
|
|||
|
} else if (instance.update) {
|
|||
|
// call update on the instance
|
|||
|
instance.update.apply(instance, args);
|
|||
|
}
|
|||
|
} else {
|
|||
|
// create the plugin
|
|||
|
new plugin(this, options);
|
|||
|
}
|
|||
|
});
|
|||
|
return (ismethod && result != undefined) ? result : this;
|
|||
|
};
|
|||
|
};
|
|||
|
|
|||
|
// OVMS namespace:
|
|||
|
var ovms = {uid:0};
|
|||
|
|
|||
|
// Widget root class:
|
|||
|
ovms.Widget = function(el, options) {
|
|||
|
if (el) this.init(el, options);
|
|||
|
}
|
|||
|
$.extend(ovms.Widget.prototype, {
|
|||
|
cname: "widget",
|
|||
|
options: {},
|
|||
|
init: function(el, options) {
|
|||
|
this.uid = ++ovms.uid;
|
|||
|
this.$el = $(el);
|
|||
|
this.$el.data(this.cname, this);
|
|||
|
this.$el.addClass(this.cname);
|
|||
|
var dataoptions = this.$el.data("options");
|
|||
|
if (dataoptions != null && typeof dataoptions != "object") {
|
|||
|
console.error("Invalid JSON syntax: " + this.$el.data("options"));
|
|||
|
dataoptions = null;
|
|||
|
}
|
|||
|
this.options = $.extend({}, this.options, dataoptions, options);
|
|||
|
},
|
|||
|
update: function(options) {
|
|||
|
$.extend(this.options, options);
|
|||
|
},
|
|||
|
});
|
|||
|
|
|||
|
// Dialog:
|
|||
|
ovms.Dialog = function(el, options) {
|
|||
|
if (el) this.init(el, options);
|
|||
|
}
|
|||
|
$.extend(ovms.Dialog.prototype, ovms.Widget.prototype, {
|
|||
|
cname: "dialog",
|
|||
|
|
|||
|
options: {
|
|||
|
title: '',
|
|||
|
body: '',
|
|||
|
show: false,
|
|||
|
remote: false,
|
|||
|
backdrop: true,
|
|||
|
keyboard: true,
|
|||
|
transition: 'fade',
|
|||
|
size: '',
|
|||
|
contentClass: '',
|
|||
|
onShow: null,
|
|||
|
onHide: null,
|
|||
|
onShown: null,
|
|||
|
onHidden: null,
|
|||
|
onUpdate: null,
|
|||
|
buttons: [{}],
|
|||
|
timeout: 0,
|
|||
|
input: null,
|
|||
|
},
|
|||
|
|
|||
|
init: function(el, options) {
|
|||
|
if ($(el).parent().length == 0) {
|
|||
|
options = $.extend(options, { show: true, isDynamic: true });
|
|||
|
}
|
|||
|
ovms.Widget.prototype.init.call(this, el, options);
|
|||
|
this.input = options.input ? options.input : {};
|
|||
|
this.data = { showing: false };
|
|||
|
this.$buttons = [];
|
|||
|
// convert element to modal if not predefined by user:
|
|||
|
if (this.$el.children().length == 0) {
|
|||
|
this.$el.html('<div class="modal-dialog"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button><h4 class="modal-title"></h4></div><div class="modal-body"></div><div class="modal-footer"></div></div></div></div>');
|
|||
|
this.$el.addClass("modal");
|
|||
|
}
|
|||
|
this.$el.on('show.bs.modal', $.proxy(this.onShow, this));
|
|||
|
this.$el.on('hide.bs.modal', $.proxy(this.onHide, this));
|
|||
|
this.$el.on('shown.bs.modal', $.proxy(this.onShown, this));
|
|||
|
this.$el.on('hidden.bs.modal', $.proxy(this.onHidden, this));
|
|||
|
if (this.options.isDynamic) this.$el.appendTo('body');
|
|||
|
this.update(this.options);
|
|||
|
},
|
|||
|
|
|||
|
update: function(options) {
|
|||
|
$.extend(this.options, options);
|
|||
|
// configure modal:
|
|||
|
this.$el.removeClass('fade').addClass(this.options.transition);
|
|||
|
this.$el.find('.modal-dialog').attr("class", "modal-dialog " + (this.options.size ? ("modal-" + this.options.size) : ""));
|
|||
|
this.$el.find('.modal-content').attr("class", "modal-content " + (this.options.contentClass || ""));
|
|||
|
if (options.title != null)
|
|||
|
this.$el.find('.modal-title').html(options.title);
|
|||
|
if (options.body != null)
|
|||
|
this.$el.find('.modal-body').html(options.body);
|
|||
|
if (options.buttons != null) {
|
|||
|
var footer = this.$el.find('.modal-footer');
|
|||
|
footer.empty();
|
|||
|
this.$buttons = [];
|
|||
|
for (var i = 0; i < this.options.buttons.length; i++) {
|
|||
|
var btn = $.extend({ label: 'Close', btnClass: 'default', autoHide: true, action: null }, this.options.buttons[i], { index: i });
|
|||
|
this.options.buttons[i] = btn;
|
|||
|
this.$buttons[i] = $('<button type="button" class="btn btn-'+btn.btnClass+'" value="'+btn.index+'">'+btn.label+'</button>')
|
|||
|
.appendTo(footer).on('click', $.proxy(this.onClick, this, btn));
|
|||
|
}
|
|||
|
}
|
|||
|
if (this.options.onUpdate)
|
|||
|
this.options.onUpdate.call(this.$el, this.input);
|
|||
|
this.$el.modal(this.options);
|
|||
|
},
|
|||
|
|
|||
|
show: function(options) {
|
|||
|
if (options) this.update(options);
|
|||
|
this.$el.modal('show');
|
|||
|
},
|
|||
|
hide: function() {
|
|||
|
this.$el.modal('hide');
|
|||
|
},
|
|||
|
|
|||
|
onShow: function() {
|
|||
|
if (this.options.onShow)
|
|||
|
this.options.onShow.call(this.$el, this.input);
|
|||
|
},
|
|||
|
|
|||
|
onHide: function() {
|
|||
|
if (this.options.onHide)
|
|||
|
this.options.onHide.call(this.$el, this.input);
|
|||
|
},
|
|||
|
|
|||
|
onShown: function() {
|
|||
|
this.data.showing = true;
|
|||
|
this.input.button = null;
|
|||
|
if (!supportsTouch) this.$el.find('.form-control, .btn').first().focus();
|
|||
|
if (this.options.onShown)
|
|||
|
this.options.onShown.call(this.$el, this.input);
|
|||
|
if (this.options.timeout)
|
|||
|
after(this.options.timeout, $.proxy(this.hide, this));
|
|||
|
},
|
|||
|
|
|||
|
onHidden: function() {
|
|||
|
this.data.showing = false;
|
|||
|
if (this.options.isDynamic)
|
|||
|
this.$el.detach();
|
|||
|
if (this.options.onHidden)
|
|||
|
this.options.onHidden.call(this.$el, this.input);
|
|||
|
if (this.input.button != null && this.input.button.action) {
|
|||
|
this.input.button.action.call(this.$el, this.input);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
isShowing: function() {
|
|||
|
return this.data.showing;
|
|||
|
},
|
|||
|
|
|||
|
triggerButton: function(button) {
|
|||
|
var btn;
|
|||
|
if (typeof button == "number") {
|
|||
|
btn = this.$buttons[button];
|
|||
|
}
|
|||
|
else if (typeof button == "string") {
|
|||
|
for (var i = 0; i < this.options.buttons.length; i++) {
|
|||
|
if (this.options.buttons[i].label == button) {
|
|||
|
btn = this.$buttons[i];
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (btn && !btn.prop("disabled")) {
|
|||
|
btn.trigger('click');
|
|||
|
return true;
|
|||
|
}
|
|||
|
return false;
|
|||
|
},
|
|||
|
|
|||
|
onClick: function(button) {
|
|||
|
this.input.button = button;
|
|||
|
if (button.autoHide)
|
|||
|
this.hide();
|
|||
|
else if (button.action) {
|
|||
|
button.action.call(this.$el, this.input);
|
|||
|
this.input.button = null;
|
|||
|
}
|
|||
|
},
|
|||
|
});
|
|||
|
$.pluginMaker(ovms.Dialog);
|
|||
|
|
|||
|
|
|||
|
// Dialog utility wrappers:
|
|||
|
|
|||
|
$.fn.confirmdialog = function(_title, _body, _buttons, _action, _timeout) {
|
|||
|
if (typeof _action == "number") { _timeout = _action; _action = null; }
|
|||
|
var options = { show: true, title: _title, body: _body, buttons: [], timeout: _timeout };
|
|||
|
if (_buttons) {
|
|||
|
for (var i = 0; i < _buttons.length; i++) {
|
|||
|
options.buttons.push({ label: _buttons[i],
|
|||
|
btnClass: (_buttons.length <= 2 && i==_buttons.length-1) ? "primary" : "default" });
|
|||
|
}
|
|||
|
}
|
|||
|
if (_action) {
|
|||
|
options.onHidden = function(input) { _action(input.button ? input.button.index : null); };
|
|||
|
}
|
|||
|
return this.dialog(options);
|
|||
|
};
|
|||
|
var confirmdialog = function() { return $.fn.confirmdialog.apply($('<div />'), arguments); };
|
|||
|
|
|||
|
$.fn.promptdialog = function(_type, _title, _prompt, _buttons, _action) {
|
|||
|
var options = { show: true, title: _title, buttons: [] };
|
|||
|
var uid = ++ovms.uid;
|
|||
|
options.body = '<label for="prompt'+uid+'">'+_prompt+'</label><input id="prompt'+uid+'" type="'+_type+'" class="form-control">';
|
|||
|
if (_buttons) {
|
|||
|
for (var i = 0; i < _buttons.length; i++) {
|
|||
|
options.buttons.push({ label: _buttons[i],
|
|||
|
btnClass: (_buttons.length <= 2 && i==_buttons.length-1) ? "primary" : "default" });
|
|||
|
}
|
|||
|
}
|
|||
|
options.onUpdate = function(input) {
|
|||
|
var footer = $(this).find('.modal-footer');
|
|||
|
$(this).find('input').val(input.text).on('keydown', function(ev) {
|
|||
|
if (ev.which == 13) footer.find('.btn-primary').trigger('click');
|
|||
|
});
|
|||
|
};
|
|||
|
options.onShown = function(input) {
|
|||
|
$(this).find('input').first().focus();
|
|||
|
}
|
|||
|
options.onHidden = function(input) {
|
|||
|
if (input.button) input.text = $(this).find('input').val();
|
|||
|
if (_action) _action(input.button ? input.button.index : null, input.text);
|
|||
|
};
|
|||
|
return this.dialog(options);
|
|||
|
};
|
|||
|
var promptdialog = function() { return $.fn.promptdialog.apply($('<div />'), arguments); };
|
|||
|
|
|||
|
|
|||
|
// FileBrowser:
|
|||
|
ovms.FileBrowser = function(el, options) {
|
|||
|
if (el) this.init(el, options);
|
|||
|
}
|
|||
|
$.extend(ovms.FileBrowser.prototype, ovms.Widget.prototype, {
|
|||
|
cname: "filebrowser",
|
|||
|
|
|||
|
options: {
|
|||
|
path: '',
|
|||
|
quicknav: ['/sd/', '/store/'],
|
|||
|
filter: null,
|
|||
|
sortBy: null,
|
|||
|
sortDir: 1,
|
|||
|
onUpdate: null,
|
|||
|
onPathChange: null,
|
|||
|
onAction: null,
|
|||
|
input: null,
|
|||
|
},
|
|||
|
|
|||
|
init: function(el, options) {
|
|||
|
ovms.Widget.prototype.init.call(this, el, options);
|
|||
|
|
|||
|
this.input = options.input ? options.input : {};
|
|||
|
$.extend(this.input, {
|
|||
|
path: '',
|
|||
|
dir: '',
|
|||
|
file: '',
|
|||
|
noload: false,
|
|||
|
});
|
|||
|
this.data = {
|
|||
|
lastpath: '',
|
|||
|
lastdir: '',
|
|||
|
listdir: '',
|
|||
|
listsel: '',
|
|||
|
};
|
|||
|
this.xhr = null;
|
|||
|
|
|||
|
if (this.$el.children().length == 0) {
|
|||
|
this.$el.html('<div class="fb-pathbox form-group"><label class="control-label" for="input-path-${uid}">Path (trailing slash = dir):</label><div class="input-group"><input type="text" class="form-control font-monospace" name="path" id="input-path-${uid}" value=""><div class="input-group-btn"><button type="button" class="btn btn-default fb-path-stop" disabled title="Stop">×</button><button type="button" class="btn btn-default fb-path-reload" title="Reload">⟲</button><button type="button" class="btn btn-default fb-path-up" title="Up">↰</button></div></div></div><div class="fb-quicknav form-group"/><div class="fb-files"><table class="table table-condensed table-hover table-scrollable font-monospace get-window-resize"><thead><tr><th class="col-xs-3 col-sm-2" data-key="size">Size</th><th class="hidden-xs col-sm-4" data-key="date">Date</th><th class="col-xs-9 col-sm-6" data-key="name">Name</th></tr></thead><tbody/></table></div>'.replace(/\$\{uid\}/g, this.uid));
|
|||
|
}
|
|||
|
|
|||
|
this.$pathinput = this.$el.find("input[name=path]");
|
|||
|
this.$pathinput.on('change', $.proxy(this.setPath, this)).on('keydown', $.proxy(function(ev) {
|
|||
|
if (ev.which == 13) {
|
|||
|
var lastdir = this.input.dir;
|
|||
|
this.setPath();
|
|||
|
if (this.input.file || this.input.dir == lastdir)
|
|||
|
this.onAction();
|
|||
|
ev.preventDefault();
|
|||
|
}
|
|||
|
else if (ev.which == 27) {
|
|||
|
this.stopLoad();
|
|||
|
}
|
|||
|
}, this));
|
|||
|
this.$el.find('.fb-path-up').on('click', $.proxy(function(ev) {
|
|||
|
var parent = this.input.dir.replace(/\/[^/]+$/, '');
|
|||
|
if (parent) this.setPath(parent+'/');
|
|||
|
else this.setPath(this.input.dir+'/');
|
|||
|
}, this));
|
|||
|
this.$el.find('.fb-path-reload').on('click', $.proxy(function(ev) {
|
|||
|
this.setPath(this.input.path, true);
|
|||
|
}, this));
|
|||
|
this.$btnstop = this.$el.find('.fb-path-stop');
|
|||
|
this.$btnstop.on('click', $.proxy(function(ev) { this.stopLoad(); }, this));
|
|||
|
|
|||
|
this.$quicknav = this.$el.find(".fb-quicknav");
|
|||
|
|
|||
|
this.$filetable = this.$el.find(".fb-files>table");
|
|||
|
this.$filecols = this.$filetable.find("thead th");
|
|||
|
this.$filecols.on('click', $.proxy(function(ev) {
|
|||
|
var by = $(ev.currentTarget).data("key");
|
|||
|
var dir = $(ev.currentTarget).hasClass('sort-down') ? -1 : 1;
|
|||
|
if (by == this.options.sortBy) dir = -dir;
|
|||
|
this.sortList(by, dir);
|
|||
|
if (!supportsTouch) this.$pathinput.focus();
|
|||
|
}, this));
|
|||
|
|
|||
|
this.$filebody = this.$filetable.find("tbody");
|
|||
|
this.$filebody.on('click', 'tr', $.proxy(function(ev) {
|
|||
|
this.data.listsel = $(ev.currentTarget).data("name");
|
|||
|
this.setPath(this.data.listdir + "/" + this.data.listsel);
|
|||
|
ev.preventDefault();
|
|||
|
}, this)).on('dblclick', 'tr', $.proxy(this.onAction, this));
|
|||
|
|
|||
|
this.update(this.options);
|
|||
|
},
|
|||
|
|
|||
|
update: function(options) {
|
|||
|
$.extend(this.options, options);
|
|||
|
|
|||
|
this.$quicknav.empty();
|
|||
|
for (var i = 0; i < this.options.quicknav.length; i++) {
|
|||
|
this.$quicknav.append('<button type="button" class="btn btn-sm btn-default"><code>' + this.options.quicknav[i] + '</code></button>');
|
|||
|
}
|
|||
|
this.$quicknav.find("button").on('click', $.proxy(function(ev) {
|
|||
|
this.setPath($(ev.delegateTarget).text());
|
|||
|
}, this));
|
|||
|
|
|||
|
if (this.options.onUpdate)
|
|||
|
this.options.onUpdate.call(this.$el, this.input);
|
|||
|
|
|||
|
this.sortList(this.options.sortBy, this.options.sortDir);
|
|||
|
if (options.path !== undefined) {
|
|||
|
this.setPath(options.path);
|
|||
|
}
|
|||
|
else if (options.filter !== undefined) {
|
|||
|
this.loadDir();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
getInput: function() {
|
|||
|
return this.input;
|
|||
|
},
|
|||
|
|
|||
|
setPath: function(newpath, reload) {
|
|||
|
if (typeof newpath == "string") {
|
|||
|
this.input.path = newpath;
|
|||
|
this.$pathinput.val(newpath);
|
|||
|
} else {
|
|||
|
this.input.path = this.$pathinput.val();
|
|||
|
}
|
|||
|
this.input.dir = this.input.path.replace(/\/[^/]*$/, '');
|
|||
|
this.input.file = this.input.path.substr(this.input.dir.length+1);
|
|||
|
|
|||
|
var fn = "";
|
|||
|
if (this.input.dir == this.data.listdir)
|
|||
|
fn = this.input.path.substr(this.data.listdir.length+1);
|
|||
|
this.$filebody.children().each(function() {
|
|||
|
if ($(this).data("name") === fn) $(this).addClass("active");
|
|||
|
else $(this).removeClass("active");
|
|||
|
});
|
|||
|
|
|||
|
if (reload)
|
|||
|
this.data.lastdir = '';
|
|||
|
|
|||
|
if (this.input.path != this.data.lastpath) {
|
|||
|
this.input.noload = false;
|
|||
|
if (this.options.onPathChange)
|
|||
|
this.options.onPathChange.call(this.$el, this.input);
|
|||
|
this.data.lastpath = this.input.path;
|
|||
|
}
|
|||
|
|
|||
|
if (this.input.dir != this.data.lastdir) {
|
|||
|
if (this.input.noload) {
|
|||
|
this.stopLoad();
|
|||
|
this.data.lastdir = '';
|
|||
|
this.$filebody.empty();
|
|||
|
this.data.listdir = '';
|
|||
|
this.data.listsel = '';
|
|||
|
} else {
|
|||
|
this.loadDir();
|
|||
|
this.data.lastdir = this.input.dir;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!supportsTouch) this.$pathinput.focus();
|
|||
|
},
|
|||
|
|
|||
|
loadDir: function() {
|
|||
|
var self = this;
|
|||
|
var fbuf = "";
|
|||
|
|
|||
|
this.stopLoad();
|
|||
|
this.$filebody.empty();
|
|||
|
this.data.listdir = this.input.dir;
|
|||
|
this.data.listsel = '';
|
|||
|
if (!this.data.listdir)
|
|||
|
return;
|
|||
|
|
|||
|
this.$btnstop.prop('disabled', false);
|
|||
|
this.xhr = loadcmd("vfs ls '" + this.input.dir + "'", this.$filebody, function(msg) {
|
|||
|
if (msg.error) {
|
|||
|
if (msg.error.startsWith("Request abort"))
|
|||
|
return '<div class="bg-info">Stopped.</div>';
|
|||
|
else
|
|||
|
return '<div class="bg-danger">'+msg.error+'</div>';
|
|||
|
}
|
|||
|
|
|||
|
fbuf += msg.text;
|
|||
|
var lines = fbuf.split("\n");
|
|||
|
if (!lines || lines.length < 2) return "";
|
|||
|
fbuf = lines[lines.length-1];
|
|||
|
var res = '', f = {};
|
|||
|
for (var i = 0; i < lines.length-1; i++) {
|
|||
|
if (!lines[i]) continue;
|
|||
|
if (lines[i].startsWith("Error"))
|
|||
|
return '<div class="bg-danger">' + lines[i] + '</div>';
|
|||
|
f.size = lines[i].substring(0, 10).trim();
|
|||
|
f.date = lines[i].substring(10, 29).trim();
|
|||
|
f.name = lines[i].substring(29).trim();
|
|||
|
if (!f.name) {
|
|||
|
console.log("FileBrowser.loadDir: can't parse line: '" + lines[i] + "'");
|
|||
|
} else {
|
|||
|
f.path = self.data.listdir + '/' + f.name;
|
|||
|
f.isdir = (f.size == '[DIR]');
|
|||
|
f.bytes = self.sizeToBytes(f.size);
|
|||
|
f.isodate = self.dateToISO(f.date);
|
|||
|
f.class = "";
|
|||
|
if (self.options.filter && (
|
|||
|
(typeof self.options.filter == "string" && !f.isdir && !f.name.match(self.options.filter)) ||
|
|||
|
(typeof self.options.filter == "function" && !self.options.filter(f)))) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
if (f.name == self.input.file) {
|
|||
|
f.class += " active";
|
|||
|
self.data.listsel = f.name;
|
|||
|
} else {
|
|||
|
f.class = "";
|
|||
|
}
|
|||
|
res += '<tr data-name="' + encode_html(f.name) + '" data-size="' + f.bytes +
|
|||
|
'" data-date="' + f.isodate + '" class="' + f.class + '">' +
|
|||
|
'<td class="col-xs-3 col-sm-2">' + f.size +
|
|||
|
'</td><td class="hidden-xs col-sm-4">' + f.date +
|
|||
|
'</td><td class="col-xs-9 col-sm-6">' + encode_html(f.name) + '</td></tr>';
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!self.options.sortBy)
|
|||
|
return res;
|
|||
|
if (res) {
|
|||
|
var scrollpos = self.$filebody.get(0).scrollTop;
|
|||
|
self.$filebody.detach().append(res);
|
|||
|
self.sortList();
|
|||
|
self.$filebody.appendTo(self.$filetable).scrollTop(scrollpos);
|
|||
|
self.$filetable.trigger('window-resize');
|
|||
|
}
|
|||
|
return null;
|
|||
|
}).always($.proxy(function() {
|
|||
|
this.$btnstop.prop('disabled', true);
|
|||
|
}, this));
|
|||
|
},
|
|||
|
|
|||
|
sizeToBytes: function(size) {
|
|||
|
if (size == "[DIR]") return -1;
|
|||
|
var unit = size[size.length-1], val = parseFloat(size);
|
|||
|
if (unit == 'k') return val*1024;
|
|||
|
else if (unit == 'M') return val*1048576;
|
|||
|
else if (unit == 'G') return val*1073741824;
|
|||
|
else return val;
|
|||
|
},
|
|||
|
|
|||
|
dateToISO: function(date) {
|
|||
|
var month = monthnames.indexOf(date.substr(3,3)) + 1;
|
|||
|
return date.substr(7,4)+'-'+(month<10?'0':'')+month+'-'+date.substr(0,2)+' '+date.substr(12);
|
|||
|
},
|
|||
|
|
|||
|
stopLoad: function() {
|
|||
|
this.$btnstop.prop('disabled', true);
|
|||
|
if (this.xhr)
|
|||
|
this.xhr.abort();
|
|||
|
if (!supportsTouch) this.$pathinput.focus();
|
|||
|
},
|
|||
|
|
|||
|
sortList: function(by, dir) {
|
|||
|
if (by == null) {
|
|||
|
by = this.options.sortBy;
|
|||
|
dir = this.options.sortDir;
|
|||
|
} else {
|
|||
|
dir = dir || 1;
|
|||
|
this.options.sortBy = by;
|
|||
|
this.options.sortDir = dir;
|
|||
|
this.$filecols.removeClass('sort-up sort-down')
|
|||
|
.filter('[data-key="'+by+'"]').addClass((dir<0) ? 'sort-down' : 'sort-up');
|
|||
|
}
|
|||
|
if (!by) return;
|
|||
|
var rows = $.makeArray(this.$filebody.children());
|
|||
|
rows.sort(function(a,b){
|
|||
|
if (!a.dataset[by]) return 1; if (!b.dataset[by]) return -1;
|
|||
|
var pri = a.dataset[by].localeCompare(b.dataset[by]);
|
|||
|
var sec = (by!="name") ? a.dataset["name"].localeCompare(b.dataset["name"]) : 0;
|
|||
|
return dir * (pri ? pri : sec);
|
|||
|
});
|
|||
|
this.$filebody.html(rows);
|
|||
|
},
|
|||
|
|
|||
|
newDir: function() {
|
|||
|
this.stopLoad();
|
|||
|
var path = this.input.dir + "/";
|
|||
|
var self = this;
|
|||
|
promptdialog("text", "Create new directory", path + "… (empty = create this dir)", ["Cancel", "Create"], function(create, dirname) {
|
|||
|
if (create) {
|
|||
|
path = (path + dirname).replace(/\/+$/, "");
|
|||
|
$.post("/api/execute", { "command": "vfs mkdir '" + path + "'" }, function(result) {
|
|||
|
if (result.startsWith("Error"))
|
|||
|
confirmdialog("Error", result, ["OK"]);
|
|||
|
else
|
|||
|
self.setPath(path + "/", path == self.input.dir);
|
|||
|
}).fail(function(request, textStatus, errorThrown){
|
|||
|
confirmdialog("Error", xhrErrorInfo(request, textStatus, errorThrown), ["OK"]);
|
|||
|
});
|
|||
|
}
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
onAction: function() {
|
|||
|
if (this.options.onAction)
|
|||
|
this.options.onAction.call(this.$el, this.input);
|
|||
|
},
|
|||
|
|
|||
|
});
|
|||
|
$.pluginMaker(ovms.FileBrowser);
|
|||
|
|
|||
|
|
|||
|
// FileDialog:
|
|||
|
ovms.FileDialog = function(el, options) {
|
|||
|
if (el) this.init(el, options);
|
|||
|
}
|
|||
|
$.extend(ovms.FileDialog.prototype, ovms.Widget.prototype, {
|
|||
|
cname: "filedialog",
|
|||
|
|
|||
|
options: {
|
|||
|
title: 'Select file',
|
|||
|
submit: 'Select',
|
|||
|
onSubmit: null,
|
|||
|
onCancel: null,
|
|||
|
path: '',
|
|||
|
quicknav: ['/sd/', '/store/'],
|
|||
|
filter: null,
|
|||
|
sortBy: null,
|
|||
|
sortDir: 1,
|
|||
|
select: 'f',
|
|||
|
showNewDir: true,
|
|||
|
backdrop: true,
|
|||
|
keyboard: true,
|
|||
|
transition: 'fade',
|
|||
|
size: 'lg',
|
|||
|
onUpdate: null,
|
|||
|
show: false,
|
|||
|
},
|
|||
|
|
|||
|
init: function(el, options) {
|
|||
|
ovms.Widget.prototype.init.call(this, el, options);
|
|||
|
this.input = {};
|
|||
|
this.$fb = null;
|
|||
|
this.$el.dialog({
|
|||
|
input: this.input,
|
|||
|
body: '<div class="filebrowser"/>',
|
|||
|
onHide: $.proxy(this.onHide, this),
|
|||
|
onHidden: $.proxy(this.onHidden, this),
|
|||
|
});
|
|||
|
this.$fb = this.$el.find('.filebrowser').filebrowser({
|
|||
|
input: this.input,
|
|||
|
onPathChange: $.proxy(this.onPathChange, this),
|
|||
|
onAction: $.proxy(this.onAction, this),
|
|||
|
});
|
|||
|
this.update(this.options);
|
|||
|
},
|
|||
|
|
|||
|
update: function(options) {
|
|||
|
$.extend(this.options, options);
|
|||
|
var newbtns = [];
|
|||
|
if (this.options.showNewDir) newbtns.push(
|
|||
|
{ label: "New dir", btnClass: "default pull-left", autoHide: false, action: $.proxy(this.newDir, this) });
|
|||
|
newbtns.push(
|
|||
|
{ label: "Cancel" },
|
|||
|
{ label: this.options.submit, btnClass: "primary" });
|
|||
|
this.$el.dialog({
|
|||
|
title: this.options.title,
|
|||
|
buttons: newbtns,
|
|||
|
backdrop: this.options.backdrop,
|
|||
|
keyboard: this.options.keyboard,
|
|||
|
transition: this.options.transition,
|
|||
|
size: this.options.size,
|
|||
|
});
|
|||
|
this.$btnsubmit = this.$el.find(".modal-footer .btn-primary");
|
|||
|
this.$fb.filebrowser({
|
|||
|
path: (options.path != null) ? options.path : this.input.path,
|
|||
|
quicknav: this.options.quicknav,
|
|||
|
filter: this.options.filter,
|
|||
|
sortBy: this.options.sortBy,
|
|||
|
sortDir: this.options.sortDir,
|
|||
|
});
|
|||
|
this.onPathChange();
|
|||
|
if (this.options.onUpdate)
|
|||
|
this.options.onUpdate.call(this.$el, this.input);
|
|||
|
if (options.show != null) {
|
|||
|
var showing = this.$el.dialog('isShowing');
|
|||
|
if (options.show === true && !showing)
|
|||
|
this.$el.dialog('show');
|
|||
|
else if (options.show === false && showing)
|
|||
|
this.$el.dialog('hide');
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
show: function(options) {
|
|||
|
if (options) this.update(options);
|
|||
|
this.$el.dialog('show');
|
|||
|
},
|
|||
|
hide: function() {
|
|||
|
this.$el.dialog('hide');
|
|||
|
},
|
|||
|
|
|||
|
setPath: function(newpath, reload) {
|
|||
|
this.$fb.filebrowser('setPath', newpath, reload);
|
|||
|
},
|
|||
|
newDir: function() {
|
|||
|
this.$fb.filebrowser('newDir');
|
|||
|
},
|
|||
|
|
|||
|
onPathChange: function() {
|
|||
|
var ok = (
|
|||
|
(this.options.select == 'f' && this.input.file) ||
|
|||
|
(this.options.select == 'd' && !this.input.file));
|
|||
|
this.$btnsubmit.prop("disabled", !ok);
|
|||
|
},
|
|||
|
onAction: function() {
|
|||
|
this.$el.dialog('triggerButton', this.options.submit);
|
|||
|
},
|
|||
|
onHide: function() {
|
|||
|
this.$fb.filebrowser('stopLoad');
|
|||
|
},
|
|||
|
onHidden: function(input) {
|
|||
|
if (input.button && input.button.label == this.options.submit) {
|
|||
|
if (this.options.onSubmit)
|
|||
|
this.options.onSubmit.call(this.$el, this.input);
|
|||
|
}
|
|||
|
else {
|
|||
|
if (this.options.onCancel)
|
|||
|
this.options.onCancel.call(this.$el, this.input);
|
|||
|
}
|
|||
|
},
|
|||
|
});
|
|||
|
$.pluginMaker(ovms.FileDialog);
|
|||
|
|
|||
|
|
|||
|
// Template list editor:
|
|||
|
|
|||
|
$.fn.listEditor = function(op, data){
|
|||
|
return this.each(function(){
|
|||
|
if (op) {
|
|||
|
$(this).trigger('list:'+op, data);
|
|||
|
} else {
|
|||
|
// init:
|
|||
|
var $this = $(this);
|
|||
|
var el_itemid = $this.find('.list-item-id');
|
|||
|
var el_body = $this.find('.list-items');
|
|||
|
var el_template = $this.find('template').html();
|
|||
|
|
|||
|
$this.on('list:addItem', function(evt, data) {
|
|||
|
var id = Number(el_itemid.val()) + 1;
|
|||
|
el_itemid.val(id);
|
|||
|
data = $.extend({ MODE: "upd" }, data);
|
|||
|
var txt = el_template.replace(/ITEM_ID/g, id).replace(/ITEM_MODE/g, data.MODE);
|
|||
|
for (key in data)
|
|||
|
txt = txt.replace(new RegExp('ITEM_'+key,'g'), encode_html(data[key]));
|
|||
|
txt = txt.replace(/ITEM_\w+/g, '');
|
|||
|
var $el = $(txt).appendTo(el_body);
|
|||
|
|
|||
|
$el.find("option[data-value]").each(function(){
|
|||
|
$(this).prop("selected", $(this).val() == $(this).data("value"));
|
|||
|
});
|
|||
|
$el.find("input[data-value]").each(function(){
|
|||
|
var sel = $(this).val() == $(this).data("value");
|
|||
|
$(this).prop("checked", sel);
|
|||
|
if (sel) $(this).parent().addClass("active");
|
|||
|
else $(this).parent().removeClass("active");
|
|||
|
});
|
|||
|
|
|||
|
var discl = (data.MODE == "add") ? "add-disabled" : "list-disabled";
|
|||
|
$el.find("select."+discl).each(function(){
|
|||
|
$(this).prop("disabled", true).append($('<input type="hidden">')
|
|||
|
.attr("name", $(this).attr("name")).attr("value", $(this).val()));
|
|||
|
});
|
|||
|
$el.find("input."+discl).prop("readonly", true);
|
|||
|
$el.find("button."+discl).prop("disabled", true);
|
|||
|
|
|||
|
$this.trigger('list:validate');
|
|||
|
});
|
|||
|
|
|||
|
$this.on('change keyup', 'input,select,textarea', function(ev) {
|
|||
|
$this.trigger('list:validate');
|
|||
|
});
|
|||
|
|
|||
|
$this.on('click', '.list-item-add', function(evt) {
|
|||
|
var data = $.extend({ MODE: "add" }, $(this).data('preset'));
|
|||
|
$(this).trigger('list:addItem', data);
|
|||
|
});
|
|||
|
|
|||
|
$this.on('click', '.list-item-del', function(evt) {
|
|||
|
$(this).closest('.list-item').remove();
|
|||
|
$this.trigger('list:validate');
|
|||
|
});
|
|||
|
|
|||
|
}
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* Slider widget plugin
|
|||
|
*/
|
|||
|
|
|||
|
$.fn.slider = function(options) {
|
|||
|
return this.each(function() {
|
|||
|
var $sld = $(this).closest('.slider'), data = $.extend({ checked: true }, $sld.data());
|
|||
|
var opts = (typeof options == "object") ? options : data;
|
|||
|
// init?
|
|||
|
if ($sld.children().length == 0) {
|
|||
|
var id = $sld.attr('id');
|
|||
|
$sld.html('\
|
|||
|
<div class="slider-control form-inline">\
|
|||
|
<input class="slider-enable" type="checkbox" checked>\
|
|||
|
<input class="form-control slider-value" type="number" id="input-ID" name="ID">\
|
|||
|
<span class="slider-unit">UNIT</span>\
|
|||
|
<input class="btn btn-default slider-down" type="button" value="➖">\
|
|||
|
<input class="btn btn-default slider-set" type="button" value="◈">\
|
|||
|
<input class="btn btn-default slider-up" type="button" value="➕">\
|
|||
|
</div>\
|
|||
|
<input class="slider-input" type="range">'
|
|||
|
.replace(/ID/g, id).replace(/UNIT/g, opts.unit||''));
|
|||
|
}
|
|||
|
// update:
|
|||
|
var $inp = $sld.find('.slider-value, .slider-input'), $cb = $sld.find('.slider-enable'),
|
|||
|
$bt = $sld.find('input[type=button]'), $sb = $bt.filter('.slider-set'),
|
|||
|
oldchk = data.checked, chk = (opts.checked != null) ? opts.checked : oldchk,
|
|||
|
dis = (opts.disabled != null) ? opts.disabled : ($sld.prop('disabled')==true);
|
|||
|
$.extend(data, opts);
|
|||
|
if (opts.unit != null) $sld.find('.slider-unit').html(opts.unit);
|
|||
|
if (opts.min != null) $inp.attr('min', opts.min);
|
|||
|
if (opts.max != null) $inp.attr('max', opts.max);
|
|||
|
if (opts.step != null) $inp.attr('step', opts.step);
|
|||
|
if (opts.default != null) {
|
|||
|
if ($sb.length == 1)
|
|||
|
$sb.data('set', opts.default);
|
|||
|
if (!chk)
|
|||
|
$inp.val(opts.default);
|
|||
|
data.default = Math.max(data.min, Math.min(data.max, 1*opts.default));
|
|||
|
}
|
|||
|
if (opts.value !== undefined) {
|
|||
|
if (opts.value === null)
|
|||
|
data.value = data.uservalue = data.default;
|
|||
|
else
|
|||
|
data.value = Math.max(data.min, Math.min(data.max, 1*opts.value));
|
|||
|
if (chk)
|
|||
|
$inp.attr('value', data.value).val(data.value);
|
|||
|
if (chk || data.uservalue == null)
|
|||
|
data.uservalue = data.value;
|
|||
|
}
|
|||
|
if (chk != oldchk) {
|
|||
|
$cb.prop('checked', chk);
|
|||
|
if (chk) {
|
|||
|
if (opts.reset || data.reset)
|
|||
|
data.value = data.default;
|
|||
|
else
|
|||
|
data.value = data.uservalue;
|
|||
|
} else {
|
|||
|
data.uservalue = data.value;
|
|||
|
data.value = data.default;
|
|||
|
}
|
|||
|
$inp.val(data.value);
|
|||
|
}
|
|||
|
$cb.prop('disabled', dis);
|
|||
|
$bt.prop('disabled', !chk || dis);
|
|||
|
$inp.prop('disabled', !chk || dis).prop('checked', chk);
|
|||
|
if (dis)
|
|||
|
$sld.addClass('disabled').prop('disabled', true).attr('disabled', true);
|
|||
|
else
|
|||
|
$sld.removeClass('disabled').prop('disabled', false).attr('disabled', false);
|
|||
|
$sld.data(data);
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* Highcharts
|
|||
|
*/
|
|||
|
|
|||
|
var highchartsLoader;
|
|||
|
|
|||
|
$.fn.chart = function(options) {
|
|||
|
if (this.length == 0)
|
|||
|
return this;
|
|||
|
var $this = this;
|
|||
|
if (options === "destroy") {
|
|||
|
// destroy:
|
|||
|
var $cl = this.find(".has-chart").add(this.filter(".has-chart"));
|
|||
|
$cl.each(function() {
|
|||
|
var chart = $(this).data("chart");
|
|||
|
if (typeof chart == "object") {
|
|||
|
$(this).data("chart", null);
|
|||
|
chart.destroy();
|
|||
|
}
|
|||
|
});
|
|||
|
} else {
|
|||
|
// init:
|
|||
|
function init_charts() {
|
|||
|
$this.each(function() {
|
|||
|
var chart = Highcharts.chart(this, options, function() {
|
|||
|
if (this.userOptions && this.userOptions.onUpdate)
|
|||
|
this.userOptions.onUpdate.call(this, metrics);
|
|||
|
});
|
|||
|
$(this).data("chart", chart).addClass("has-chart get-window-resize").on("window-resize", function() {
|
|||
|
$(this).data("chart").reflow();
|
|||
|
});
|
|||
|
$(this).closest(".metric.chart").data("chart", chart);
|
|||
|
});
|
|||
|
}
|
|||
|
if (window.Highcharts) {
|
|||
|
init_charts();
|
|||
|
} else if (highchartsLoader) {
|
|||
|
highchartsLoader.then(init_charts);
|
|||
|
} else {
|
|||
|
highchartsLoader = $.ajax({
|
|||
|
url: (window.assets && window.assets["charts_js"]) || "/assets/charts.js?v=6.0.7",
|
|||
|
dataType: "script",
|
|||
|
cache: true,
|
|||
|
success: function(){ init_charts(); }
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
return this;
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* DataTables
|
|||
|
*/
|
|||
|
|
|||
|
var datatablesLoader;
|
|||
|
|
|||
|
$.fn.table = function(options) {
|
|||
|
if (this.length == 0)
|
|||
|
return this;
|
|||
|
var $this = this;
|
|||
|
if (options === "destroy") {
|
|||
|
// destroy:
|
|||
|
var $tl = this.find(".has-dataTable").add(this.filter(".has-dataTable"));
|
|||
|
$tl.each(function() {
|
|||
|
var table = $(this).data("dataTable");
|
|||
|
if (typeof table == "object") {
|
|||
|
$(this).data("dataTable", null);
|
|||
|
table.destroy();
|
|||
|
}
|
|||
|
});
|
|||
|
} else {
|
|||
|
// init:
|
|||
|
function init_tables() {
|
|||
|
$this.each(function() {
|
|||
|
$(this).one("init.dt", function(ev, settings) {
|
|||
|
if (settings && settings.oInstance && settings.oInit && settings.oInit.onUpdate)
|
|||
|
settings.oInit.onUpdate.call(settings.oInstance.api(), metrics);
|
|||
|
});
|
|||
|
var table = $(this).DataTable(options);
|
|||
|
$(this).data("dataTable", table).addClass("has-dataTable");
|
|||
|
$(this).closest(".metric.table").data("dataTable", table);
|
|||
|
});
|
|||
|
}
|
|||
|
if ($.fn.DataTables) {
|
|||
|
init_tables();
|
|||
|
} else if (datatablesLoader) {
|
|||
|
datatablesLoader.then(init_tables);
|
|||
|
} else {
|
|||
|
datatablesLoader = $.ajax({
|
|||
|
url: (window.assets && window.assets["tables_js"]) || "/assets/tables.js?v=1.10.18",
|
|||
|
dataType: "script",
|
|||
|
cache: true,
|
|||
|
success: function(){ init_tables(); }
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
return this;
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
/**
|
|||
|
* Framework Init
|
|||
|
*/
|
|||
|
|
|||
|
$(function(){
|
|||
|
|
|||
|
// Toggle night mode:
|
|||
|
$('body').on('click', '.toggle-night', function(event){
|
|||
|
var nm = $('body').toggleClass("night").hasClass("night");
|
|||
|
page.params["nm"] = 0+nm;
|
|||
|
updateLocation();
|
|||
|
event.stopPropagation();
|
|||
|
return false;
|
|||
|
});
|
|||
|
|
|||
|
// Toggle fullscreen mode:
|
|||
|
document.fullScreenMode = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen;
|
|||
|
$(document).on('mozfullscreenchange webkitfullscreenchange fullscreenchange', function() {
|
|||
|
this.fullScreenMode = !this.fullScreenMode;
|
|||
|
if (this.fullScreenMode) {
|
|||
|
$('body').addClass("fullscreened");
|
|||
|
} else {
|
|||
|
$('body').removeClass("fullscreened");
|
|||
|
}
|
|||
|
$(window).trigger("resize");
|
|||
|
});
|
|||
|
$('body').on('click', '.toggle-fullscreen', function(evt) {
|
|||
|
element = document.body;
|
|||
|
if (element.requestFullscreen) {
|
|||
|
element.requestFullscreen();
|
|||
|
} else if (element.mozRequestFullScreen) {
|
|||
|
element.mozRequestFullScreen();
|
|||
|
} else if (element.webkitRequestFullscreen) {
|
|||
|
element.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
|
|||
|
} else if (element.msRequestFullscreen) {
|
|||
|
element.msRequestFullscreen();
|
|||
|
}
|
|||
|
return false;
|
|||
|
});
|
|||
|
|
|||
|
// AJAX page links, forms & buttons:
|
|||
|
$('body').on('click', 'a[target^="#"], form[target^="#"] .btn[type="submit"]', function(event){
|
|||
|
var method = $(this).data("method") || "get";
|
|||
|
var uri = $(this).attr("href");
|
|||
|
var target = $(this).attr("target");
|
|||
|
var data = {};
|
|||
|
if (method.toLowerCase() == "post") {
|
|||
|
var p = uri.split("?");
|
|||
|
if (p.length == 2) {
|
|||
|
uri = p[0];
|
|||
|
data = p[1];
|
|||
|
}
|
|||
|
if (uri == "" || uri == "#")
|
|||
|
uri = $("#main").data("uri");
|
|||
|
}
|
|||
|
if (!uri) {
|
|||
|
var frm = $(this.form);
|
|||
|
method = frm.attr("method") || "get";
|
|||
|
uri = frm.attr("action");
|
|||
|
target = frm.attr("target");
|
|||
|
data = frm.serialize();
|
|||
|
if (this.name)
|
|||
|
data += (data?"&":"") + encodeURI(this.name+"="+(this.value||"1"));
|
|||
|
}
|
|||
|
if ($(this).data("dismiss") == "modal")
|
|||
|
$(this).closest(".modal").removeClass("fade").modal("hide");
|
|||
|
if (!loaduri(target, method, uri, data))
|
|||
|
return true;
|
|||
|
event.preventDefault();
|
|||
|
event.stopPropagation();
|
|||
|
return false;
|
|||
|
});
|
|||
|
$('body').on('submit', 'form[target^="#"]', function(event) {
|
|||
|
var $frm = $(this);
|
|||
|
var method = $frm.attr("method") || "get";
|
|||
|
var uri = $frm.attr("action");
|
|||
|
var target = $frm.attr("target");
|
|||
|
var data = $frm.serialize();
|
|||
|
var $btn = $frm.find('input[type="submit"], button[type="submit"]').first();
|
|||
|
if ($btn.length && $btn.attr("name"))
|
|||
|
data += (data?"&":"") + encodeURI($btn.attr("name") +"="+ ($btn.val()||"1"));
|
|||
|
if ($frm.data("dismiss") == "modal" || $btn.data("dismiss") == "modal")
|
|||
|
$frm.closest(".modal").removeClass("fade").modal("hide");
|
|||
|
if (!loaduri(target, method, uri, data))
|
|||
|
return true;
|
|||
|
event.preventDefault();
|
|||
|
event.stopPropagation();
|
|||
|
return false;
|
|||
|
});
|
|||
|
|
|||
|
// AJAX command links & buttons:
|
|||
|
$('body').on('click', '.btn[data-cmd]', function(event){
|
|||
|
var btn = $(this);
|
|||
|
var cmd = btn.data("cmd");
|
|||
|
var tgt = btn.data("target");
|
|||
|
var updcnt = btn.data("watchcnt") || 0;
|
|||
|
var updint = btn.data("watchint") || 2;
|
|||
|
btn.prop("disabled", true);
|
|||
|
$(tgt).data("updcnt", 0);
|
|||
|
loadcmd(cmd, tgt).then(function(){
|
|||
|
btn.prop("disabled", false);
|
|||
|
$(tgt).data("updcnt", updcnt);
|
|||
|
$(tgt).data("updint", updint);
|
|||
|
$(tgt).data("updlast", now());
|
|||
|
}, function(){
|
|||
|
btn.prop("disabled", false);
|
|||
|
});
|
|||
|
event.stopPropagation();
|
|||
|
return false;
|
|||
|
});
|
|||
|
$('body').on('click', '.btn[data-js]', function(event){
|
|||
|
var btn = $(this);
|
|||
|
var js = btn.data("js");
|
|||
|
var tgt = btn.data("target");
|
|||
|
var updcnt = btn.data("watchcnt") || 0;
|
|||
|
var updint = btn.data("watchint") || 2;
|
|||
|
btn.prop("disabled", true);
|
|||
|
$(tgt).data("updcnt", 0);
|
|||
|
loadjs(js, tgt).then(function(){
|
|||
|
btn.prop("disabled", false);
|
|||
|
$(tgt).data("updcnt", updcnt);
|
|||
|
$(tgt).data("updint", updint);
|
|||
|
$(tgt).data("updlast", now());
|
|||
|
}, function(){
|
|||
|
btn.prop("disabled", false);
|
|||
|
});
|
|||
|
event.stopPropagation();
|
|||
|
return false;
|
|||
|
});
|
|||
|
|
|||
|
// Long touch buttons:
|
|||
|
var longtouchTimeout, $longtouchProgress;
|
|||
|
$('body').on('touchstart', '.btn-longtouch .btn, .btn.btn-longtouch', function(ev) {
|
|||
|
var $this = $(this);
|
|||
|
if ($this.prop('disabled')) return;
|
|||
|
var action = $this.attr("title") || $this.text();
|
|||
|
if (navigator.vibrate) navigator.vibrate([100,400,100,400,100,400]);
|
|||
|
$longtouchProgress = $('<div class="hover-progress longtouch"><div class="hover-progress-body"><div class="info">Hold touch for/to</div><div class="action">'+encode_html(action)+'</div><div class="progress"><div class="progress-bar progress-bar-info" style="width:0%"></div></div></div></div>').appendTo("body").find(".progress-bar");
|
|||
|
window.getComputedStyle($longtouchProgress.get(0)).width;
|
|||
|
$longtouchProgress.css("width", "100%");
|
|||
|
longtouchTimeout = window.setTimeout(function() {
|
|||
|
if (navigator.vibrate) navigator.vibrate(1000);
|
|||
|
if ($longtouchProgress) $longtouchProgress.closest(".hover-progress").remove();
|
|||
|
longtouchTimeout = null;
|
|||
|
$longtouchProgress = null;
|
|||
|
$this.trigger('click');
|
|||
|
}, 1500);
|
|||
|
ev.preventDefault();
|
|||
|
}).on('touchcancel touchend', '.btn-longtouch .btn, .btn.btn-longtouch', function(ev) {
|
|||
|
window.clearTimeout(longtouchTimeout);
|
|||
|
if (navigator.vibrate) navigator.vibrate(0);
|
|||
|
if ($longtouchProgress) $longtouchProgress.closest(".hover-progress").remove();
|
|||
|
longtouchTimeout = null;
|
|||
|
$longtouchProgress = null;
|
|||
|
ev.preventDefault();
|
|||
|
}).on('contextmenu', function(ev) {
|
|||
|
if ($longtouchProgress) {
|
|||
|
ev.preventDefault();
|
|||
|
ev.stopPropagation();
|
|||
|
ev.stopImmediatePropagation();
|
|||
|
return false;
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Slider widget event handling:
|
|||
|
$("body").on("change", ".slider-enable", function(evt) {
|
|||
|
var $this = $(this), $sld = $this.closest(".slider"), $inp = $sld.find(".slider-input, .slider-value");
|
|||
|
$this.slider({ checked: this.checked });
|
|||
|
$inp.trigger("input", true).trigger("change", true);
|
|||
|
});
|
|||
|
$("body").on("input change", ".slider-value", function(evt, noprop) {
|
|||
|
var $this = $(this), $sld = $this.closest(".slider"), $inp = $sld.find(".slider-input");
|
|||
|
if (!noprop) {
|
|||
|
$this.slider({ value: this.value });
|
|||
|
$inp.trigger(evt.type, true);
|
|||
|
}
|
|||
|
});
|
|||
|
$("body").on("input change", ".slider-input", function(evt, noprop) {
|
|||
|
var $this = $(this), $sld = $this.closest(".slider"), $inp = $sld.find(".slider-value");
|
|||
|
if (!noprop) {
|
|||
|
$this.slider({ value: this.value });
|
|||
|
$inp.trigger(evt.type, true);
|
|||
|
}
|
|||
|
});
|
|||
|
$("body").on("click", ".slider-up", function(evt) {
|
|||
|
$(this).closest(".slider").find(".slider-input").val(function() {
|
|||
|
return Math.min(1*this.value + (1*this.step||1), this.max);
|
|||
|
}).trigger("input").trigger("change");
|
|||
|
});
|
|||
|
$("body").on("click", ".slider-down", function(evt) {
|
|||
|
$(this).closest(".slider").find(".slider-input").val(function() {
|
|||
|
return Math.max(1*this.value - (1*this.step||1), this.min);
|
|||
|
}).trigger("input").trigger("change");
|
|||
|
});
|
|||
|
$("body").on("click", ".slider-set", function(evt) {
|
|||
|
var $inp = $(this).closest(".slider").find(".slider-input");
|
|||
|
$inp.val($(this).data("set")).trigger("input").trigger("change");
|
|||
|
});
|
|||
|
|
|||
|
// data-toggle="filefialog":
|
|||
|
$("body").on('click', '.btn[data-toggle="filedialog"]', function(evt) {
|
|||
|
var $this = $(this);
|
|||
|
var $tgt = $($this.data("target"));
|
|||
|
var $inp = $($this.data("input"));
|
|||
|
if ($tgt.length && $inp.length) {
|
|||
|
var val = $inp.val();
|
|||
|
var opt = {
|
|||
|
show: true,
|
|||
|
onSubmit: function(input) { $inp.val(input.path); }
|
|||
|
};
|
|||
|
if (val) opt.path = val;
|
|||
|
$tgt.filedialog(opt);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
// Metrics displays:
|
|||
|
$("body").on('msg:metrics', '.receiver', function(e, update) {
|
|||
|
$(this).find(".metric").each(function() {
|
|||
|
var $el = $(this), metric = $el.data("metric"), prec = $el.data("prec"), scale = $el.data("scale");
|
|||
|
if (!metric) return;
|
|||
|
// filter:
|
|||
|
var keys = metric.split(","), val;
|
|||
|
for (var i=0; i<keys.length; i++) {
|
|||
|
if ((val = update[keys[i]]) != null) break;
|
|||
|
}
|
|||
|
if (val == null) return;
|
|||
|
// process:
|
|||
|
if ($el.hasClass("text")) {
|
|||
|
$el.children(".value").text(val);
|
|||
|
} else if ($el.hasClass("number")) {
|
|||
|
var vf = val;
|
|||
|
if (scale != null) vf = Number(vf) * scale;
|
|||
|
if (prec != null) vf = Number(vf).toFixed(prec);
|
|||
|
$el.children(".value").text(vf);
|
|||
|
} else if ($el.hasClass("progress")) {
|
|||
|
var vf = val;
|
|||
|
if (scale != null) vf = Number(vf) * scale;
|
|||
|
if (prec != null) vf = Number(vf).toFixed(prec);
|
|||
|
var $pb = $(this.firstElementChild), min = $pb.attr("aria-valuemin"), max = $pb.attr("aria-valuemax");
|
|||
|
var vp = (val-min) / (max-min) * 100;
|
|||
|
$pb.css("width", vp+"%").attr("aria-valuenow", vp).find(".value").text(vf);
|
|||
|
var lw = 0; $pb.find("span").each(function(){ lw += $(this).width(); });
|
|||
|
if (($pb.parent().width()*vp/100) < lw) $pb.addClass("value-low"); else $pb.removeClass("value-low");
|
|||
|
} else if ($el.hasClass("chart")) {
|
|||
|
var ch = $(this).data("chart");
|
|||
|
if (ch && ch.userOptions && ch.userOptions.onUpdate)
|
|||
|
ch.userOptions.onUpdate.call(ch, update);
|
|||
|
} else if ($el.hasClass("table")) {
|
|||
|
var dt = $(this).data("dataTable");
|
|||
|
if (dt && dt.settings() && dt.settings()[0] && dt.settings()[0].oInit && dt.settings()[0].oInit.onUpdate)
|
|||
|
dt.settings()[0].oInit.onUpdate.call(dt, update);
|
|||
|
} else {
|
|||
|
$el.text(val);
|
|||
|
}
|
|||
|
});
|
|||
|
});
|
|||
|
|
|||
|
// Modal autoclear:
|
|||
|
$("body").on("hidden.bs.modal", ".modal", function(evt) {
|
|||
|
$(this).find(".modal-autoclear").empty();
|
|||
|
});
|
|||
|
|
|||
|
// Proxy window resize:
|
|||
|
$(window).on("resize", function(event){
|
|||
|
$(".get-window-resize").trigger("window-resize");
|
|||
|
});
|
|||
|
$("body").on("window-resize", ".table-scrollable", function(evt) {
|
|||
|
var bw = $(this).find('>tbody').get(0).scrollWidth;
|
|||
|
if (bw) $(this).find('>thead').css('width', bw);
|
|||
|
});
|
|||
|
|
|||
|
// Monitor timer:
|
|||
|
if (!monitorTimer)
|
|||
|
monitorTimer = window.setInterval(monitorUpdate, 1000);
|
|||
|
|
|||
|
// AJAX page init:
|
|||
|
window.onpopstate = loadPage;
|
|||
|
loadPage();
|
|||
|
});
|