Add files via upload

master
zmv7 2020-11-02 10:46:05 +05:00 committed by GitHub
parent ea8ed0e4f8
commit 4ed6a6ac54
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 12759 additions and 0 deletions

111
mag/mini/system/buffer.js Normal file
View File

@ -0,0 +1,111 @@
'use strict';
/* jshint unused:false */
/**
* Cut/copy files/links system storage singleton
* manages buffer items in two modes (cut/copy)
* @author Stanislav Kalashnik <sk@infomir.eu>
* @type {globalBuffer}
*/
var globalBuffer = (function () {
// private vars
var data = []; // all items
var mode = 1; // default is "copy"
var place = {}; // place where copy was made
// main body
return {
// constants
MODE_COPY : 1, // do nothing on paste
MODE_CUT : 2, // remove source item on paste
/**
* callback function on each item paste event
* @param {*} data item that was pasted successfully
*/
onPaste : null,
/**
* push new item to the list if not exist
* @param {*} item buffer element
* @return {boolean} operation status: true - item was successfully added
*/
add : function ( item ) {
if ( data.indexOf(item) === -1 ) {
data.push(item);
return true;
}
return false;
},
/**
* collects all buffer items and returns new copy of this list
* @return {Array}
*/
paste : function ( func ) {
var self = this;
// clone data
data.forEach(function(item){
// run callback after cut
if ( func(item) && typeof self.onPaste === 'function' ) {
self.onPaste(item);
}
});
// reset everything
this.clear();
},
/**
* setter/getter for mode inner flag
* @param {number} [newMode] if applied then mode will be set otherwise returns the current value
* @return {number} new or current mode value
*/
mode : function ( newMode ) {
if ( newMode !== undefined ) {
mode = newMode;
}
return mode;
},
/**
* setter/getter for place inner flag (the place where copy operation was made)
* @param {Object} [newPlace] if applied then place will be set otherwise returns the current value
* @return {Object} new or current place value
*/
place : function ( newPlace ) {
if ( newPlace !== undefined ) {
place = newPlace;
}
return place;
},
/**
* gets the buffer items total amount
* @return {number}
*/
size : function () {
return data.length;
},
/**
* deletes the given item from the buffer
* @param {*} item buffer element to be removed
* @return {boolean} operation status: true - item was successfully removed
*/
remove : function ( item ) {
// some input
if ( item !== undefined ) {
// find item index to remove
var index = data.indexOf(item);
// one item should be removed
if ( index !== -1 ) {
return data.splice(index, 1).length === 1;
}
}
return false;
},
/**
* resets all data
*/
clear : function () {
data = [];
place = {};
this.onPaste = null;
}
};
})();

View File

@ -0,0 +1,244 @@
'use strict';
function CAutocomplete ( parent, parameters ){
var self = this;
this.attributes = {
wrapper: {
className: 'cautocomplete-box'
},
row: {
className: 'row',
onmouseover: function(){
self.SetActive(this);
},
onclick: function(){
self.Abort();
self.trigger('onEnter', {text: self.GetValue(), type: self.GetType()});
self.Clear();
},
ico: {
className: 'ico'
},
hint: {
className: 'hint'
},
text: {
className: 'text'
}
}
};
CBase.call(this, parent || null, parameters);
this.name = 'CAutocomplete';
this.active = null;
this.events = {};
this.isVisible = false;
CAutocomplete.parameters.forEach(function(name){
if (parameters[name] !== undefined) {
self[name] = parameters[name];
}
});
this.bind(this.events);
this.base = this.base || this.input;
this.size = this.size || 0;
this.Init( element('div', this.attributes.wrapper) );
this.buffer = {};
this.input.oninput = (function(oninput){
return function(event){
self.Show(false, false);
clearTimeout(self.input_timer);
if (self.input.value !== self.GetDefault()){
self.buffer.data = self.input.value;
self.input_timer = setTimeout(function(){
self.RefreshData(self.input.value, function(){
this.Show(true, false);
this.RefreshUi();
});
}, 600);
}
if (typeof oninput === 'function') {
oninput(event);
}
};
})(this.input.oninput);
}
CAutocomplete.parameters = ['input', 'base', 'size', 'data', 'url', 'events', 'titleField', 'valueField', 'typeField', 'hintField'];
// extending
CAutocomplete.prototype = Object.create(CBase.prototype);
CAutocomplete.prototype.constructor = CAutocomplete;
CAutocomplete.prototype.onInit = function (){
elchild(document.body, this.handle);
};
CAutocomplete.prototype.UpdateGeometry = function (){
var inputRect = this.base.getBoundingClientRect();
this.handle.style.top = (inputRect.top + inputRect.height) + 'px';
this.handle.style.left = inputRect.left + 'px';
this.handle.style.width = inputRect.width + 'px';
};
CAutocomplete.prototype.RefreshUi = function ( data ){
var i, size, row;
if (this.handle.style.width === ''){
this.UpdateGeometry();
}
data = data || this._data;
elclear(this.handle);
size = this.size > data.length ? data.length : this.size;
if (size > 0){
for (i = 0; i < size; i++){
row = this.AddRow(data[i]);
}
this.SetActive(this.buffer.nextSibling = this.handle.firstElementChild);
this.buffer.previousSibling = this.handle.lastElementChild;
}else{
this.Show(false);
}
};
CAutocomplete.prototype.RefreshData = function (data, callback ) {
var url, self = this;
if (data === '') {
this.Show(false, false);
return;
}
this.buffer.data = data;
url = this.url ? this.url + data : data;
if (url){
this.xhr = ajax('GET', url, function(res, status){
if (status === 200){
if (typeof self.data === 'function'){
self._data = self.data.call(this, res);
}else{
self._data = res[self.data];
}
callback.call(self, res);
self.trigger('onDataLoad', data);
} else {
self.Show(false);
}
});
}
};
CAutocomplete.prototype.GetTitle = function ( data ) {
data = data || this.active.data;
if (this.active === this.buffer) {
return data;
}
return this.titleField ? data[this.titleField] : this.active.data;
};
CAutocomplete.prototype.GetValue = function ( data ) {
data = data || this.active.data;
if (this.active === this.buffer) {
return data;
}
data = this.valueField ? data[this.valueField] : this.GetTitle();
data = data.replace(/<\/?b>/g,'');
return data;
};
CAutocomplete.prototype.GetType = function ( data ) {
data = data || this.active.data;
return data[this.typeField] ? data[this.typeField] : 'search';
};
CAutocomplete.prototype.GetHint = function ( data ) {
data = data || this.active.data;
return this.hintField ? data[this.hintField] : '';
};
CAutocomplete.prototype.GetDefault = function () {
return this.buffer.data;
};
CAutocomplete.prototype.SetActive = function ( active ){
if (this.active !== null && this.active !== this.buffer){
this.active.classList.remove('active');
}
if (active !== this.buffer){
active.classList.add('active');
}
this.active = active;
this.trigger('onChange');
};
CAutocomplete.prototype.Next = function (){
if ( this.active.nextSibling ) {
this.SetActive(this.active.nextSibling);
} else {
this.SetActive(this.buffer);
}
};
CAutocomplete.prototype.Previous = function (){
if (this.active.previousSibling){
this.SetActive(this.active.previousSibling);
}else{
this.SetActive(this.buffer);
}
};
CAutocomplete.prototype.AddRow = function ( data ){
var el, ico, hint, text, attributes = extend({}, this.attributes.row);
attributes.className += ' ' + this.GetType(data);
ico = element('div', this.attributes.row.ico);
hint = element('div', this.attributes.row.hint);
text = element('div', this.attributes.row.text);
elchild(this.handle, el = element('div', attributes, [ico, text, hint]));
text.innerHTML = this.GetTitle(data);
hint.innerHTML = this.GetHint(data);
el.data = data;
return el;
};
CAutocomplete.prototype.Abort = function(){
if (this.xhr !== undefined){
this.xhr.abort();
}
};
CAutocomplete.prototype.Clear = function (){
this.Show(false);
elclear(this.handle);
clearTimeout(this.input_timer);
this.Abort();
this.active = null;
this.buffer = {};
};
CAutocomplete.prototype.EventHandler = function ( event ){
eventPrepare(event);
if (this.isVisible === true){
switch(event.code){
case KEYS.UP:
this.Previous();
event.preventDefault();
break;
case KEYS.DOWN:
this.Next();
event.preventDefault();
break;
case KEYS.OK:
this.Abort();
this.trigger('onEnter', {text: this.GetValue(), type: this.GetType()});
this.Clear();
break;
default:
return false;
}
return true;
}
};
Events.inject(CAutocomplete);

265
mag/mini/system/cbase.js Normal file
View File

@ -0,0 +1,265 @@
'use strict';
/**
* Base class for any visual component.
* Always has one html placeholder.
* @class CBase
* @constructor
* @author Stanislav Kalashnik <sk@infomir.eu>
*/
function CBase ( parent ) {
/**
* The component inner name
* @type {string}
*/
this.name = 'CBase';
/**
* Component placeholder
* should be never used for append nodes inside the component methods
* use this.handleInner instead
* @type {Node}
*/
this.handle = null;
/**
* Object owner of the component
* @type {CBase}
*/
this.parent = null;
/**
* Component main body placeholder (should be the same as this.handle)
* can be different only in case when the given placeholder is invalid (don't have the necessary class)
* so the valid wrapper with this.baseClass should be created instead
* @type {Node}
*/
this.handleInner = null;
/**
* CSS "display" attribute value to make the component visible
* to hide the default value is "none"
* @type {string}
*/
this.showAttr = 'block';
/**
* CSS class name associated with the component
* it is checking on initialization in the placeholder
* if not found a wrapper is created
* @type {string}
*/
this.baseClass = '';
/**
* The previous DOM element that had focus
* (used for focus management on show/hide operations)
* @type {Node}
*/
this.prevFocus = null;
/**
* Flag to indicate the component state
* using for event handling and activate/deactivate hooks
* @type {boolean}
*/
this.isActive = false;
/**
* Current active child element
* @type {CBase}
*/
this.activeChild = null;
/**
* default visibility state
* @type {boolean}
*/
this.isVisible = true;
// apply hierarchy
this.SetParent(parent);
}
/**
* Set the parent object owner of this component
* @param {CBase} parent object owner
*/
CBase.prototype.SetParent = function ( parent ) {
// check input
if ( parent instanceof CBase ) {
// store here as well
this.parent = parent;
}
};
/**
* Component initialization with its placeholder.
* Should be called once before use just after constructor invoke and all hooks preparation.
* @param {Node} handle component placeholder (it should be an empty div element)
*/
CBase.prototype.Init = function ( handle ) {
// input validation
if ( handle && handle.nodeName ) {
// global store
this.handle = handle;
// the given placeholder is invalid (don't have the necessary base class)
if ( this.baseClass && handle.className.indexOf(this.baseClass) === -1 ) {
// add wrapper
this.handleInner = this.handle.appendChild(element('div', {className:this.baseClass}));
} else {
// the component body pointer
this.handleInner = this.handle;
}
var self = this;
this.handle.addEventListener('click', function(event){
if ( typeof self.onClick === 'function' && self.onClick() === false ) {
// prevents further propagation of the current event
event.stopPropagation();
}
});
// run callback hook
if ( typeof this.onInit === 'function' ) {
this.onInit();
}
}
};
/**
* Makes the component active or disable it
* process the activation/deactivation hooks for this component and its parent
* @param {boolean} [active=true]
*/
CBase.prototype.Activate = function ( active ) {
this.isActive = active !== false;
if ( this.isActive ) {
// run this component activation callback hook
if ( typeof this.onActivate === 'function' ) {
this.onActivate();
}
// has parent
if ( this.parent ) {
// run the previous active component deactivation (if not itself)
if ( this.parent.activeChild && this.parent.activeChild !== this ) {
this.parent.activeChild.Activate(false);
}
// set link in the parent
this.parent.activeChild = this;
}
} else {
// run this component deactivation callback hook
if ( typeof this.onDeactivate === 'function' ) {
this.onDeactivate();
}
// has parent
if ( this.parent ) {
// set link in the parent
this.parent.activeChild = null;
}
}
};
/**
* Manage the component visibility, global focus and exec show/hide hooks
* @param {boolean} [visible=true] component visibility: true - visible, false - hidden
* @param {boolean} [manageFocus=true] focus handling mode: true - set/remove focus accordingly, false - manual focus management
* @return {boolean} status: true - operation was successful (mode changed), false - operation was skipped
*/
CBase.prototype.Show = function ( visible, manageFocus ) {
var success = false;
// placeholder validation
if ( this.handle ) {
this.isVisible = visible !== false;
// show
if ( this.isVisible ) {
// prevent double invoke
if ( this.handle.style.display !== this.showAttr ) {
// save the previous focused element
this.prevFocus = document.activeElement;
// remove focus if necessary and set
if ( manageFocus !== false && document.activeElement ) {
document.activeElement.blur();
}
// show this component
this.handle.style.display = this.showAttr;
// set focus if necessary
if ( manageFocus !== false ) {
this.handle.focus();
}
// invoke callback hook
if ( typeof this.onShow === 'function' ) {
this.onShow();
}
success = true;
}
// hide
} else {
// prevent double invoke
if ( this.handle.style.display !== 'none' ) {
// remove focus if necessary and set
if ( manageFocus !== false && document.activeElement ) {
document.activeElement.blur();
}
// hide this component
this.handle.style.display = 'none';
// return focus to the previous element if necessary and set
if ( manageFocus !== false && this.prevFocus ) {
this.prevFocus.focus();
}
// invoke callback hook
if ( typeof this.onHide === 'function' ) {
this.onHide();
}
// deactivate this component if necessary
if ( this.isActive ) {
this.Activate(false);
}
success = true;
}
}
}
return success;
};
/**
* Set visibility mode
* @param {boolean} [mode=false] show/hide the component
*/
CBase.prototype.Visible = function ( mode ) {
this.handle.style.visibility = Boolean(mode) ? 'visible' : 'hidden';
};
/**
* Events handler entry point.
* Should be recreated if necessary in each child object to handle parent events.
* @type {Function}
*/
CBase.prototype.EventHandler = null;
// hooks to redefine
CBase.prototype.onInit = null;
CBase.prototype.onShow = null;
CBase.prototype.onHide = null;
CBase.prototype.onActivate = null;
CBase.prototype.onDeactivate = null;
/**
* Method to activate a component by mouse click on it
* @returns {boolean}
*/
CBase.prototype.onClick = function () {
this.Activate(true);
return true;
};

196
mag/mini/system/cbcrumb.js Normal file
View File

@ -0,0 +1,196 @@
'use strict';
/**
* Class for breadcrumb with images.
* @class CBreadCrumb
* @constructor
* @author Stanislav Kalashnik <sk@infomir.eu>
*/
function CBreadCrumb ( parent ) {
// parent constructor
CBase.call(this, parent);
/**
* The component inner name
* @type {string}
*/
this.name = 'CBreadCrumb';
/**
* CSS class name associated with the component
* @type {string}
*/
this.baseClass = 'cbcrumb-main';
/**
* directory with icons
* depends on screen resolution
* @type {string}
*/
this.path = '';
/**
* Amount of left items before the ...
* @type {number}
*/
this.leftItems = 3;
/**
* Amount of right items after the ...
* @type {number}
*/
this.rightItems = 2;
}
// extending
CBreadCrumb.prototype = Object.create(CBase.prototype);
CBreadCrumb.prototype.constructor = CBreadCrumb;
/**
* Component initialization with image path set.
* @param {string} path image path dependant on resolution
* @param {Node} [handle] component placeholder
* @param {number} [leftItems] amount of left items before the ...
* @param {number} [rightItems] amount of right items after the ...
*/
CBreadCrumb.prototype.Init = function ( path, handle, leftItems, rightItems ) {
// global vars
this.path = path;
// parent call init with placeholder
CBase.prototype.Init.call(this, handle);
this.dom = {};
// add to component container
elchild(this.handleInner, [
this.dom.list = element('div', {className: 'cbcrumb-list'}),
this.dom.name = element('div', {className: 'cbcrumb-name'})
]);
this.leftItems = leftItems || this.leftItems;
this.rightItems = rightItems || this.rightItems;
};
/**
* Set optional auto-hide title to the right from the root icon
* @param {string} text label to show
*/
CBreadCrumb.prototype.SetName = function ( text ) {
elclear(this.dom.name);
elchild(this.dom.name, element('div', {innerHTML: text, className: 'cbcrumb-name-text'}));
// show or hide title
this.DrawName();
};
/**
* Set title visibility
*/
CBreadCrumb.prototype.DrawName = function () {
// show or hide title
this.dom.name.style.display = this.dom.list.children.length === 1 ? 'table-cell' : 'none';
};
/**
* Append a new button
* @param {string} path
* @param {string} [icon] file of the button icon
* @param {string} [text] button title
* @param {string} [iid] item id
* @returns {Node}
*/
CBreadCrumb.prototype.Push = function ( path, icon, text, iid ) {
var last = this.dom.list.lastChild;
if ( last ) {
last.className = 'cbcrumb-item';
}
// build item
var item = element('div', {className: 'cbcrumb-item active', onclick: null, path: path}, [
icon ? element('img', {className: 'cbcrumb-icon', onclick: null, src: this.path + icon}) : null,
text ? element('div', {className: 'cbcrumb-text', onclick: null}, text) : null
]);
// set id
if ( iid ) {
item.iid = iid;
}
// add divider
if ( this.dom.list.children.length === this.leftItems ) {
elchild(this.dom.list, element('div', {className: 'cbcrumb-item divider'}, '. . .'));
}
// add to component container
elchild(this.dom.list, item);
// show ... and hide item
if ( this.dom.list.children.length > this.leftItems + this.rightItems + 1 ) {
this.dom.list.children[this.leftItems].style.display = 'table-cell';
this.dom.list.children[this.dom.list.children.length - this.rightItems - 1].style.display = 'none';
}
// show or hide title
this.DrawName();
return item;
};
/**
* Extracts the last item
* @return {Node}
*/
CBreadCrumb.prototype.Pop = function () {
var item = this.dom.list.lastChild;
if ( item ) {
this.dom.list.removeChild(item);
// remove divider
if ( this.dom.list.children.length === this.leftItems + 1 ) {
this.dom.list.removeChild(this.dom.list.lastChild);
} else if ( this.dom.list.children.length > this.leftItems + this.rightItems ) {
this.dom.list.children[this.dom.list.children.length - this.rightItems].style.display = 'table-cell';
}
if ( this.dom.list.children.length === this.leftItems + this.rightItems + 1 ) {
this.dom.list.children[this.leftItems].style.display = 'none';
}
if ( this.dom.list.lastChild ) {
this.dom.list.lastChild.className = 'cbcrumb-item active';
}
// show or hide title
this.DrawName();
}
return item;
};
/**
* Gets the current item
* @return {Node}
*/
CBreadCrumb.prototype.Tip = function () {
return this.dom.list.lastChild;
};
/**
* Clears all the items
*/
CBreadCrumb.prototype.Reset = function () {
elclear(this.dom.list);
// show or hide title
this.DrawName();
};
/**
* Can handle a mouse click
*/
CBreadCrumb.prototype.onClick = function () {
return false;
};

194
mag/mini/system/cbpanel.js Normal file
View File

@ -0,0 +1,194 @@
'use strict';
/**
* Class for button with images panel.
* @class CButtonPanel
* @constructor
* @author Stanislav Kalashnik <sk@infomir.eu>
*/
function CButtonPanel ( parent ) {
// parent constructor
CBase.call(this, parent);
/**
* The component inner name
* @type {string}
*/
this.name = 'CButtonPanel';
/**
* CSS class name associated with the component
* @type {string}
*/
this.baseClass = 'cbpanel-main';
/**
* directory with icons
* depends on screen resolution
* @type {string}
*/
this.path = '';
/**
* Flag to indicate the component state
* @type {boolean}
*/
this.isActive = true;
}
// extending
CButtonPanel.prototype = Object.create(CBase.prototype);
CButtonPanel.prototype.constructor = CButtonPanel;
/**
* Component initialization with image path set.
* Should be called once before use just after constructor invoke and all hooks preparation.
* @param {string} path image path dependant on resolution
* @param {Node} [handle] component placeholder
*/
CButtonPanel.prototype.Init = function ( path, handle ) {
// global image path
this.path = path;
if ( handle && handle.nodeName ) {
// parent call init with placeholder
CBase.prototype.Init.call(this, handle);
} else {
// parent call init with placeholder
CBase.prototype.Init.call(this, element('div', {className:'cbpanel-main'}));
}
};
/**
* Append a new button
* @param {number} code keydown code
* @param {string} icon file of the button icon
* @param {string} text button title
* @param {Function} callback click/keyboard handler
* @param {boolean} [hidden=false] is button visible
* @returns {Node}
*/
CButtonPanel.prototype.Add = function ( code, icon, text, callback, hidden ) {
// prepare text
var html = null,
self = this,
func = function(){
if ( self.isActive && typeof callback === 'function' ) {
gSTB.HideVirtualKeyboard();
callback(code);
}
};
if ( text ) {
html = element('div', {className:'cbpanel-text', onclick:func});
html.innerHTML = text;
}
// build button item
var item = element('div', {className:'cbpanel-item', data:{code:code, onclick:func}}, [
element('img', {className:'cbpanel-icon', onclick:func, src:this.path + '/' + icon}),
html
]);
item.$name = html;
// apply visibility option
this.Hidden(item, hidden || false);
// add to component container
elchild(this.handleInner, item);
return item;
};
/**
* Manage the given item visibility
* @param {Node} item the group element to alter
* @param {boolean} state true - set hidden; false - set visible
*/
CButtonPanel.prototype.Hidden = function ( item, state ) {
// valid group object and states are different
if ( item && item.nodeName && item.data.hidden !== state ) {
// set inner attribute
item.data.hidden = state;
// actual show/hide
item.style.display = state ? 'none' : 'table-cell';
}
};
/**
* Set the given button name
* @param {Node} item the group element to alter
* @param {string} name new button name
*/
CButtonPanel.prototype.SetName = function ( item, name ) {
var html = null;
if ( item && item.nodeName && name ) {
if ( item.$name ) {
this.Rename(item, name);
} else {
html = element('div', {className:'cbpanel-text', onclick:item.data.onclick});
html.innerHTML = name;
item.$name = html;
item.appendChild(html);
}
}
};
/**
* Manage the given button name
* @param {Node} item the group element to alter
* @param {string} name new button name
*/
CButtonPanel.prototype.Rename = function ( item, name ) {
// valid group object and states are different
if ( item && item.nodeName) {
// actual rename
item.$name.innerHTML = name;
}
};
/**
* Makes the component active or disable it to start/stop event handling
* @param {boolean} [active=true]
*/
CButtonPanel.prototype.Activate = function ( active ) {
this.isActive = active !== false;
};
/**
* Handle external events.
*
* @param {Event} event - global event object
*/
CButtonPanel.prototype.EventHandler = function ( event ) {
var index, data, items, length;
if ( event.stopped === true ) {
return;
}
if ( this.isActive ) {
// iterate all items
for ( index = 0, items = this.handleInner.children, length = items.length; index < length; index += 1 ) {
data = items[index].data;
// check data, visibility and validate id
if ( !data.hidden && data.code === event.code && typeof data.onclick === 'function' ) {
if ( data.onclick() ) {
// stop event spreading
event.preventDefault();
}
return;
}
}
}
};
/**
* Can handle a mouse click
*/
CButtonPanel.prototype.onClick = function () {
return false;
};

View File

@ -0,0 +1,171 @@
'use strict';
/**
* Class for checkboxes in the portal
* @param {CBase} parent Object owner of the component
* @param {Object} parameters Initialization parameters
* @class CCheckBox
* @constructor
* @author Igor Kopanev
* @example checkbox = new CCheckBox(currCPage, {
* element: element // {Node} If checkbox already exist on the page you can init component from him.
* parentNode: parent // {Node} parent node for checkbox
* attributes: attributes // {Object} attributes that are applied to a check
* checked: true // {boolean} shows that the box is checked or not
* onchange: onchange // {Function} onchange event function
* });
* @throw if parentNode of element parameters is not specified
*/
function CCheckBox ( parent, parameters ){
var parentNode,
_default = !!parameters.checked,
self = this;
CBase.call(this, parent || null);
this.parameters = parameters || {};
this.parameters.attributes = parameters.attributes || {};
this.name = parameters.name || 'input';
this.type = this.tagName = 'checkbox';
this.style = parameters.style || '';
this.events = parameters.events || {};
// if parentNode not specified get the checkbox parentNode
if ((parentNode = parameters.parent) === undefined){
// if checkbox not specified to throw exception
if (parameters.element === undefined){
throw('parentNode or element parameter must be specified');
}
parentNode = parameters.element.parentNode;
}
this.bind(this.events);
this.IsChanged = function(){
return _default !== self.IsChecked();
};
this.Init(parentNode);
}
// extending
CCheckBox.prototype = Object.create(CBase.prototype);
CCheckBox.prototype.constructor = CCheckBox;
/**
* Default values for element initialization
* @type {Object}
*/
CCheckBox.DefaultAttributes = {
checkbox: {
type: 'checkbox',
className: 'ccheck-box-input'
},
label: {
className: 'ccheck-box-label'
},
wrapper: {
className: 'ccheck-box-wrapper',
tabIndex: '1'
}
};
/**
* Callback for CBase.Init function
* Checkbox initialize
*/
CCheckBox.prototype.onInit = function(){
if (this.parameters.element !== undefined && this.parameters.element.nodeName === 'INPUT' && this.parameters.element.type === 'checkbox'){
this.parameters.element.parentNode.removeChild(this.parameters.element);
this.checkbox = this.parameters.element;
elattr(this.checkbox, extend(CCheckBox.DefaultAttributes.checkbox, this.parameters.attributes.checkbox, true));
}
// if checkbox not specified in parameters or specified wrong then generate it
if (this.checkbox === undefined){
this.checkbox = this.generateCheckbox();
}
this.wrapper = this.generateWrapper(this.checkbox); // create wrapper
this.wrapper.className += ' ' + this.style;
if (this.parameters.checked === true) {
this.Check(true, true);
} // check checkbox if needed
elchild(this.handleInner, this.wrapper);
};
/**
* Generate input[type=checkbox] element with necessary parameters
* @returns {Node}
*/
CCheckBox.prototype.generateCheckbox = function() {
var self = this,
attrs = extend(CCheckBox.DefaultAttributes.checkbox, this.parameters.attributes.checkbox, true);
if (attrs.id === undefined) {
attrs.id = CCheckBox.GenerateId();
}
attrs.onchange = function(){
self.checked = this.checked;
self.trigger('onChange', arguments);
};
attrs.onclick = function() {
self.Check(!self.IsChecked());
};
return element('input', attrs);
};
/**
* Generate wrapper for checkbox and label
* @param checkbox {Node} current checkbox
* @param [label] {Node} label for this checkbox
* @returns {Node} new wrapper
*/
CCheckBox.prototype.generateWrapper = function( checkbox, label ){
var self = this;
if (this.checkbox === undefined){
throw('Checkbox must be created before label');
}
this.label = element('label', extend({htmlFor: this.checkbox.id}, CCheckBox.DefaultAttributes.label));
this.label.onclick = function(){self.Check(!self.IsChecked());};
return element('div', CCheckBox.DefaultAttributes.wrapper, [checkbox, label || this.label]);
};
/**
* Generate random id for checkbox
* @return {string}
*/
CCheckBox.GenerateId = function(){
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
};
/**
* Check or uncheck the checkbox
* @param {boolean} checked true - checked, false - unchecked
* @param {boolean} [block_event] true - not trigger onchange event
*/
CCheckBox.prototype.Check = function(checked, block_event){
this.checked = this.checkbox.checked = checked === true;
if (block_event !== true) {
this.trigger('onChange');
}
};
/**
* Return checkbox state
* @returns {boolean}
*/
CCheckBox.prototype.IsChecked = function(){
return this.checkbox.checked === true;
};
CCheckBox.prototype.EventHandler = function(event){
switch (event.code){
case KEYS.LEFT:
case KEYS.RIGHT:
this.Check(event.code === KEYS.LEFT);
event.stopped = true;
break;
default: break;
}
};
CCheckBox.prototype.focus = function(){
this.wrapper.focus();
};
Events.inject(CCheckBox);

296
mag/mini/system/cgmenu.js Normal file
View File

@ -0,0 +1,296 @@
'use strict';
/**
* Menu panel with groups module
* General use case:
* - create CGroupMenu object
* - add group/groups
* - fill group/groups with items
* - switch to the default group
* @class CGroupMenu
* @constructor
* @author Stanislav Kalashnik <sk@infomir.eu>
*/
function CGroupMenu ( parent ) {
// parent constructor
CBase.call(this, parent);
/**
* The component inner name
* @type {string}
*/
this.name = 'CGroupMenu';
/**
* CSS class name associated with the component
* @type {string}
*/
this.baseClass = 'cgmenu-main';
/**
* current active group
* @type {Node}
*/
this.activeGroup = null;
/**
* is the component has focus at the moment
* @type {boolean}
*/
this.active = false;
/**
* move focus to the first marked item in the group
* @type {boolean}
*/
this.autoFocus = true;
}
// extending
CGroupMenu.prototype = Object.create(CBase.prototype);
CGroupMenu.prototype.constructor = CGroupMenu;
/**
* Component initialization with its placeholder.
* Should be called once before use just after constructor invoke and all hooks preparation.
* @param {Node} handle component placeholder (it should be an empty div element)
*/
CGroupMenu.prototype.Init = function ( handle ) {
CBase.prototype.Init.call(this, handle);
this.handle.onclick = function(event){
// stop click
event.stopPropagation();
};
};
/**
* Finds and returns the group data object
* @param {string|number} gid unique name of the group
* @return {Object|boolean} found group data object or false
*/
CGroupMenu.prototype.GetGroup = function ( gid ) {
// iterate all items
for ( var i = 0, items = this.handleInner.children, length = items.length; i < length; i++ ) {
// check data and validate id
if ( items[i].gid === gid ) {
return items[i];
}
}
return false;
};
/**
* Add a new group with items
* @param {string|number} gid unique name of the group
* @param {string} title group visible title
* @param {Object} [options] list of the group attributes (onclick and hidden)
* @return {Node|boolean} group data object
*/
CGroupMenu.prototype.AddGroup = function ( gid, title, options ) {
var self = this;
// valid gid and not already exist
if ( gid && !this.GetGroup(gid) ) {
// CScrollList placeholder
var aleft, aright,
hlist = element('div', {className:'cslist-main'});
// create html
var group = element('table', {
gid : gid,
slist : new CScrollList(),
options : options || {}
}, [
// title row
element('tr', {className:'title'}, [
element('td', {className:'side', onclick:function(){
self.Switch(self.Next({hidden:false}, true), true);
}}, aleft = element('p')), // arrow left
element('td', null, title), // group title
element('td', {className:'side', onclick:function(){
self.Switch(self.Next({hidden:false}), true);
}}, aright = element('p')) // arrow right
]),
// group items row
element('tr', {className:'ilist'}, element('td', {colSpan:3}, hlist))
]
);
// correct group attributes if necessary
group.options.hidden = group.options.hidden ? true : false;
// create scroll list
group.slist.Init(hlist);
group.aleft = aleft;
group.aright = aright;
// add to dom
elchild(this.handleInner, group);
// ok
return group;
}
return false;
};
/**
* Manage the given group visibility
* @param {Node} group the group element to alter
* @param {boolean} state true - set hidden; false - set visible
*/
CGroupMenu.prototype.Hidden = function ( group, state ) {
// valid group object
if ( group && group.nodeName ) {
// set inner attribute
group.options.hidden = state;
}
};
/**
* Fill the given group with specified item
* @param {Object} group group data object
* @param {string|number} iid unique name of the group item
* @param {string|HTMLElement|Array} body group item title or complex content
* @param {Object} [options] group item additional attributes (hidden, marked, focused, disabled)
* @return {Node} created and added group item
*/
CGroupMenu.prototype.AddItem = function ( group, iid, body, options ) {
// prepare attributes list
options = options || {};
// item id
options.iid = iid;
// default handler for each item if not overwritten
if ( group.options.onclick && !options.onclick ) {
options.onclick = group.options.onclick;
}
if ( options.icon ) {
body = element('div', {className:'short'}, body);
body.style.backgroundImage = 'url("' + options.icon + '")';
body.style.backgroundPosition = 'right';
body.style.backgroundRepeat = 'no-repeat';
}
// add item to the group list
return group.slist.Add(body, options);
};
/**
* Apply visual styles for group arrows
* depending on the neighbour groups activity
*/
CGroupMenu.prototype.ApplyArrows = function () {
this.activeGroup.aright.className = this.Next({hidden:false}) ? 'active' : '';
this.activeGroup.aleft.className = this.Next({hidden:false}, true) ? 'active' : '';
};
/**
* Switch to the given group (hides the current one)
* @param {Object} group new active group
* @param {boolean} [activate] flag: true - make new visible group focused; false - skip activation (default)
* @return {boolean} operation status: true - successfully switched
*/
CGroupMenu.prototype.Switch = function ( group, activate ) {
// validate group object
if ( group && group.gid && group.nodeName ) {
// hide the previous one
if ( this.activeGroup ) {
this.activeGroup.slist.Activate(false);
this.activeGroup.style.display = 'none';
}
// set global active flag
this.activeGroup = group;
// show it
this.activeGroup.style.display = 'table';
// set focus and active state if necessary
if ( activate ) {
this.activeGroup.slist.Activate();
// sync focus to marked item
if ( this.autoFocus && Array.isArray(this.activeGroup.slist.states.marked) ) {
// get the first marked and focus it
this.activeGroup.slist.Focused(this.activeGroup.slist.states.marked[0], true);
}
}
// apply visual styles for group arrows
this.ApplyArrows();
// ok
return true;
}
return false;
};
/**
* Set active group
* @param {boolean} [active=true] set active or deactivate
* @param {boolean} [setFocus=true] focus handling mode: true - set/remove focus accordingly, false - manual focus management
* @return {boolean} operation status
*/
CGroupMenu.prototype.Activate = function ( active, setFocus ) {
var status = false;
if ( this.activeGroup !== null ) {
status = this.activeGroup.slist.Activate(active, setFocus);
}
// sync focus to marked item
if ( this.autoFocus && Array.isArray(this.activeGroup.slist.states.marked) ) {
// get the first marked and focus it
this.activeGroup.slist.Focused(this.activeGroup.slist.states.marked[0], true);
}
return status;
};
/**
* Get the next/previous item from the current item
* according to the given filter and search direction
* searching for a closest next item by default
* @param {Object} [filter=this.defaultFilter] list of attributes for searching
* @param {boolean} [reverse=false] to invert search direction (true - return previous, false - next)
* @return {Node|null} found item or null if there are no suitable ones
*/
CGroupMenu.prototype.Next = function ( filter, reverse ) {
// preparing
var match, // flag for items comparison
pointer = this.activeGroup; // the floating current item for processing
// there is a starting item
if ( pointer ) {
// iterate from the current position till the edge of the list
while ( (pointer = (reverse ? pointer.previousSibling : pointer.nextSibling)) ) {
// suitable by default
match = true;
// check all the filter attributes (all should match)
for ( var attr in filter ) {
if ( filter.hasOwnProperty(attr) ) {
match = match && (pointer.options[attr] === filter[attr]);
}
}
// suitable item is found
if ( match ) {
return pointer;
}
}
}
return null;
};
/**
* Handle external events
* @param {Event} event global event object
*/
CGroupMenu.prototype.EventHandler = function ( event ) {
// moving directions
switch ( event.code ) {
case KEYS.LEFT:
this.Switch(this.Next({hidden:false}, true), true);
break;
case KEYS.RIGHT:
this.Switch(this.Next({hidden:false}), true);
break;
default:
// delegate everything else to scroll list
this.activeGroup.slist.EventHandler(event);
}
};

400
mag/mini/system/cinput.js Normal file
View File

@ -0,0 +1,400 @@
'use strict';
var CINPUT_IMG_PATH = PATH_IMG_PUBLIC;
function CInput ( parent, parameters ) {
var _default = '', self = this;
this.parameters = parameters;
/**
* Default values for element initialization
* @type {Object}
*/
this.attributes = {
input : {
className: 'cinput-input',
type: parameters.type || 'text'
},
hint : {
className: 'cinput-hint cinput-input'
},
icon : {
className: 'cinput-icon'
},
wrapper : {
className: 'cinput-wrapper'
},
'text-wrapper': {
className: 'cinput-text-wrapper'
}
};
this.SetAutocomplete(parameters.autocomplete);
this.FoldedClass = 'folded';
if ( parameters.attributes !== undefined ) {
extend(this.attributes, parameters.attributes);
}
if ( parameters.folded === true ) {
this.attributes.wrapper.className += ' ' + this.FoldedClass;
}
if ( parameters.style !== undefined ) {
this.attributes.wrapper.className += ' ' + parameters.style;
}
this.events = parameters.events || {};
CBase.call(this, parent || null);
this.bind(this.events);
this.type = 'input';
this.name = parameters.name || 'input';
if ( !(this.parentNode = parameters.parent) ) {
if ( parameters.input === undefined ){
throw('parent or input parameter must be specified');
}
this.parentNode = parameters.input.parentNode;
}
this.parameters.icons = this.parameters.icons || [];
this.icons = {};
self.IsChanged = function () {
return _default !== self.GetValue();
};
self.GetDefault = function () {
return _default;
};
self.SetDefault = function ( def ){
_default = def;
};
this.Init(this.GenerateHandle());
elchild(this.parentNode, this.handle);
}
// extending
CInput.prototype = Object.create(CBase.prototype);
CInput.prototype.constructor = CInput;
CInput.prototype.GenerateHandle = function () {
var i, icon, attrs, changed,
self = this;
this.row = element('tr');
if ( this.parameters.hint !== undefined ) {
this.DefaultHint = this.attributes.hint.value = this.parameters.hint;
this.hint = element('input', extend(this.attributes.input, this.attributes.hint, false));
}
if ( this.parameters.input === undefined ) {
if ( this.parameters.value !== undefined ){
this.attributes.input.value = this.parameters.value;
this.SetDefault(this.parameters.value);
}
elchild(this.row, element('td', this.attributes['text-wrapper'], [this.input = element('input', this.attributes.input), this.hint || '']));
} else {
this.input = this.parameters.input;
this.input.parentNode.removeChild(this.input);
this.input.className += this.attributes.input.className;
elchild(this.row, element('td', {}, element('div', this.attributes['text-wrapper'], [this.input, this.hint || ''])));
if ( this.input.value !== '' ){
this.ShowHint(false);
this.SetDefault(this.input.value);
}
}
this.input.oninput = function ( e ) {
changed = false;
self.trigger('onChange', e);
if ( self.GetValue() !== '' ) {
self.ShowHint(false);
} else {
self.SetHint(self.DefaultHint);
self.ShowHint(true);
}
};
this.input.onblur = function () {
if ( self.GetValue() !== '' ) {
self.ShowHint(changed);
} else {
self.SetHint(self.DefaultHint);
self.ShowHint(true);
}
};
if ( this.parameters.icons !== undefined ) {
for ( i = 0; i < this.parameters.icons.length; i++ ) {
icon = this.parameters.icons[i];
attrs = extend({}, this.attributes.icon);
attrs.className += ' ' + (icon.style || '');
this.AddIcon(icon, attrs);
}
}
return element('table', this.attributes.wrapper, this.row);
};
CInput.prototype.SetAutocomplete = function ( autocomplete ){
this.autocomplete = autocomplete;
};
CInput.prototype.onInit = function () {
};
CInput.prototype.ShowIcon = function ( name, show ) {
if ( this.icons[name] !== undefined ) {
this.icons[name].parentNode.style.display = show === true ? 'table-cell' : 'none';
}
};
CInput.prototype.ApplyHint = function () {
this.SetValue(this.GetHint());
};
CInput.prototype.AddIcon = function ( icon, attrs ) {
var self = this, iconFile = null,
attributes = extend(icon.attributes, {}),
remoteControlButtonsImagesPath = configuration.newRemoteControl ? PATH_IMG_SYSTEM + 'buttons/new/' : PATH_IMG_SYSTEM + 'buttons/old/';
if ( icon.src !== undefined ) {
iconFile = icon.src;
} else {
iconFile = icon.name ? icon.name + '.png' : null;
}
if (iconFile === 'f4.png') {
if ( iconFile ) {
attributes.src = (this.parameters.icons_path || remoteControlButtonsImagesPath) + iconFile;
}
} else {
if ( iconFile ) {
attributes.src = (this.parameters.icons_path || CINPUT_IMG_PATH) + iconFile;
}
}
element('td', attrs || this.attributes.icon, this.icons[icon.name] = element('img', attributes));
if ( icon.type === 'left' ) {
this.row.insertBefore(this.icons[icon.name].parentNode, this.row.firstChild);
} else {
this.row.appendChild(this.icons[icon.name].parentNode);
}
if ( typeof icon.click === 'function' ) {
this.icons[icon.name].onclick = function ( event ) {
icon.click.call(self, event);
};
}
};
CInput.prototype.RemoveIcon = function ( icon_name ) {
if ( this.icons[icon_name] !== undefined ) {
this.row.removeChild(this.icons[icon_name].parentNode);
}
};
/**
*
* @param value
* @param [siltent]
* @constructor
*/
CInput.prototype.SetValue = function ( value, siltent ) {
this.input.value = value;
this.ShowHint(value === '');
if ( siltent !== true ){
this.trigger('onChange');
this.input.oninput();
}
};
CInput.prototype.GetValue = function () {
return this.input.value;
};
CInput.prototype.SetHint = function ( hint ) {
if ( this.hint !== undefined ){
this.hint.value = hint;
return this.hint.value;
}
};
CInput.prototype.GetHint = function () {
if ( this.hint !== undefined ){
return this.hint.value;
}
};
CInput.prototype.ShowHint = function ( show ) {
if ( this.hint !== undefined ){
this.hint.style.display = show === true ? 'block' : 'none';
}
};
/**
*
* @param fold
* @param [stop_event]
* @param [manage_focus]
* @constructor
*/
CInput.prototype.Fold = function ( fold, manage_focus, stop_event ) {
if ( fold === true ) {
if ( this.handle.className.indexOf('folded') === -1 ) {
this.handle.className += ' ' + this.FoldedClass;
if ( stop_event !== true ) {
this.trigger('onFold');
}
if ( manage_focus === true ) {
this.blur();
}
}
gSTB.HideVirtualKeyboard();
} else {
this.handle.className = this.handle.className.replace(this.FoldedClass, '');
if ( stop_event !== true ) {
this.trigger('onUnfold');
}
if ( manage_focus === true ) {
this.focus();
gSTB.ShowVirtualKeyboard();
}
}
};
CInput.prototype.Reset = function () {
this.Fold(this.parameters.folded === true, true);
this.SetValue(this.GetDefault(), true);
};
CInput.prototype.focus = function () {
this.input.focus();
};
CInput.prototype.blur = function () {
this.input.blur();
};
CInput.prototype.select = function () {
this.input.select();
};
/**
* @return {boolean}
*/
CInput.prototype.IsFolded = function () {
return this.handle.className.indexOf(this.FoldedClass) !== -1;
};
/**
* @return {boolean}
*/
CInput.prototype.IsFocused = function () {
return this.input === document.activeElement;
};
CInput.prototype.EventHandler = function () {};
Events.inject(CInput);
/**
* Filter input component
* @param parent
* @param parameters
* @constructor
* @example weather_location = new CFilterInput(this, {
input: document.getElementById("place"),
hint: "Enter city name please...",
folded: true,
events:{
onChange: function(){
weather.getSuggestsList(this.GetValue());
},
onUnfold: function(){
WeatherPage.$bcrumb.style.display = 'none';
},
onFold: function(){
WeatherPage.$bcrumb.style.display = 'table-cell';
},
onKey: function(){
weather.newLocation();
},
onExit: function(){
WeatherPage.actionExit();
}
}
});
*/
function CFilterInput ( parent, parameters ) {
parameters = extend({
style: 'main-filter-input',
icons: [
{
name : 'ico_search',
type : 'left',
style: 'black_search'
},
{
name : 'ico_search2',
type : 'left',
style: 'white_search'
},
{
name : 'f4',
click: function () {
this.Fold(!this.IsFolded(), true);
this.trigger('onKey');
}
}
]
}, parameters);
CInput.call(this, parent || null, parameters);
}
// extending
CFilterInput.prototype = Object.create(CInput.prototype);
CFilterInput.prototype.constructor = CFilterInput;
// true - stop next events
/**
* @return {boolean}
*/
CFilterInput.prototype.EventHandler = function ( event ) {
eventPrepare(event, true, 'CFilterInput');
var event_res = false;
if ( this.trigger('onEvent', event)[0] === true ) {
return true;
}
switch ( event.code ) {
case KEYS.F4:
this.Fold(!this.IsFolded(), true);
event_res = this.trigger('onKey')[0];
break;
case KEYS.EXIT:
if ( !this.IsFolded() ) {
this.Fold(true, true);
this.trigger('onExit');
return true;
}
break;
case KEYS.OK:
if ( !this.IsFolded() ) {
event_res = this.trigger('onEnter')[0];
echo(event_res, 'onEnter');
this.Fold(true, true);
event.preventDefault();
}
break;
case KEYS.CHANNEL_NEXT:
case KEYS.CHANNEL_PREV:
event.preventDefault();
break;
}
if ( this.IsFolded() !== true || event_res === true ) {
return true;
}
};

860
mag/mini/system/clist.js Normal file
View File

@ -0,0 +1,860 @@
/**
* Item list without native scroll navigation module
* @author Roman Stoian
*/
'use strict';
/**
* @class CList
* @param parent
* @param {Object} customParams
* @constructor
*/
function CList ( parent, customParams ) {
// parent constructor
CBase.call( this, parent );
if ( !customParams ) {
customParams = {};
}
/**
* The component inner name
* @type {string}
*/
this.name = 'CList';
/**
* CSS class name associated with the component
* @type {string}
*/
this.baseClass = 'clist-main';
/**
* Shows the possibility of multiple selection
* @type {boolean}
*/
this.multipleSelection = true;
/**
* the current selected item
* @type {Node}
*/
this.activeItem = null;
this.items = [];
this.list = [];
this.itemIndex = 0;
this.listIndex = 0;
this.pageIndex = 0;
this.itemsHandle = null;
this.scrollHandle = null;
this.scrollThumb = null;
/**
* List of items for each state flag
* Example: {focused:[item], marked:[item,item]}
* @type {Object}
*/
this.states = {};
/**
* Default settings for focus management in list
* @type {boolean}
*/
this.manageFocus = false;
/**
* default item attribute values
* used for an item initialization
* @namespace
* @property {boolean} hidden display or not
* @property {boolean} marked initial checked state
* @property {boolean} disabled can be focused or not
* @property {boolean} focused initial focused state
*/
this.defaultParams = {
hidden: false, // display or not
marked: false, // initial checked state
disabled: false, // can be focused or not
focused: false, // initial focused state
self: this, // back link to the component itself
index: -1, // index of list elements
data: null,
// flag to manage focus handling
manageFocus: false,
// right mouse click (suppress the context menu)
oncontextmenu: EMULATION? null : function () {
return false;
},
// mouse click on the item or Ok/Enter key
onclick: function () {
// activate item
this.self.OnMouseover( this );
this.self.pressOK( this );
return false;
},
onmouseover: function () {
// activate item
this.self.OnMouseover( this );
return false;
}
};
if ( customParams.defaultParams ) {
this.defaultParams = extend ( this.defaultParams, customParams.defaultParams );
}
this.onPage = customParams.onPage ? customParams.onPage : (function () {
var count = {
480: 5,
576: 6,
720: 7,
1080: 7
};
return count[ screen.height ];
})();
this.fillsName = customParams.fillsName ? customParams.fillsName : {
text: 'text',
data: 'data'
};
/**
* default item filter values
* used for focus handling
* @type {Object}
*/
this.defaultFilter = {
hidden: false, // visible
disabled: false // enabled
};
/**
* buffer for added items in the bulk mode
* @type {DocumentFragment}
*/
this.fragment = document.createDocumentFragment();
}
// extending
CList.prototype = Object.create ( CBase.prototype );
CList.prototype.constructor = CList;
/**
* set list.
* @param {Array} list
*/
CList.prototype.SetList = function ( list ) {
if ( Array.isArray ( list ) ) {
this.list = list;
return true;
}
return false;
};
/**
* set list.
* @param {Array} arr list elements
* @param {number} start position in list
* @param {number} deleteCount count delete elements in list
*/
CList.prototype.InsertToList = function ( arr, start, deleteCount ) {
var lastIndex = 0,
activeItem = this.list[ this.listIndex ];
if ( Array.isArray( arr ) && this.list.length !== 0 ) {
lastIndex = ( this.pageIndex + 1 ) * this.onPage - 1;
if ( !start && start !== 0 ) {
start = this.list.length;
}
if ( !deleteCount ) {
deleteCount = 0;
} else {
this.list.splice ( start, deleteCount );
}
for ( var i = 0; i < arr.length; i++ ) {
this.list.splice ( start + i, 0, arr[ i ] );
}
if ( lastIndex >= start ) {
if ( start <= this.listIndex ) {
this.listIndex = this.FindByElement( activeItem );
this.RefreshPageIndex();
}
this.FillItems();
}
return true;
}
return false;
};
/**
* set list.
* @param {Array} arr list elements
*/
CList.prototype.DeleteFromList = function ( arr ) {
var index = -1,
listIndex = this.listIndex,
activeItem = this.list[ this.listIndex ];
if ( Array.isArray( arr ) && this.list.length !== 0 ) {
for ( var i = 0; i < arr.length; i++ ){
index = this.list.indexOf( arr[ i ] );
if( index !== -1 ) {
this.list.splice( index, 1 );
if ( index < listIndex ) {
listIndex--;
}
}
}
index = this.list.indexOf( activeItem );
if ( index === -1 ) {
this.listIndex = listIndex;
if ( this.listIndex >= this.list.length ){
this.listIndex = this.list.length - 1;
}
} else {
this.listIndex = index;
}
this.RefreshPageIndex();
this.FillItems();
return true;
}
return false;
};
/**
* refresh page index.
* @return {boolean} change page
*/
CList.prototype.RefreshPageIndex = function () {
var pageIndex = this.pageIndex;
this.pageIndex = Math.floor( this.listIndex / this.onPage );
this.itemIndex = this.listIndex % this.onPage;
return pageIndex !== this.pageIndex;
};
/**
* find by element in list.
* @param {Object} obj list elements
* @return {number} index of element in list
*/
CList.prototype.FindByElement = function ( obj ) {
return this.list.indexOf( obj );
};
/**
* find by fild of element in list.
* @param {*} value
* @param {string} fild of list element
* @return {number} index of element in list
*/
CList.prototype.FindByFild = function ( value, fild ) {
var index = -1,
map = [];
if ( value !== undefined ) {
if ( !fild ) {
fild = this.fillsName.data;
}
map = this.list.map( function ( item ) {
return item[ fild ];
});
index = map.indexOf( value );
}
return index;
};
/**
* Component initialization with its placeholder.
* Should be called once before use just after constructor invoke and all hooks preparation.
* @param {Node} handle component placeholder (it should be an empty div element)
*/
CList.prototype.Init = function ( handle ) {
// parent call init with placeholder
CBase.prototype.Init.call( this, handle );
var self = this,
table = null;
table = element ( 'table', {className: 'maxh maxw'},
element ( 'tr', {}, [
this.itemsHandle = element ( 'td', {className: 'list'} ),
element ('td', {className: 'scroll'},
this.scrollHandle = element ( 'div' )
)
])
);
elchild ( this.scrollHandle, this.scrollThumb = element ( 'div', {className: 'thumb'} ) );
elchild ( this.handleInner, table );
this.handleInner.onmousewheel = function (event) {
// direction and new focused item
var direction = event.wheelDeltaY > 0;
var found = direction ? self.Prev() : self.Next();
// apply
if (found) {
self.Focused(found, true);
}
event.stopPropagation();
// prevent
return false;
};
this.RenderBody();
};
/**
* On show event
*/
CList.prototype.onShow = function () {
this.setScroll();
};
/**
* Add all the added items to the DOM
*/
CList.prototype.RenderBody = function () {
if (this.itemsHandle.children.length === 0 || this.items.length === 0) { //generate dome elements if this need
this.items = [];
for (var i = 0; i < this.onPage; i++) {
this.items[i] = this.fragment.appendChild( element ( 'div', this.defaultParams ));
elchild(this.items[i],this.RenderItem());
}
this.itemsHandle.appendChild( this.fragment );
}
};
/**
* Generate body item
*/
CList.prototype.RenderItem = function () {
return null;
};
/**
* set scroll position
*/
CList.prototype.setScroll = function () {
var margin = 0,
percent = 0;
percent = Math.ceil ( this.onPage / this.list.length * 100 );
if (percent >= 100) {
percent = 0;
}
margin = Math.ceil ( this.scrollHandle.offsetHeight / Math.ceil( this.list.length / this.onPage ) * this.pageIndex );
this.scrollThumb.style.height = percent + '%';
this.scrollThumb.style.marginTop = margin + 'px';
};
/**
* Fill items
* @params {boolean} noFocus don't set focus
*/
CList.prototype.FillItems = function ( noFocus ) {
var startPos = this.onPage * this.pageIndex,
listLength = this.list.length,
active = null,
list = [];
for (var i = 0; i < this.onPage; i++) {
if ( listLength > i + startPos ) {
this.items[ i ].innerHTML = this.list[ i + startPos ][ this.fillsName.text ];
list.push( this.list[ i + startPos ] );
if ( this.list[ i + startPos ][ this.fillsName.data ] ) {
this.items[ i ].data = this.list[ i + startPos ][ this.fillsName.data ];
}
this.items[ i ].index = i + startPos;
this.Hidden( this.items[ i ], false );
if ( i + startPos === this.listIndex ) {
active = this.items[ i ];
}
} else {
this.items[ i ].index = -1;
this.items[ i ].data = null;
this.Hidden( this.items[ i ], true );
}
}
this.Reset ( active );
if ( !noFocus ) {
this.Focused( active, true );
}
this.setScroll();
if ( typeof this.onFillItems === 'function' ) {
this.onFillItems( active, list );
}
return active;
};
/**
* Fill items
* @params {Object} active item
* @param {Array} list items on page
*/
CList.prototype.onFillItems = null;
/**
* Reset and clear all items and options.
* This will make the component ready for a new filling.
*/
CList.prototype.Clear = function () {
// cleaning all items
this.list = [];
this.pageIndex = 0;
this.itemIndex = 0;
this.listIndex = 0;
// vars
this.activeItem = null;
this.states = {};
this.FillItems();
};
/**
* Reset only the given item to the default state
* @param {Node} item the element to be processed
*/
CList.prototype.Reset = function ( item ) {
// valid html element given
if ( item && item.nodeName ) {
// apply flags and decoration
this.Hidden( item, this.defaultParams.hidden );
this.Marked( item, this.defaultParams.marked );
this.Disabled( item, this.defaultParams.disabled );
// clear focus pointer if necessary
if ( item === this.activeItem && !item.focused ) {
this.activeItem = null;
}
this.Focused( item, this.defaultParams.focused );
}
};
/**
* Getter for list length
* @return {number}
*/
CList.prototype.Length = function () {
return this.list.length;
};
/**
* Set inner item flags and decoration
* @param {Node} item the element to be processed
* @param {string} option item inner flag name
* @param {boolean} state flag of the operation (true if change is made)
* @return {boolean} operation status
*/
CList.prototype.SetState = function ( item, option, state ) {
state = Boolean( state );
// current and new states are different
if ( item[ option ] !== state ) {
// check if exist
if ( !this.states[ option ] ) {
this.states[ option ] = [];
}
var index = this.states[ option ].indexOf( item );
// update internal list
if ( state ) {
// add to the list
if ( index === -1 ) {
this.states[ option ].push( item );
}
} else {
// remove
if ( index !== -1 ) {
this.states[ option ].splice( index, 1 );
}
}
var oldVal = item[ option ];
// flag
item[ option ] = state;
// decoration
if ( state ) {
// add the corresponding class
item.classList.add( option );
} else {
// remove the corresponding class
item.classList.remove( option );
}
// call user hook
if ( typeof this.onStateChange === 'function' ) {
this.onStateChange( item, option, oldVal, state );
}
return true;
}
// nothing has changed
return false;
};
/**
* Handle visibility state for the given item
* also correct check/focus state if hiding
* @param {Node} item the element to be processed
* @param {boolean} state flag of the state
* @return {boolean} operation status
*/
CList.prototype.Hidden = function ( item, state ) {
state = Boolean( state );
// valid html element given
if ( item && item.nodeName ) {
// flag and decoration
var changed = this.SetState( item, 'hidden', state );
// operation ok and the item is hidden
if (changed && state) {
// clear internal cursor if necessary
if ( item.focused ) {
this.activeItem = null;
}
// uncheck and remove focus
this.SetState( item, 'marked', false );
this.SetState( item, 'focused', false );
}
// operation status
return changed;
}
// failure
return false;
};
/**
* Handle checked state for the given item
* @param {Node} item the element to be processed
* @param {boolean} state flag of the state
* @return {boolean} operation status
*/
CList.prototype.Marked = function ( item, state ) {
var self = this;
state = Boolean( state );
// valid html element given, enabled and visible
if ( item && item.nodeName && !item.disabled && !item.hidden ) {
if ( this.multipleSelection === false ) {
( this.states.marked || [] ).forEach( function ( marked ) {
self.SetState( marked, 'marked', false );
});
}
// operation status
return this.SetState( item, 'marked', state );
}
// failure
return false;
};
/**
* Handle enable/disable state for the given item
* @param {Node} item the element to be processed
* @param {boolean} state flag of the state
* @return {boolean} operation status
*/
CList.prototype.Disabled = function ( item, state ) {
state = Boolean( state );
// valid html element given
if ( item && item.nodeName ) {
// flag and decoration
var changed = this.SetState( item, 'disabled', state );
// operation ok and the item is disabled
if ( changed && state ) {
// clear internal cursor if necessary
if ( item.focused ) {
this.activeItem = null;
}
// uncheck and remove focus
this.SetState( item, 'marked', false );
this.SetState( item, 'focused', false );
}
// operation status
return changed;
}
// failure
return false;
};
/**
* Handle focus state for the given item on mouseover
* also removes the focus from the previously focused item
* @param {Node} item the element to be processed
*/
CList.prototype.OnMouseover = function ( item ) {
if ( this.listIndex === item.index ) {
return false;
}
this.listIndex = item.index;
this.RefreshPageIndex();
this.Focused( item, true );
};
/**
* Handle focus state for the given item
* also removes the focus from the previously focused item
* @param {Node} item the element to be processed
* @param {boolean} [state=true] flag of the state
* @param {boolean} [manageFocus=true] flag to manage focus handling
* @return {boolean} operation status
*/
CList.prototype.Focused = function ( item, state, manageFocus ) {
var changed = false,
prevent = false;
state = state !== false;
if ( manageFocus === undefined ) {
manageFocus = this.manageFocus;
}
// valid html element given, enabled and visible
if ( item && item.nodeName && !item.disabled && !item.hidden ) {
// states differ
if ( state !== item.focused ) {
if ( state ) {
// different items (not currently active item)
if ( item !== this.activeItem ) {
// call user hook which can prevent further processing
if ( typeof this.onFocus === 'function' ) {
prevent = this.onFocus( item, this.activeItem );
}
// block or not
if ( !prevent ) {
// flag and decoration
changed = this.SetState( item, 'focused', state );
// clear the previously focused item
this.Focused( this.activeItem, false, manageFocus );
// global flag
this.activeItem = item;
// set actual focus if necessary
if ( manageFocus !== false ) {
this.activeItem.focus();
}
}
}
} else {
// flag and decoration
changed = this.SetState( item, 'focused', state );
// focus removed if necessary
if ( manageFocus !== false ) {
this.activeItem.blur();
}
this.activeItem = null;
}
}
}
// operation status
return changed;
};
/**
* Make the whole component active, set focused item and give actual focus
* give a focus to the appropriate item (last focused or the first one)
* @param {boolean} [state=true] set active or deactivate
* @param {boolean} [manageFocus=true] focus handling mode: true - set/remove focus accordingly, false - manual focus management
* @return {boolean} operation status
*/
CList.prototype.Activate = function ( state, manageFocus ) {
if ( manageFocus === undefined ) {
manageFocus = this.manageFocus;
}
// parent call
CBase.prototype.Activate.call( this, state );
if ( this.isActive ) {
// get the first good one
this.activeItem = this.activeItem || this.ActiveItem();
// still no active item
if ( this.activeItem === null ) {
return false;
}
// flag and decoration
this.itemIndex = this.activeItem.index;
if ( this.RefreshPageIndex() ) {
this.FillItems();
} else {
this.SetState( this.activeItem, 'focused', true );
}
// make it focused
if ( manageFocus === true ) {
this.activeItem.focus();
}
} else {
// remove focus if there is an element
if ( this.activeItem ) {
this.activeItem.blur();
}
}
// all is ok
return true;
};
/**
* Get active item
* @return {Node|null} found item or null
*/
CList.prototype.ActiveItem = function () {
return this.items[ this.itemIndex ] || null;
};
/**
* Get active items
* @return {Array} found items
*/
CList.prototype.ActiveItems = function () {
var list = [];
this.items.forEach( function ( item ) {
if ( !item.disabled && !item.hidden ) {
list.push( item );
}
});
return list;
};
/**
* Get the next item from the current focused item
* can go to the next page
* @param {count} [count=1] count steps next
* @return {Node|null} found item or null if there are no suitable ones
*/
CList.prototype.Next = function ( count ) {
var next = null,
endList = false;
count = count ? count > 0 ? count : 1 : 1;
this.itemIndex += count;
this.listIndex += count;
if ( this.listIndex >= this.list.length ) {
this.listIndex = this.list.length - 1;
endList = true;
}
if ( this.itemIndex >= this.onPage || endList ) {
if ( this.RefreshPageIndex() ) {
next = this.FillItems( false );
} else {
next = this.items[ this.itemIndex ];
}
} else {
next = this.items[ this.itemIndex ];
}
this.Focused( next, true );
return next;
};
/**
* Get the previous item from the current focused item
* can go to the previous page
* @param {count} [count=1] count steps next
* @return {Node|null} found item or null if there are no suitable ones
*/
CList.prototype.Prev = function ( count ) {
var prev = null;
count = count ? count > 0 ? count : 1 : 1;
this.itemIndex -= count;
this.listIndex -= count;
if ( this.listIndex < 0 ) {
this.listIndex = 0;
}
if ( this.itemIndex < 0 ) {
if ( this.RefreshPageIndex() ) {
prev = this.FillItems( false );
} else {
prev = this.items[ this.itemIndex ];
}
} else {
prev = this.items[ this.itemIndex ];
}
this.Focused( prev, true );
return prev;
};
/**
* Set position some list element
* @param {Object|Node} item of list or dome
* @param {boolean} [manageFocus] - set actual focus
*/
CList.prototype.SetPosition = function ( item, makeFocused ) {
var index = this.items.indexOf( item );
if ( index === -1 ) {
index = this.list.indexOf( item );
} else {
index = item.index;
}
if ( index === -1 || index === undefined ) {
return false;
}
this.listIndex = index;
if ( this.RefreshPageIndex() ) {
this.FillItems();
} else if ( makeFocused ) {
this.Focused( this.items[ this.itemIndex ], true );
}
return true;
};
/**
* Handle external events
* @param {Event} event global event object
* @param {Object} [filter=this.defaultFilter] list of attributes for searching
* @param {boolean} [manageFocus=this.manageFocus]
*/
CList.prototype.EventHandler = function ( event, filter, manageFocus ) {
var found = null;
if ( manageFocus === undefined ) {
manageFocus = this.manageFocus;
}
if ( event.stopped === true ) {
return;
}
// moving direction
switch ( event.code ) {
case KEYS.PAGE_UP:
this.Prev( this.onPage );
break;
case KEYS.PAGE_DOWN:
this.Next( this.onPage );
break;
case KEYS.LEFT:
case KEYS.HOME:
this.Prev( this.list.length );
break;
case KEYS.RIGHT:
case KEYS.END:
this.Next( this.list.length );
break;
case KEYS.UP:
this.Prev();
break;
case KEYS.DOWN:
this.Next();
break;
case KEYS.OK:
this.pressOK(this.ActiveItem());
event.preventDefault();
return;
default:
// suppress everything else and exit
return;
}
event.preventDefault();
// make focused the first item if not found
this.Focused( found || this.ActiveItem(), true, manageFocus );
};
/**
* Hook method on focus item change
* should be declared in child to invoke
* @param {Node} item the new focused item
*/
CList.prototype.pressOK = function () {};
/**
* Hook method on focus item change
* should be declared in child to invoke
* @param {Node} item the new focused item
* @param {Node} previous the old focused item
* @return {boolean} true - prevent focus from changing, false|undefined - usual behaviour
*/
CList.prototype.onFocus = null;
/**
* Hook method on item internal states change
* should be declared in child to invoke
* @param {Node} item the new focused item
* @param {string} option affected item state name
* @param {string|boolean} oldVal previous state value
* @param {string|boolean} newVal new state value
*/
CList.prototype.onStateChange = null;

55
mag/mini/system/clog.js Normal file
View File

@ -0,0 +1,55 @@
'use strict';
var CLog = function(parent, options){
var self = this;
CBase.call(this, parent || null);
this.newest = false;
CLog.parameters.forEach(function(option){
if (options[option] !== undefined) {
self[option] = options[option];
}
});
this.bind(options.events || {});
this.Init(element('div', {className: 'clog'}, this.$log = element('ul', {})));
};
CLog.parameters = ['time', 'autofocus', 'parentNode', 'events', 'defaultType', 'newest', 'isVisible'];
// extending
CLog.prototype = Object.create(CBase.prototype);
CLog.prototype.constructor = CLog;
CLog.prototype.onInit = function(){
elchild(this.parentNode, this.handle);
};
CLog.prototype.onShow = function(){
if (this.autofocus === true && this.$log.childElementCount !== 0){
this.$log.lastElementChild.focus();
}
this.trigger('onShow');
};
CLog.prototype.onHide = function(){
this.trigger('onHide');
};
CLog.prototype.Add = function( message, type){
if (this.newest === false || (this.newest === true && this._lastMessage !== message)){
this._lastMessage = message;
var currTime = new Date(),
$data = [element('span', {className: 'message'}, message)];
if (this.time === true) {
$data.push(element('span', {className: 'time'}, ('0' + currTime.getHours()).slice(-2) + ':' + ('0' + currTime.getMinutes()).slice(-2) + ':' + ('0' + currTime.getSeconds()).slice(-2)));
}
elchild(this.$log,
element('li', {className: type || this.defaultType, tabIndex: '1'}, $data)
);
if (this.autofocus === true) {
this.$log.lastElementChild.focus();
}
this.trigger('onAdd');
}
};
Events.inject(CLog);

115
mag/mini/system/cmap.js Normal file
View File

@ -0,0 +1,115 @@
'use strict';
/**
* Base class for Google Map
* @class CMap
* @constructor
* @author Denys Vasylyev
*/
function CMap ( parent, parameters ) {
/**
* Map object
* @type {Object}
*/
this.map = null;
/**
* Map marker object
* @type {Object}
*/
this.marker = null;
/**
* Map container
* @type {Node}
*/
this.container = parameters.container;
/**
* Events object
* @type {Object}
*/
this.events = parameters.events || {};
/**
* Global variable determines whether the map is loaded
* @type {boolean}
*/
window.googleMapsApiScriptLoaded = window.googleMapsApiScriptLoaded || false;
CBase.call(this, parent || null);
this.bind(this.events);
}
// extending
CMap.prototype = Object.create(CBase.prototype);
CMap.prototype.constructor = CMap;
/**
* Load Google Maps Api script method with the callback function in url
* @param {string} objTitle string with the name of the object in which the callback function will be called
* @param {string} language code of the displayed map
*/
CMap.prototype.LoadGoogleMapsApiScript = function ( objTitle, language ) {
loadScript('https://maps.googleapis.com/maps/api/js?callback=' + objTitle + '.GoogleMapsApiScriptCallback&language=' + language);
};
/**
* Function call when the script is loaded
*/
CMap.prototype.GoogleMapsApiScriptCallback = function () {
window.googleMapsApiScriptLoaded = true;
this.trigger('onGoogleMapsApiScriptCallback');
};
/**
* Build google map method
* @param {Object} mapOptions object with properties to create or modify map
* @param {boolean} useMarker show/hide map marker
* @param {boolean} showMapIfExists show/hide map if map object already exists
*/
CMap.prototype.BuildMap = function ( mapOptions, useMarker, showMapIfExists ) {
var self = this;
if ( this.map ) {
this.map.setOptions(mapOptions);
if ( showMapIfExists ) {
this.Visible(true);
}
} else {
this.map = new google.maps.Map(this.container, mapOptions);
}
if ( useMarker ) {
if ( this.marker ) {
this.marker.setPosition(mapOptions.center);
} else {
this.marker = new google.maps.Marker({
position: mapOptions.center,
map: this.map
});
}
this.marker.setVisible(true);
} else {
if ( this.marker ) {
this.marker.setVisible(false);
}
}
google.maps.event.addListenerOnce(this.map, 'tilesloaded', function() {
self.trigger('onGoogleMapReady');
});
};
/**
* Set visibility mode
* @param {boolean} [mode=false] show/hide the component
*/
CMap.prototype.Visible = function ( mode ) {
this.container.style.visibility = Boolean(mode) ? 'visible' : 'hidden';
};
Events.inject(CMap);

470
mag/mini/system/cmodal.js Normal file
View File

@ -0,0 +1,470 @@
/**
* Modal windows of different types
* CModal
* CModalBox
* CModalHint
* CModalAlert
* CModalConfirm
* @author Stanislav Kalashnik <sk@infomir.eu>
*/
'use strict';
// default icon images path
var CMODAL_IMG_PATH = window.configuration && configuration.newRemoteControl ? PATH_IMG_SYSTEM + 'buttons/new/' : PATH_IMG_SYSTEM + 'buttons/old/';
/**
* Class for modal windows and messages.
* Default use case:
* - create
* - set title/content/footer
* - init (after the DOM is ready)
* - show
* - hide/destroy
* @class CModal
* @constructor
* @author Stanislav Kalashnik <sk@infomir.eu>
*/
function CModal ( parent ) {
// parent constructor
CPage.call(this, parent);
/**
* The component inner name
* @type {string}
*/
this.name = 'CModal';
/**
* CSS class name associated with the component
* @type {string}
*/
this.baseClass = 'cmodal-main';
/**
* CSS "display" attribute value to make the component visible
* to hide the default value is "none"
* @type {string}
*/
this.showAttr = 'table';
this.focusList = [];
this.focusPos = 0;
}
// extending
CModal.prototype = Object.create(CPage.prototype);
CModal.prototype.constructor = CModal;
/**
* Prepare html and all placeholders
* @param {Node|string} body window content
*/
CModal.prototype.Init = function ( body ) {
// parent call init with placeholder
CPage.prototype.Init.call(this,
element('div', {className: this.baseClass},
element('div', {className:'cmodal-cell'},
body
)));
// get the node to append to
var owner = (this.parent && this.parent.handle && this.parent.handle.nodeName ? this.parent.handle : document.body);
// get the upper parent if exist in order to prevent nesting
if ( this.parent instanceof CModal && this.parent.parent ) {
owner = this.parent.parent.handle;
}
// fill
owner.appendChild(this.handle);
};
/**
* Destroy the window and free resources
*/
CModal.prototype.Free = function () {
// global or local clearing
if ( this.handle && this.handle.parentNode ) {
this.handle.parentNode.removeChild(this.handle);
}
elclear(this.handle);
};
/**
* Manage the window visibility
* also enable/disable parent window event handling
* @param {boolean} [visible=true] true - visible; false - hidden
* @param {boolean} [manageFocus=true] focus handling mode: true - set/remove focus accordingly, false - manual focus management
*/
CModal.prototype.Show = function ( visible, manageFocus ) {
// parent call
CBase.prototype.Show.call(this, visible, manageFocus !== false);
if ( visible === false ) { // hide
window.currCPage = this.parent;
} else { // show
window.currCPage = this;
}
};
/**
* Move focus to the previous element from the focusList set
*/
CModal.prototype.FocusPrev = function ( event, manageVK ) {
if ( this.focusList.length > 0 ) {
// cycling the index
if ( --this.focusPos < 0 ) {
this.focusPos = this.focusList.length-1;
}
// get the next html element in the list
var el = this.focusList[this.focusPos];
if ( manageVK !== false ) {
gSTB.HideVirtualKeyboard();
}
// set focus
el.focus();
// skip looping select options elements
if ( event && el.tagName === 'INPUT' && el.type === 'text' ) {
event.preventDefault();
if ( manageVK !== false ) {
gSTB.ShowVirtualKeyboard();
}
}
}
};
/**
* Move focus to the next element from the focusList set
*/
CModal.prototype.FocusNext = function ( event, manageVK ) {
if ( this.focusList.length > 0 ) {
// cycling the index
if ( ++this.focusPos >= this.focusList.length ) {
this.focusPos = 0;
}
// get the next html element in the list
var el = this.focusList[this.focusPos];
if ( manageVK !== false ) {
gSTB.HideVirtualKeyboard();
}
// set focus
el.focus();
// skip looping select options elements
if ( event && el.tagName === 'INPUT' && el.type === 'text' ) {
event.preventDefault();
if ( manageVK !== false ) {
gSTB.ShowVirtualKeyboard();
}
}
}
};
///////////////////////////////////////////////////////////////////////////////
/**
* Show small modal info panel which automatically hides in the given time
* @class CModalBox
* @constructor
* @example
* var mb = new CModalBox();
*/
function CModalBox ( parent ) {
// parent constructor
CModal.call(this, parent);
/**
* The component inner name
* @type {string}
*/
this.name = 'CModalBox';
/**
* html element for window title
* @type {Node}
*/
this.header = element('div', {className:'cmodal-header'});
/**
* html element for window main content
* @type {Node}
*/
this.content = element('div', {className:'cmodal-content'});
/**
* html element for window bottom panel
* @type {Node}
*/
this.footer = element('div', {className:'cmodal-footer'});
}
// extending
CModalBox.prototype = Object.create(CModal.prototype);
CModalBox.prototype.constructor = CModalBox;
/**
* Internal method to update one of the placeholders
* makes the inserted node visible
* @param {Node|Element|HTMLElement} place placeholder
* @param {Node|Element|HTMLElement|Array|string} data some data to set
* @return {Node} updated placeholder
*/
CModalBox.prototype.SetData = function ( place, data ) {
// clear
elclear(place);
// and append
if ( data instanceof Node || Array.isArray(data) ) {
elchild(place, data);
} else {
// simple string
place.innerHTML = data;
}
// make sure it visible
if ( data && data.nodeName ) {
data.style.display = 'block';
}
// show if there is some data
place.style.display = data ? 'block' : 'none';
return place;
};
/**
* Set window title (alias for SetData)
* @param {Node|Array|string} [data] some data to set
* @return {Node} updated placeholder
*/
CModalBox.prototype.SetHeader = function ( data ) { return this.SetData(this.header, data || ''); };
/**
* Set window body (alias for SetData)
* @param {Node|Array|string} [data] some data to set
* @return {Node} updated placeholder
*/
CModalBox.prototype.SetContent = function ( data ) { return this.SetData(this.content, data || ''); };
/**
* Set window footer (alias for SetData)
* @param {Node|Array|string} [data] some data to set
* @return {Node} updated placeholder
*/
CModalBox.prototype.SetFooter = function ( data ) { return this.SetData(this.footer, data || ''); };
/**
* Prepare html and all placeholders
*/
CModalBox.prototype.Init = function ( ) {
// parent call init with placeholder
CModal.prototype.Init.call(this,
element('div', {className:'cmodal-body'}, [
this.header,
this.content,
this.footer
]
));
};
///////////////////////////////////////////////////////////////////////////////
/**
* Show small modal info panel which automatically hides in the given time
* @param {CPage|CBase} [parent] object owner (document.body if not set)
* @param {string} data info text
* @param {Number|boolean} time milliseconds before hiding (not set - manual hide)
* @param {boolean} [isForced=false] true do not allow to close this hint till it auto hide
* @class CModalHint
* @constructor
* @example
* new CModalHint(CurrentPage, 'some test short info');
*/
function CModalHint ( parent, data, time, isForced ) {
// check input
if ( data ) {
// parent constructor
CModalBox.call(this, parent);
/**
* The component inner name
* @type {string}
*/
this.name = 'CModalHint';
// for limited scopes
var self = this;
// filling
this.SetHeader();
this.SetContent(data);
this.SetFooter();
// free resources on hide
this.onHide = function(){
self.Free();
};
// hide on mouse click
this.onClick = function() {
self.Show(false);
};
// build and display
this.Init();
this.Show(true);
// allow to close by user
if ( isForced !== true ) {
// forward events to button panel
this.EventHandler = function ( event ) {
// hide
self.Show(false);
// reset autohide if set
if ( self.timer ) {
clearTimeout(self.timer);
}
event.preventDefault();
};
}
if ( time ) {
// hide in some time
this.timer = setTimeout(function(){
self.Show(false);
}, time || 5000);
}
}
}
// extending
CModalHint.prototype = Object.create(CModalBox.prototype);
CModalHint.prototype.constructor = CModalHint;
///////////////////////////////////////////////////////////////////////////////
/**
* Show modal message box with single button Exit
* @param {CPage|CBase} [parent] object owner (document.body if not set)
* @param {string} title modal message box caption
* @param {string} data modal message box text
* @param {string} btnExitTitle exit button caption
* @param {Function} [btnExitClick] callback on exit button click
* @class CModalAlert
* @constructor
* @example
* new CModalAlert(CurrentPage, 'Some title', 'Some long or short message text', 'Close', function(){alert('exit')});
*/
function CModalAlert ( parent, title, data, btnExitTitle, btnExitClick ) {
// check input
if ( data ) {
// parent constructor
CModalBox.call(this, parent);
/**
* The component inner name
* @type {string}
*/
this.name = 'CModalAlert';
// for limited scopes
var self = this;
this.bpanel = new CButtonPanel();
this.bpanel.Init(CMODAL_IMG_PATH);
this.bpanel.btnExit = this.bpanel.Add(KEYS.EXIT, 'exit.png', btnExitTitle || '', function(){
if ( typeof btnExitClick === 'function' ) {
btnExitClick.call(self);
}
// hide and destroy
self.Show(false);
});
// filling
this.SetHeader(title);
this.SetContent(data);
this.SetFooter(this.bpanel.handle);
// free resources on hide
this.onHide = function(){
elclear(self.bpanel.handle);
delete self.bpanel;
self.Free();
};
// forward events to button panel
this.EventHandler = function ( e ) {
if ( !eventPrepare(e, true, this.name) ) {
return;
}
self.bpanel.EventHandler(e);
};
// build and display
this.Init();
this.Show(true);
}
}
// extending
CModalAlert.prototype = Object.create(CModalBox.prototype);
CModalAlert.prototype.constructor = CModalAlert;
///////////////////////////////////////////////////////////////////////////////
/**
* Show modal message box with single button Exit
* @param {CPage|CBase} parent object owner (document.body if not set)
* @param {string} title modal message box caption
* @param {string} data modal message box text
* @param {string} btnExitTitle exit button caption
* @param {Function} btnExitClick callback on exit button click
* @param {string} btnOKTitle ok button caption
* @param {Function} btnOKClick callback on ok button click
* @class CModalConfirm
* @constructor
* @example
* new CModalConfirm(CurrentPage, 'Some title', 'Some long or short message text', 'Close', function(){alert('exit')}, 'Ok', function(){alert('f2');});
*/
function CModalConfirm ( parent, title, data, btnExitTitle, btnExitClick, btnOKTitle, btnOKClick ) {
// parent constructor
CModalAlert.call(this, parent, title, data, btnExitTitle, btnExitClick);
/**
* The component inner name
* @type {string}
*/
this.name = 'CModalConfirm';
// for limited scopes
var self = this;
// additional button
this.bpanel.Add(KEYS.OK, 'ok.png', btnOKTitle, function(){
// hide and destroy
self.Show(false);
if ( typeof btnOKClick === 'function' ) {
btnOKClick.call(self);
// prevent double invoke
btnOKClick = null;
}
});
}
// extending
CModalConfirm.prototype = Object.create(CModalAlert.prototype);
CModalConfirm.prototype.constructor = CModalConfirm;

106
mag/mini/system/config.js Normal file
View File

@ -0,0 +1,106 @@
/**
* System configuration
*/
'use strict';
/* jshint unused:false */
var configuration = (function () {
var config = {
newRemoteControl: false,
allowBluetooth: false,
allowMulticastUpdate: false,
videoOutputMode: [
{value: 'Auto', title: 'Auto', translated: false},
{value: 'PAL', title: 'PAL (576i)', translated: true},
{value: '576p-50', title: '576p-50', translated: true},
{value: '720p-50', title: '720p-50', translated: true},
{value: '1080i-50', title: '1080i-50', translated: true},
{value: '1080p-50', title: '1080p-50', translated: true},
{value: 'NTSC', title: 'NTSC (480i)', translated: true},
{value: '720p-60', title: '720p-60', translated: true},
{value: '1080i-60', title: '1080i-60', translated: true},
{value: '1080p-60', title: '1080p-60', translated: true}
]
},
model = gSTB.GetDeviceModelExt(),
control = null;
switch ( model ) {
case 'AuraHD2':
case 'AuraHD3':
case 'AuraHD8':
case 'MAG275':
case 'WR320':
config.newRemoteControl = true;
break;
// remove 576p-50 video mode
// TODO: remove this code when MAG256 fix this video mode
case 'MAG256':
case 'MAG257':
config.videoOutputMode = [
{value: 'Auto', title: 'Auto', translated: false},
{value: 'PAL', title: 'PAL (576i)', translated: true},
{value: '720p-50', title: '720p-50', translated: true},
{value: '1080i-50', title: '1080i-50', translated: true},
{value: '1080p-50', title: '1080p-50', translated: true},
{value: 'NTSC', title: 'NTSC (480i)', translated: true},
{value: '720p-60', title: '720p-60', translated: true},
{value: '1080i-60', title: '1080i-60', translated: true},
{value: '1080p-60', title: '1080p-60', translated: true}
];
break;
case 'MAG351':
case 'MAG352':
case 'MAG420':
case 'MAG422':
case 'MAG424':
case 'MAG425':
case 'IM4410WV':
case 'IM4411':
case 'IM4411V':
case 'IM4411WV':
case 'IM4412':
config.videoOutputMode = [
{value: 'Auto', title: 'Auto', translated: false},
{value: 'PAL', title: 'PAL (576i)', translated: true},
{value: '576p-50', title: '576p-50', translated: true},
{value: '720p-50', title: '720p-50', translated: true},
{value: '1080i-50', title: '1080i-50', translated: true},
{value: '1080p-50', title: '1080p-50', translated: true},
{value: 'NTSC', title: 'NTSC (480i)', translated: true},
{value: '720p-60', title: '720p-60', translated: true},
{value: '1080i-60', title: '1080i-60', translated: true},
{value: '1080p-60', title: '1080p-60', translated: true},
{value: '3840x2160p50', title: '3840x2160p-50', translated: true},
{value: '3840x2160p60', title: '3840x2160p-60', translated: true}
];
break;
default:
break;
}
// enable bluetooth only for these devices
if ( ['MAG424', 'IM4410', 'IM4410WV', 'IM4412'].indexOf(model) > -1 ) {
config.allowBluetooth = true;
}
try{
control = JSON.parse(gSTB.GetEnv('{"varList":["controlModel"]}'));
if ( !control.errMsg && control.result && control.result.controlModel ) {
if ( control.result.controlModel === 'SRC4513' ) {
config.newRemoteControl = true;
} else {
config.newRemoteControl = false;
}
}
} catch (e) {
echo(e ,'controlModel parse')
}
return config;
})();

98
mag/mini/system/cpage.js Normal file
View File

@ -0,0 +1,98 @@
'use strict';
/**
* Class for main pages of the portal.
* Each page should be created from this class.
* @class CPage
* @constructor
* @author Stanislav Kalashnik <sk@infomir.eu>
*/
function CPage ( parent ) {
// parent constructor
CBase.call(this, parent || null);
/**
* The component inner name
* @type {string}
*/
this.name = 'CPage';
/**
* the object that will become visible after this one is hidden
* @type {CPage|CBase}
*/
this.previous = null;
/**
* default visibility state
* @type {boolean}
*/
this.isVisible = false;
}
// extending
CPage.prototype = Object.create(CBase.prototype);
CPage.prototype.constructor = CPage;
/**
* Manage the page visibility
* @param {boolean} [visible=true] component visibility: true - visible, false - hidden
* @param {CPage|CBase} [previous=null] the page to return to on this page hiding
*/
CPage.prototype.Show = function ( visible, previous ) {
// custom action
if ( visible === false ) { // hide
// turn off events
if ( this.activeChild ) {
this.activeChild.Activate(false);
}
// hide this
CBase.prototype.Show.call(this, false, true, true);
// go to home if not set
window.currCPage = this.previous || window.baseCPage;
this.previous = null;
// if set and not itself
if ( window.currCPage && window.currCPage !== this ) {
// show it
CBase.prototype.Show.call(window.currCPage, true, true, true);
}
} else { // show
// if set and not itself
this.previous = previous || null;
if ( window.currCPage && window.currCPage !== this ) {
if ( window.currCPage.activeChild ) {
window.currCPage.activeChild.Activate(false);
}
// hide it
CBase.prototype.Show.call(window.currCPage, false, true, true);
}
// show this
CBase.prototype.Show.call(this, true, true, true);
// set back route
window.currCPage = this;
}
};
/**
* Can handle a mouse click
*/
CPage.prototype.onClick = function () {
return false;
};
/**
* Should be overwritten in each instance
*/
CPage.prototype.EventHandler = function () {
// ...
};

View File

@ -0,0 +1,31 @@
'use strict';
/**
* Progressbar component
* @class CProgressBar
* @param {CBase} parent
* @param {HTMLElement} parentNode Элемент в который будет вставлен компонент
* @constructor
*/
var CProgressBar = function (parent, parentNode) {
CBase.call(this, parent || null);
if (parentNode === undefined) {
parentNode = element('div');
}
elchild(parentNode, this.$bar = element('div', {className: 'cprogressbar'},
element('div', {className: 'progressbar_bg'}, [
this.$line = element('div', {className: 'progressbar_line'}),
this.$digit = element('div', {className: 'progressbar_digit'}, '0 %')
])
));
this.Init(parentNode);
};
// extending
CProgressBar.prototype = Object.create(CBase.prototype);
CProgressBar.prototype.constructor = CProgressBar;
CProgressBar.prototype.SetProgress = function (percent) {
this.$line.style.width = percent + '%';
this.$digit.innerHTML = percent + '%';
};

263
mag/mini/system/csbar.js Normal file
View File

@ -0,0 +1,263 @@
'use strict';
/**
* Search bar module
* contains a text input and icon/button
* @param {Node} parent component
* @class CSearchBar
* @constructor
* @author Stanislav Kalashnik <sk@infomir.eu>
*/
function CSearchBar ( parent ) {
// parent constructor
CBase.call(this, parent);
/**
* The component inner name
* @type {string}
*/
this.name = 'CSearchBar';
/**
* CSS class name associated with the component
* it is checking on initialization in the placeholder
* if not found a wrapper is created
* @type {string}
*/
this.baseClass = 'csbar-main';
/**
* text data for filtering
* @type {string}
*/
this.filter = '';
/**
* directory with icons
* depends on screen resolution
* @type {string}
*/
this.path = '';
/**
* list of inner nodes
* @type {Object}
*/
this.items = {};
/**
* numerical ID of the timeout
* @type {number}
*/
this.timer = 0;
/**
* grey hint text line
* @type {string}
*/
this.hint = 0;
/**
* default initial image
* @type {string}
*/
this.icoSearch = 'ico_search2.png';
/**
* image with not empty search
* @type {string}
*/
this.icoFilter = 'ico_filter.png';
}
// extending
CSearchBar.prototype = Object.create(CBase.prototype);
CSearchBar.prototype.constructor = CSearchBar;
/**
* Component initialization with image path set.
* Should be called once before use just after constructor invoke and all hooks preparation.
* @param {string} path image path dependant on resolution
* @param {Node} [handle] component placeholder
* @param {string} [hint] default hint text
*/
CSearchBar.prototype.Init = function ( path, handle, hint ) {
// global vars
this.path = path;
this.hint = hint || '';
var self = this;
var func = function () {
self.Activate(!self.isActive);
event.stopPropagation();
};
// parent call init with placeholder
CBase.prototype.Init.call(this, handle);
this.items.hint = element('input', {type: 'text', className: 'hint maxw', value:this.hint});
this.items.text = element('input', {type: 'text', className: 'text maxw', oninput:function(){if ( self.onChange ) {self.onChange(this.value);} }, onkeydown:function(event){ self.EventHandler(event); }});
this.items.span = element('div', {className: 'csbar-item span'});
this.items.icon = element('div', {className: 'csbar-item icon', onclick: func}, element('img', {className: '', src: this.path + this.icoSearch}));
this.items.button = element('div', {className: 'csbar-item icon', onclick: func}, element('img', {className: '', src: this.path + 'f2.png'}));
// add to component container
this.items.input = element('div', {className: 'csbar-item stext'}, [this.items.hint, this.items.text]);
elchild(this.handleInner, [this.items.input, this.items.span, this.items.icon, this.items.button]);
};
/**
* Manage the component search input field visibility
* @param {boolean} [visible=true] component visibility: true - visible, false - hidden
* @param {boolean} [manageFocus=true] flag to manage focus handling
*/
CSearchBar.prototype.ShowInput = function ( visible, manageFocus ) {
visible = visible !== false;
if ( !visible ) {
this.items.text.blur();
gSTB.HideVirtualKeyboard();
// return focus to the previous element if necessary and set
if ( manageFocus !== false && this.prevFocus ) {
this.prevFocus.focus();
}
}
this.items.input.style.display = visible ? 'table-cell' : 'none';
this.items.span.style.display = visible ? 'none' : 'table-cell';
if ( visible ) {
// save the previous focused element
this.prevFocus = document.activeElement;
// set focus to input
this.items.text.focus();
gSTB.ShowVirtualKeyboard();
}
};
/**
* Apply default parameters
*
*/
CSearchBar.prototype.Reset = function () {
this.Activate(false);
this.items.text.value = this.items.hint.value = this.hint = this.filter = '';
this.items.icon.lastChild.src = this.path + '/' + this.icoSearch;
};
/**
* Search callback
* @param {string} value search value
*/
CSearchBar.prototype.onSearch = null;
/**
* Get hint if search value was changed
* @param {string} value search value
*/
CSearchBar.prototype.onHint = null;
/**
* Event on search text change
* @param {string} value search value
*/
CSearchBar.prototype.onChange = null;
/**
* Apply the hint value to the input element
* @param {string} hint search suggest text
*/
CSearchBar.prototype.SetHint = function ( hint ) {
hint = hint || this.items.text.value || this.hint;
// apply if there is a hint
if ( hint ) {
this.items.hint.value = hint;
}
};
/**
* Apply the given value to the input element
* @param {string} text search text
*/
CSearchBar.prototype.SetValue = function ( text ) {
this.filter = text;
// apply
this.items.text.value = this.filter;
};
/**
* Handle external events
* @param {Event} event global event object
*/
CSearchBar.prototype.EventHandler = function ( event ) {
switch ( event.code ) {
case KEYS.LEFT:
case KEYS.RIGHT:
case KEYS.UP:
case KEYS.DOWN:
return;
case KEYS.EXIT:
this.Activate(false);
event.stopPropagation();
break;
case KEYS.F2:
this.Activate(!this.isActive);
event.stopPropagation();
break;
case KEYS.OK:
this.Activate(false);
// hook
if ( typeof this.onSearch === 'function' ) {
this.onSearch(this.items.text.value);
}
// set icon
this.items.icon.firstChild.src = this.items.text.value ? this.path + '/' + this.icoFilter : this.path + '/' + this.icoSearch;
event.stopPropagation();
break;
default:
// hide or show default hint state
this.items.hint.value = this.items.text.value === '' ? this.hint : '';
// hint callback set
if ( typeof this.onHint === 'function' ) {
if ( this.items.hint.value === this.hint ) {
this.SetHint(' ');
}
if ( this.timer ) {
clearTimeout(this.timer);
}
var self = this;
this.timer = setTimeout(function(){
self.onHint.call(self, self.items.text.value);
}, 300);
}
break;
}
};
/**
* Set active state of the component
* @param {boolean} active state
* @param {boolean} [manageFocus=true] flag to manage focus handling
*/
CSearchBar.prototype.Activate = function ( active, manageFocus ) {
if ( !active ) {
this.ShowInput(false, manageFocus);
}
// parent call init with placeholder
CBase.prototype.Activate.call(this, active);
if ( active ) {
this.ShowInput(true, manageFocus);
}
};

757
mag/mini/system/cselect.js Normal file
View File

@ -0,0 +1,757 @@
'use strict';
/**
* @param parent
* @param parameters
* @constructor
* @abstract
*/
function CSimpleSelect ( parent, parameters ) {
this.parameters = parameters || {};
this.tagName = 'SELECT';
/**
* Default values for element initialization
* @type {Object}
*/
this.attributes = {
content: {
className: 'csimple-select-content'
},
left_arrow: {
className: 'csimple-select-arrow left'
},
right_arrow: {
className: 'csimple-select-arrow right'
},
wrapper: {
className: 'csimple-select-wrapper',
tabIndex: '1'
},
content_wrapper: {
className: 'csimple-content-wrapper'
},
hint: {
className: 'csimple-select-hint csimple-select-content'
}
};
this.style = parameters.style || '';
this.type = 'select';
if ( parameters.attributes !== undefined ) {
extend(this.attributes, parameters.attributes);
}
this.bind(parameters.events || {});
CBase.call(this, parent || null);
if ( !(this.parentNode = parameters.parent) ) {
if ( parameters.content === undefined ) {
this.parentNode = element('div');
} else {
this.parentNode = parameters.content.parentNode;
}
}
}
// extending
CSimpleSelect.prototype = Object.create(CBase.prototype);
CSimpleSelect.prototype.constructor = CSimpleSelect;
CSimpleSelect.prototype.GenerateHandle = function () {
var self = this,
wrapper,
content_wrapper;
if ( this.parameters.content !== undefined && this.parameters.content.tagName === 'DIV' ) {
this.content = this.parameters.content;
this.content.parentNode.removeChild(this.content);
if ( this.content.className !== undefined )
this.attributes.content.className += ' ' + this.content.className;
this.content = element('div', this.attributes.content, this.content || '');
} else {
if ( this.parameters.content !== undefined && this.parameters.content.tagName !== 'DIV' )
this.content = this.parameters.content;
this.content = element('div', this.attributes.content, this.content || '');
}
content_wrapper = element('div', this.attributes.content_wrapper, [this.content, this.hint = element('div', this.attributes.hint)]);
if ( this.parameters.leftArrow ) {
this.leftArrow = this.parameters.leftArrow;
this.leftArrow.parentNode.removeChild(this.leftArrow);
this.leftArrow = element('div', this.attributes.left_arrow, this.leftArrow);
} else {
this.leftArrow = element('div', this.attributes.left_arrow, this.leftArrow || element('p'));
}
if ( this.parameters.rightArrow ) {
this.rightArrow = this.parameters.rightArrow;
this.rightArrow.parentNode.removeChild(this.rightArrow);
this.rightArrow = element('div', this.attributes.right_arrow, this.rightArrow);
} else {
this.rightArrow = element('div', this.attributes.right_arrow, this.rightArrow || element('p'));
}
wrapper = element('div', this.attributes.wrapper, element('div', {
className: 'content-wrapper',
onfocus: function () {
}
}, [this.leftArrow, content_wrapper, this.rightArrow]));
wrapper.className += ' ' + this.style;
if ( this.parameters.width !== undefined ) {
wrapper.style.width = this.parameters.width;
}
this.leftArrow.onclick = function ( event ) {
if ( self.disabled ) return;
self.Previous();
event.stopPropagation();
event.preventDefault()
};
this.rightArrow.onclick = function ( event ) {
if ( self.disabled ) return;
self.Next();
event.stopPropagation();
event.preventDefault()
};
return wrapper;
};
CSimpleSelect.prototype.onInit = function () {
var self = this;
elchild(this.parentNode, this.handle);
this.handle.component = this;
this.handle.onblur = function () {
self.trigger('onBlur');
};
this.handle.onfocus = function () {
self.trigger('onFocus');
}
};
CSimpleSelect.prototype.EventHandler = function ( event ) {
eventPrepare(event, true, 'CSimpleSelect');
if ( this.disabled === true )
return;
event.stopped = true;
switch ( event.code ) {
case KEYS.LEFT:
this.Previous();
break;
case KEYS.RIGHT:
this.Next();
break;
default :
event.stopped = false;
}
};
CSimpleSelect.prototype.SetContent = function ( content, placeholder ) {
if ( content === undefined && placeholder ) {
content = placeholder;
this.content.classList.add('placeholder');
}
this.content.innerHTML = '';
this.content.innerHTML = content;
};
CSimpleSelect.prototype.DisableElement = function ( element, disable ) {
if ( disable === true ) {
if ( element.className.indexOf('disabled') === -1 ) element.className += ' disabled';
} else {
element.className = element.className.replace(' disabled', '');
}
};
CSimpleSelect.prototype.SetHint = function ( hint ) {
this.hint.innerHTML = hint;
};
CSimpleSelect.prototype.Next = function () {
};
CSimpleSelect.prototype.Previous = function () {
this.autocomplete = '';
};
CSimpleSelect.prototype.focus = function () {
this.handle.focus();
};
CSimpleSelect.prototype.blur = function () {
this.handle.blur();
};
CSimpleSelect.prototype.IsFocused = function () {
return document.activeElement === this.handle;
};
CSimpleSelect.prototype.Disable = function ( disable ) {
this.DisableElement(this.handle, (this.disabled = disable === true));
};
Events.inject(CSimpleSelect);
/**
*
* @param parent
* @param parameters
* @constructor
*/
function CSelectBox ( parent, parameters ) {
CSimpleSelect.call(this, parent || null, parameters);
this.attributes.wrapper.className += ' cselect-box';
this.Init(this.GenerateHandle());
var _data = parameters.data || [],
_selectedIndex,
_default,
idField = parameters.idField || 'id',
nameField = parameters.nameField || 'name',
_autocomplete = '',
self = this,
placeholder = parameters.placeholder || _('No data');
this.name = parameters.name || 'CSelectBox';
this.dropdown = new CSelectDropdown(this, {
select: this,
scrollMode: 2,
container: parameters.container
});
this.SetData = function ( data ) {
if ( data.length === 0 )
throw 'data cannot be empty';
_data = data || [];
if ( data.length - 1 < _selectedIndex )
_selectedIndex = data.length - 1;
self.RefreshContent();
self.trigger('dataChanged');
};
this.GetNameField = function () {
return nameField;
};
this.GetIdField = function () {
return idField;
};
this.GetData = function () {
return _data;
};
this.Autocomplete = function ( keyCode ) {
var find = false, text = '';
if ( keyCode === KEYS.BACK ) {
text = _autocomplete.slice(0, -1);
} else {
text = _autocomplete + String.fromCharCode(keyCode).toLowerCase();
}
if ( text !== '' ) {
for ( var i = 0, el; i < _data.length, el = _data[i]; i++ ) {
if ( typeof el === 'object' ) {
el = el[nameField];
}
if ( el.toLowerCase().indexOf(text) === 0 ) {
this.SetIndex(i);
find = true;
break;
}
}
if ( find === true ) {
self.handle.classList.add('autocomplete');
_autocomplete = text;
}
text = this.GetLabel();
if ( text ) {
text = text.substr(0, _autocomplete.length);
}
self.SetHint(text);
} else {
self.ClearAutocomplete();
}
};
this.ClearAutocomplete = function () {
self.handle.classList.remove('autocomplete');
self.SetHint(_autocomplete = '');
};
this.GetSelected = function () {
return _data[_selectedIndex];
};
this.Next = function () {
if ( _selectedIndex < _data.length - 1 ) {
this.SetIndex(++_selectedIndex);
}
};
this.Previous = function () {
if ( _selectedIndex !== 0 ) {
this.SetIndex(--_selectedIndex);
}
};
this.GetValue = function () {
var selected = self.GetSelected();
return typeof selected === 'object' ? selected[idField] : selected;
};
this.GetLabel = function () {
var selected = self.GetSelected();
return typeof selected === 'object' ? selected[nameField] : selected;
};
this.RefreshContent = function () {
self.SetContent(self.GetLabel(), placeholder);
self.value = self.GetValue(); // remove after system settings change
if ( self.GetData().length === 1 ) {
self.DisableElement(self.leftArrow, true);
self.DisableElement(self.rightArrow, true);
return;
}
if ( self.GetIndex() === 0 ) {
self.DisableElement(self.leftArrow, true);
self.DisableElement(self.rightArrow, false);
} else if ( self.GetIndex() === self.GetData().length - 1 ) {
self.DisableElement(self.rightArrow, true);
self.DisableElement(self.leftArrow, false);
} else {
self.DisableElement(self.leftArrow, false);
self.DisableElement(self.rightArrow, false);
}
};
this.SelectById = function ( id, quiet ) {
for ( var i = 0; i < _data.length; i++ ) {
if ( _data[i][idField] === id ) {
self.SetIndex(i, quiet);
return i;
}
}
};
this.IsChanged = function () {
return _selectedIndex !== _default;
};
this.GetIndex = function () {
return _selectedIndex;
};
this.SetIndex = function ( index, quiet ) {
var old = this.GetSelected();
self.ClearAutocomplete();
if ( index === undefined || index >= _data.length || index < 0 )
_selectedIndex = 0;
else
_selectedIndex = index;
self.RefreshContent();
if ( quiet !== true )
self.trigger('onChange', {'new': self.GetSelected(), old: old});
};
this.SetIndex(parameters.selected, true);
if ( parameters.selectedId !== undefined )
this.SelectById(parameters.selectedId, true);
_default = _selectedIndex;
this.EventHandler = function ( event ) {
eventPrepare(event);
event.stopped = true;
switch ( event.code ) {
case KEYS.FRAME:
this.dropdown.Show(!this.dropdown.isVisible, false);
break;
case KEYS.LEFT:
case KEYS.RIGHT:
if ( !this.dropdown.isVisible ) {
CSimpleSelect.prototype.EventHandler.call(this, event);
this.dropdown.EventHandler(event);
}
break;
case KEYS.OK:
case KEYS.UP:
case KEYS.DOWN:
case KEYS.PAGE_UP:
case KEYS.PAGE_DOWN:
event.stopped = false;
if ( this.dropdown.isVisible ) {
this.dropdown.EventHandler(event, null, false);
event.stopped = true;
}
break;
case KEYS.EXIT:
if ( this.dropdown.isVisible ) {
this.dropdown.Show(false, false);
} else {
event.stopped = false;
}
break;
default :
event.stopped = false;
}
};
this.handle.onkeypress = function ( event ) {
if ( event.ctrlKey !== true && event.altKey !== true )
self.Autocomplete(event.charCode);
};
this.dropdown.Refresh();
this.bind('onBlur', function () {
self.ClearAutocomplete();
});
}
// extending
CSelectBox.prototype = Object.create(CSimpleSelect.prototype);
CSelectBox.prototype.constructor = CSelectBox;
function CSelectDropdown ( parent, options ) {
CScrollList.call(this, parent || null, options);
var self = this;
this.isVisible = false;
this.manageFocus = false;
this.multipleSelection = false;
this.attributes = {
wrapper: {
className: 'csimple-select-dropdown'
},
element: {
className: 'dropdown-element'
}
};
this.select = null;
this.baseClass = 'csimple-dropdown-main';
CSelectDropdown.parameters.forEach(function ( key ) {
if ( options[key] !== undefined )
self[key] = options[key];
});
this.base = this.select.handle;
this.Init(element('div', this.attributes.wrapper));
this.base.parentNode.appendChild(this.handle);
this.select.bind('dataChanged', function () {
self.Refresh();
});
this.select.bind('onChange', function () {
if ( self.isVisible ) {
var item = self.handleInner.children[this.GetIndex()];
self.SetPosition(item, true, false);
self.Focused(item, true);
self.Marked(item, true);
}
});
this.select.bind('onBlur', function () {
setTimeout(function () {
self.Show(false, self.manageFocus);
}, 100);
});
this.select.handle.addEventListener('click', function ( event ) {
self.Show(!self.isVisible, self.manageFocus);
event.stopPropagation();
event.stopImmediatePropagation();
event.preventDefault();
});
this.Show(false, self.manageFocus);
}
// extending
CSelectDropdown.prototype = Object.create(CScrollList.prototype);
CSelectDropdown.prototype.constructor = CSelectDropdown;
CSelectDropdown.parameters = ['scrollMode', 'select', 'container'];
CSelectDropdown.prototype.Refresh = function () {
var self = this,
data = this.select.GetData(), el, i;
this.Clear();
for ( i = 0; i < data.length, el = data[i]; i++ ) {
if ( typeof el === 'object' ) {
el = el[this.select.GetNameField()];
}
this.Add(element('div', extend(this.attributes.element, {innerHTML: el})), {
onclick: function () {
self.select.SetIndex(self.activeItem.index);
self.Marked(self.activeItem, true);
self.Show(false, self.manageFocus);
},
index: i
});
}
};
CSelectDropdown.prototype.onShow = function () {
this.UpdateGeometry();
var item = this.handleInner.children[this.select.GetIndex()];
this.SetPosition(item, true, false);
this.Focused(item, true);
this.Marked(item, true);
};
CSelectDropdown.prototype.UpdateGeometry = function () {
var inputRect = this.base.getBoundingClientRect(),
top = (this.base.offsetTop + inputRect.height),
height = WINDOW_HEIGHT, containerRect;
if ( this.container !== undefined ) {
containerRect = this.container.getBoundingClientRect();
height = containerRect.top + containerRect.height;
}
if ( inputRect.top + inputRect.height + this.itemHeight * this.itemsPerPage > height ) {
top = this.base.offsetTop - this.itemHeight * this.itemsPerPage;
}
if ( this.scrollMode === 1 )
top -= this.itemHeight * Math.abs(Math.floor(this.itemsPerPage / 2 * -1));
this.handle.style.top = top + 'px';
this.handle.style.left = this.base.offsetLeft + 'px';
this.handle.style.width = (inputRect.width - parseInt(document.defaultView.getComputedStyle(this.handle, '')['border-left-width']) * 2) + 'px';
};
Events.inject(CSelectDropdown);
/**
*
* @param parent
* @param parameters
* @constructor
*/
function CIntervalBox ( parent, parameters ) {
var self = this,
_value = 0, _default,
_inputValue = '', _oldValue;
CSimpleSelect.call(this, parent || null, parameters);
this.inputTimer = {};
this.attributes.wrapper.className += ' cinterval-box';
if ( parameters.align === undefined )
this.attributes.content.className += ' center';
if ( parameters.contentStyle !== undefined ) {
this.attributes.content.className += ' ' + parameters.contentStyle;
}
this.Init(this.GenerateHandle());
this.name = parameters.name || 'input';
this.type = 'interval';
this.parameters = parameters;
if ( parameters.align !== undefined ) {
this.content.className += ' ' + parameters.align;
this.content.style.textAlign = parameters.align;
}
this.max = parameters.max;
this.min = parameters.min;
this.interval = parameters.interval || 1;
if ( parameters.value !== undefined ) {
if ( this.max !== undefined && parameters.value > this.max ) {
_value = this.max;
} else if ( this.min !== undefined && parameters.value < this.min ) {
_value = this.min;
} else {
_value = parameters.value;
}
} else {
if ( this.min !== undefined ) {
_value = this.min;
} else if ( this.max !== undefined ) {
_value = this.max;
}
}
_default = _value = parseInt(_value);
this.SetValue = function ( value ) {
_value = parseInt(value);
self.SetContent(_value);
self.trigger('onChange', _value);
};
this.GetValue = function () {
return _value;
};
this.Next = function () {
if ( _inputValue.length ) {
clearInterval(this.inputTimer);
this.inputDone();
}
CSimpleSelect.prototype.Next.call();
if ( self.max !== undefined ) {
if ( _value + self.interval <= self.max ) {
_value += self.interval;
} else {
return;
}
} else {
_value += self.interval;
}
self.RefreshContent();
self.trigger('onNext', self.GetValue());
self.trigger('onChange', self.GetValue());
};
this.Previous = function () {
if ( _inputValue.length ) {
clearInterval(this.inputTimer);
this.inputDone();
}
CSimpleSelect.prototype.Next.call();
if ( self.min !== undefined ) {
if ( _value - self.interval >= self.min ) {
_value -= self.interval;
} else {
return;
}
} else {
_value -= self.interval;
}
self.RefreshContent();
self.trigger('onPrevious', self.GetValue());
self.trigger('onChange', self.GetValue());
};
this.RefreshContent = function () {
this.SetContent(self.GetValue());
self.value = self.GetValue(); // remove after system settings change
self.DisableElement(self.leftArrow, false);
self.DisableElement(self.rightArrow, false);
if ( self.min !== undefined && _value - self.interval < self.min ) {
self.DisableElement(self.leftArrow, true);
}
if ( self.max !== undefined && _value + self.interval > self.max ) {
self.DisableElement(self.rightArrow, true);
}
};
this.onInput = function ( str ) {
if ( !_inputValue.length ) {
_oldValue = _value;
this.DisableElement(this.leftArrow, true);
this.DisableElement(this.rightArrow, true);
}
_inputValue += str;
this.SetValue(_inputValue);
clearInterval(this.inputTimer);
this.inputTimer = window.setTimeout(
function () {
self.inputDone();
},
2000
);
};
this.inputClear = function () {
_inputValue = '';
_oldValue = null;
this.actionBack();
};
this.inputDone = function () {
_inputValue = '';
_oldValue = null;
_value = Math.floor(_value / this.interval) * this.interval;
if ( _value > this.max )
_value = this.max;
if ( _value < this.min )
_value = this.min;
this.SetValue(_value);
this.RefreshContent();
};
this.actionBack = function () {
clearInterval(this.inputTimer);
if ( _inputValue.length > 1 ) {
_inputValue = _inputValue.slice(0, -1);
this.SetValue(_inputValue);
this.inputTimer = window.setTimeout(
function () {
self.inputDone();
},
2000
);
} else if ( _oldValue || _oldValue === 0 ) {
this.SetValue(_oldValue);
}
};
this.EventHandler = function ( event ) {
eventPrepare(event, true, 'CSimpleSelect');
if ( this.disabled === true )
return;
event.stopped = true;
switch ( event.code ) {
case KEYS.LEFT:
this.Previous();
break;
case KEYS.RIGHT:
this.Next();
break;
case KEYS.BACK:
this.actionBack();
break;
case KEYS.NUM0:
case KEYS.NUM1:
case KEYS.NUM2:
case KEYS.NUM3:
case KEYS.NUM4:
case KEYS.NUM5:
case KEYS.NUM6:
case KEYS.NUM7:
case KEYS.NUM8:
case KEYS.NUM9:
if ( !this.parameters.noInput )
this.onInput(event.code - KEYS.NUM0);
break;
default :
event.stopped = false;
}
};
this.IsChanged = function () {
return _value !== _default;
};
this.RefreshContent();
}
// extending
CIntervalBox.prototype = Object.create(CSimpleSelect.prototype);
CIntervalBox.prototype.constructor = CIntervalBox;

769
mag/mini/system/cslist.js Normal file
View File

@ -0,0 +1,769 @@
/**
* Item list navigation module
* @author Stanislav Kalashnik <sk@infomir.eu>
*/
'use strict';
/**
* @class CScrollList
* @param parent
* @constructor
*/
function CScrollList ( parent ) {
// parent constructor
CBase.call(this, parent);
/**
* The component inner name
* @type {string}
*/
this.name = 'CScrollList';
/**
* CSS class name associated with the component
* @type {string}
*/
this.baseClass = 'cslist-main';
/**
* Shows the possibility of multiple selection
* @type {boolean}
*/
this.multipleSelection = true;
/**
* the current selected item
* @type {Node}
*/
this.activeItem = null;
/**
* List of items for each state flag
* Example: {focused:[item], marked:[item,item]}
* @type {Object}
*/
this.states = {};
/**
* Default settings for focus management in list
* @type {boolean}
*/
this.manageFocus = true;
/**
* default item attribute values
* used for an item initialization
* @namespace
* @property {boolean} hidden display or not
* @property {boolean} marked initial checked state
* @property {boolean} disabled can be focused or not
* @property {boolean} focused initial focused state
*/
this.defaultParams = {
hidden : false, // display or not
marked : false, // initial checked state
disabled : false, // can be focused or not
focused : false, // initial focused state
href : '#', // empty link
self : this, // back link to the component itself
// flag to manage focus handling
manageFocus : true,
// right mouse click (suppress the context menu)
oncontextmenu: EMULATION ? null : function () { return false; },
// mouse click on the item or Ok/Enter key
onclick : function() {
// activate item
this.self.Focused(this, true);
return false;
},
onmouseover : function() {
// activate item
this.self.Focused(this, true);
return false;
}
};
/**
* default item filter values
* used for focus handling
* @type {Object}
*/
this.defaultFilter = {
hidden : false, // visible
disabled : false // enabled
};
/**
* scrolling method on up/down arrows
* 0 - no special way (default browser shift to the center on focus)
* 1 - focus is always centered (convenient but redraw the whole container on each step)
* 2 - shift by pages (quick and low resources)
* @type {number}
*/
this.scrollMode = 2;
/**
* render mode: one by one
* @type {number}
*/
this.RENDER_MODE_SINGLE = 1;
/**
* render mode: collect all added to the fragment and in the end render everything at once
* @type {number}
*/
this.RENDER_MODE_BULK = 2;
/**
* current render mode (one by one is the default)
* @type {number}
*/
this.renderMode = this.RENDER_MODE_SINGLE;
/**
* buffer for added items in the bulk mode
* @type {DocumentFragment}
*/
this.fragment = document.createDocumentFragment();
}
// extending
CScrollList.prototype = Object.create(CBase.prototype);
CScrollList.prototype.constructor = CScrollList;
/**
* Component initialization with its placeholder.
* Should be called once before use just after constructor invoke and all hooks preparation.
* @param {Node} handle component placeholder (it should be an empty div element)
*/
CScrollList.prototype.Init = function ( handle ) {
// parent call init with placeholder
CBase.prototype.Init.call(this, handle);
var self = this;
this.handleInner.onmousewheel = function( event ) {
// direction and new focused item
var direction = event.wheelDeltaY > 0;
var found = self.Next(null, direction);
// apply
if ( found ) {
self.MoveNext(direction ? -1 : 1);
self.Focused(found, true);
}
event.stopPropagation();
// prevent
return false;
};
};
/**
* Create a new item and add it to the placeholder
* visible/enabled/not focused and not checked by default
* corresponding css classes (the same names as flags):
* hidden - for invisible items
* marked - for checked items
* disabled - for items that can't be focused or selected
* focused - for a single item active at the moment
* @param {string|Node|Array} body item content
* @param {Object} [attrs] list of element attributes
* @return {Node} created item element
*/
CScrollList.prototype.Add = function ( body, attrs ) {
// check input
attrs = attrs || {};
// new item body
var item = element('a', this.defaultParams, body);
// mode-specific
if ( this.renderMode === this.RENDER_MODE_BULK ) {
// add item to buffer
this.fragment.appendChild(item);
} else {
// add item to DOM container
this.handleInner.appendChild(item);
}
// apply flags and decoration
if ( attrs.hidden ) { this.Hidden(item, true); }
if ( attrs.marked ) { this.Marked(item, true); }
if ( attrs.disabled ) { this.Disabled(item, true); }
if ( attrs.focused ) { this.Focused(item, true, attrs.manageFocus); }
// apply custom attributes with the current defaults
for ( var name in attrs ) {
if ( attrs.hasOwnProperty(name) ) { item[name] = attrs[name]; }
}
// result element
return item;
};
/**
* Add all the added items to the DOM
*/
CScrollList.prototype.Render = function () {
// add the buffer to DOM container
if ( this.fragment.childNodes.length > 0 ) {
this.handleInner.appendChild(this.fragment);
}
};
/**
* Reset and clear all items and options.
* This will make the component ready for a new filling.
*/
CScrollList.prototype.Clear = function () {
// cleaning all items
this.handleInner.innerHTML = null; // not a life-saver :/
// vars
this.activeItem = null;
this.states = {};
};
/**
* Reset only the given item to the default state
* @param {Node} item the element to be processed
*/
CScrollList.prototype.Reset = function ( item ) {
// valid html element given
if ( item && item.nodeName ) {
// apply flags and decoration
this.Hidden(item, this.defaultParams.hidden);
this.Marked(item, this.defaultParams.marked);
this.Disabled(item, this.defaultParams.disabled);
this.Focused(item, this.defaultParams.focused);
// clear focus pointer if necessary
if ( item === this.activeItem && !item.focused ) { this.activeItem = null; }
}
};
/**
* Removes the given elements and reposition the focus
* @param {[Node]} items list of elements to be processed
*/
CScrollList.prototype.DeleteAll = function ( items ) {
var self = this,
curPos = null;
// collect affected items
// there are some
if ( Array.isArray(items) && items.length > 0 ) {
// clear focus (for future refocus)
if ( document.activeElement !== null && document.activeElement.parentNode === this.handleInner ) { document.activeElement.blur(); }
// cursor position
if ( items.indexOf(this.Current()) === -1 ) {
// not intersect
curPos = this.Current();
} else {
// get the next good (scan down)
curPos = this.Next({marked:false, hidden:false, disabled:false});
// not found or the last in the list
if ( curPos === null || curPos === this.Current() ) {
// scan up
curPos = this.Next({marked:false, hidden:false, disabled:false}, true);
}
}
// apply
items.forEach(function ( item ) {
self.Delete(item);
});
// the nearest available item
if ( curPos !== null ) {
this.Focused(curPos, true);
this.SetPosition(curPos, true);
}
}
};
/**
* Remove the given item and clear inner states if necessary
* @param {Node} item the element to be processed
*/
CScrollList.prototype.Delete = function ( item ) {
// valid html element given
if ( item && item.nodeName && item.parentNode === this.handleInner ) {
// clear states
for ( var name in this.states ) {
if ( this.states.hasOwnProperty(name) ) {
// find
var index = this.states[name].indexOf(item);
// remove
if ( index !== -1 ) { this.states[name].splice(index, 1); }
}
}
// clear focus pointer if necessary
if ( item === this.activeItem ) { this.activeItem = null; }
// delete dom element
this.handleInner.removeChild(item);
}
};
/**
* Getter for currently focused element
* @return {Node} or null if there is no such item
*/
CScrollList.prototype.Current = function () {
return this.activeItem;
};
/**
* Getter for element total number (actual items + buffered and not yet rendered)
* @return {number}
*/
CScrollList.prototype.Length = function () {
return this.handleInner.children.length + this.fragment.childNodes.length;
};
/**
* Set inner item flags and decoration
* @param {Node} item the element to be processed
* @param {string} option item inner flag name
* @param {boolean} state flag of the operation (true if change is made)
* @return {boolean} operation status
*/
CScrollList.prototype.SetState = function ( item, option, state ) {
state = Boolean(state);
// current and new states are different
if ( item[option] !== state ) {
// check if exist
if ( !this.states[option] ) { this.states[option] = []; }
var index = this.states[option].indexOf(item);
// update internal list
if ( state ) {
// add to the list
if ( index === -1 ) { this.states[option].push(item); }
} else {
// remove
if ( index !== -1 ) { this.states[option].splice(index, 1); }
}
var oldVal = item[option];
// flag
item[option] = state;
// decoration
if ( state ) {
// add the corresponding class
item.classList.add(option);
} else {
// remove the corresponding class
item.classList.remove(option);
}
// call user hook
if ( typeof this.onStateChange === 'function' ) { this.onStateChange(item, option, oldVal, state); }
return true;
}
// nothing has changed
return false;
};
/**
* Handle visibility state for the given item
* also correct check/focus state if hiding
* @param {Node} item the element to be processed
* @param {boolean} state flag of the state
* @return {boolean} operation status
*/
CScrollList.prototype.Hidden = function ( item, state ) {
state = Boolean(state);
// valid html element given
if ( item && item.nodeName ) {
// flag and decoration
var changed = this.SetState(item, 'hidden', state);
// operation ok and the item is hidden
if ( changed && state ) {
// clear internal cursor if necessary
if ( item.focused ) { this.activeItem = null; }
// uncheck and remove focus
this.SetState(item, 'marked', false);
this.SetState(item, 'focused', false);
}
// operation status
return changed;
}
// failure
return false;
};
/**
* Handle checked state for the given item
* @param {Node} item the element to be processed
* @param {boolean} state flag of the state
* @return {boolean} operation status
*/
CScrollList.prototype.Marked = function ( item, state ) {
var self = this;
state = Boolean(state);
// valid html element given, enabled and visible
if ( item && item.nodeName && !item.disabled && !item.hidden ) {
if (this.multipleSelection === false) {
(this.states.marked || []).forEach(function(marked){
self.SetState(marked, 'marked', false);
});
}
// operation status
return this.SetState(item, 'marked', state);
}
// failure
return false;
};
/**
* Handle enable/disable state for the given item
* @param {Node} item the element to be processed
* @param {boolean} state flag of the state
* @return {boolean} operation status
*/
CScrollList.prototype.Disabled = function ( item, state ) {
state = Boolean(state);
// valid html element given
if ( item && item.nodeName ) {
// flag and decoration
var changed = this.SetState(item, 'disabled', state);
// operation ok and the item is disabled
if ( changed && state ) {
// clear internal cursor if necessary
if ( item.focused ) { this.activeItem = null; }
// uncheck and remove focus
this.SetState(item, 'marked', false);
this.SetState(item, 'focused', false);
}
// operation status
return changed;
}
// failure
return false;
};
/**
* Handle focus state for the given item
* also removes the focus from the previously focused item
* @param {Node} item the element to be processed
* @param {boolean} [state=true] flag of the state
* @param {boolean} [manageFocus=true] flag to manage focus handling
* @return {boolean} operation status
*/
CScrollList.prototype.Focused = function ( item, state, manageFocus ) {
var changed = false,
prevent = false;
state = state !== false;
if (manageFocus === undefined) { manageFocus = this.manageFocus; }
// valid html element given, enabled and visible
if ( item && item.nodeName && !item.disabled && !item.hidden ) {
// states differ
if ( state !== item.focused ) {
if ( state ) {
// different items (not currently active item)
if ( item !== this.activeItem ) {
// call user hook which can prevent further processing
if ( typeof this.onFocus === 'function' ) { prevent = this.onFocus(item, this.activeItem); }
// block or not
if ( !prevent ) {
// flag and decoration
changed = this.SetState(item, 'focused', state);
// clear the previously focused item
this.Focused(this.activeItem, false, manageFocus);
// global flag
this.activeItem = item;
// set actual focus if necessary
if ( manageFocus !== false ) { this.activeItem.focus(); }
}
}
} else {
// flag and decoration
changed = this.SetState(item, 'focused', state);
// focus removed if necessary
if ( manageFocus !== false ) { this.activeItem.blur(); }
this.activeItem = null;
}
}
}
// operation status
return changed;
};
/**
* Make the whole component active, set focused item and give actual focus
* give a focus to the appropriate item (last focused or the first one)
* @param {boolean} [state=true] set active or deactivate
* @param {boolean} [manageFocus=true] focus handling mode: true - set/remove focus accordingly, false - manual focus management
* @return {boolean} operation status
*/
CScrollList.prototype.Activate = function ( state, manageFocus ) {
if (manageFocus === undefined) { manageFocus = this.manageFocus; }
// parent call
CBase.prototype.Activate.call(this, state);
if ( this.isActive ) {
// get the first good one
this.activeItem = this.activeItem || this.FindOne();
// still no active item
if ( this.activeItem === null ) { return false; }
// flag and decoration
this.SetState(this.activeItem, 'focused', true);
// make it focused
this.SetPosition(this.activeItem);
if ( manageFocus !== false ) { this.activeItem.focus(); }
} else {
// remove focus if there is an element
if ( this.activeItem ) { this.activeItem.blur(); }
}
// all is ok
return true;
};
/**
* Go through all the items
* @param {Function} callback iteration callback function
*/
CScrollList.prototype.Each = function ( callback ) {
Array.prototype.forEach.call(this.handleInner.children, callback);
};
/**
* Get item list according to the given filter conditions and amount limitation
* @param {Object} [filter=this.defaultFilter] list of attributes for searching
* @param {number} [limit=0] amount of items to get (0 - all possible)
* @param {boolean} [reverse=false] to invert search direction (true - search backwards, false - from first to last)
* @return {Node[]} found items
*/
CScrollList.prototype.Find = function ( filter, limit, reverse ) {
// preparing
var match, // flag for items comparison
found = [], // result item list
items = this.handleInner.children, // all list items
itlen = items.length, // item list amount
citem = null; // current item pointer
// use default if not set
filter = filter || this.defaultFilter;
// iterate all items till all items are found
for ( var i = 0; i < itlen; i++ ) {
// floating pointer depends on direction
citem = items[reverse ? itlen-i-1 : i];
// init state
match = true;
// check all the filter attributes (all should match)
for ( var attr in filter ) {
if ( filter.hasOwnProperty(attr) ) { match = match && (citem[attr] === filter[attr]); }
}
// matched item
if ( match ) {
// add to the result list
found.push(citem);
// check limit and exit if set and enough
if ( limit && found.length >= limit ) { break; }
}
}
return found;
};
/**
* Get the first item matching the given filter conditions
* @param {Object} [filter=this.defaultFilter] list of attributes for searching
* @param {boolean} [reverse=false] to invert search direction (true - search backwards, false - from first to last)
* @return {Node|null} found item or null
*/
CScrollList.prototype.FindOne = function ( filter, reverse ) {
return this.Find(filter, 1, reverse).pop() || null;
};
/**
* Get the next/previous item from the current focused item
* according to the given filter and search direction
* searching for a closest next item by default
* can go to the next/previous page with nskip = items-per-page
* @param {Object} [filter=this.defaultFilter] list of attributes for searching
* @param {boolean} [reverse=false] to invert search direction (true - return previous, false - next)
* @param {number} [nskip=0] amount of items to skip
* @param {boolean} [toend=false] correction for tiled list
* @return {Node|null} found item or null if there are no suitable ones
*/
CScrollList.prototype.Next = function ( filter, reverse, nskip, toend ) {
// preparing
var match, // flag for items comparison
suitable = this.activeItem, // the last found matching item (starting from the current)
pointer = this.activeItem, // the floating current item for processing
skipcount = 0; // counter of found items per page
// amount of items to skip
nskip = nskip || 0;
// there is a starting item
if ( pointer ) {
// use default if not set
filter = filter || this.defaultFilter;
// iterate from the current position till the edge of the list
while ( (pointer = (reverse ? pointer.previousSibling : pointer.nextSibling)) ) {
// suitable by default
match = true;
// check all the filter attributes (all should match)
for ( var attr in filter ) {
if ( filter.hasOwnProperty(attr) ) { match = match && (pointer[attr] === filter[attr]); }
}
// count only visible items
if ( !pointer.hidden ) { skipcount++; }
// suitable item is found
if ( match ) {
// matching becomes the current
if (toend !== false) { suitable = pointer; }
// skip item correction if necessary
if ( nskip === 0 || (nskip > 0 && skipcount >= nskip) ) { return pointer; }
}
}
}
return suitable;
};
/**
* Set scroll position relatively some list element
* @param {Object} item
* @param {boolean} [makeFocused] - apply all attributes and corresponding actions
* @param {boolean} [manageFocus] - set actual focus
*/
CScrollList.prototype.SetPosition = function ( item, makeFocused, manageFocus ) {
var index, page;
if ( makeFocused ) {
if ( manageFocus === undefined ) { manageFocus = this.manageFocus; }
this.Focused(item || this.FindOne(), true, manageFocus);
}
if ( this.activeItem !== null ) {
index = this.activeItem.offsetTop / this.itemHeight;
page = Math.floor(index / this.itemsPerPage);
// different methods to scroll
if ( this.scrollMode === 1 ) {
this.handleInner.scrollTop = (index - Math.floor((this.itemsPerPage - 1) / 2)) * this.itemHeight;
} else if ( this.scrollMode === 2 ) {
this.handleInner.scrollTop = (this.itemsPerPage * this.itemHeight * page);
}
}
};
/**
* Handle external events
* @param {Event} event global event object
* @param {Object} [filter=this.defaultFilter] list of attributes for searching
* @param {boolean} [manageFocus=this.manageFocus]
*/
CScrollList.prototype.EventHandler = function ( event, filter, manageFocus ) {
var found = null;
if (manageFocus === undefined) { manageFocus = this.manageFocus; }
if (event.stopped === true) { return; }
// moving direction
switch ( event.code ) {
case KEYS.PAGE_UP:
case KEYS.PAGE_DOWN:
// jump to the next/previous item skipping page amount of items
found = this.Next(filter, event.code !== KEYS.PAGE_DOWN, this.itemsPerPage);
// correct visible view
this.handleInner.scrollTop = this.handleInner.scrollTop + (event.code === KEYS.PAGE_UP ? -1 : 1 ) * this.itemsPerPage * this.itemHeight;
break;
case KEYS.LEFT:
case KEYS.RIGHT:
case KEYS.HOME:
case KEYS.END:
// look for a single item from the beginning or end of the list
found = this.FindOne(filter, event.code === KEYS.RIGHT || event.code === KEYS.END);
break;
case KEYS.UP:
case KEYS.DOWN:
// jump to the next/previous item
found = this.Next(filter, event.code === KEYS.UP);
// there is a selection
this.MoveNext(event.code === KEYS.UP ? -1 : 1);
break;
case KEYS.OK:
// blank but necessary to prevent suppression
// !exit!
if ( this.activeItem ) { this.activeItem.onclick(); } // commented to prevent double invoke
event.preventDefault();
return;
default:
// suppress everything else and exit
//event.preventDefault();
return;
}
event.preventDefault();
// make focused the first item if not found
this.Focused(found || this.FindOne(filter), true, manageFocus);
};
/**
* Scroll by page if needed
* @param direction scroll direction
* @constructor
*/
CScrollList.prototype.MoveNext = function (direction){
if ( this.activeItem !== null ) {
// different methods to scroll
if ( this.scrollMode === 1 ) {
// focus is always centered (convenient but redraw the whole container on each step)
this.handleInner.scrollTop = this.activeItem.offsetTop - Math.floor((this.itemsPerPage-1)/2) * this.itemHeight + direction * this.itemHeight;
} else if ( this.scrollMode === 2 ) {
// shift by pages (quick and low resources)
if ( direction === -1 ) {
if ( this.activeItem.offsetTop === this.handleInner.scrollTop ) {
this.handleInner.scrollTop = this.handleInner.scrollTop - this.itemsPerPage * this.itemHeight;
}
} else {
if ( this.activeItem.offsetTop - this.handleInner.scrollTop === (this.itemsPerPage-1) * this.itemHeight ) {
this.handleInner.scrollTop = this.handleInner.scrollTop + this.itemsPerPage * this.itemHeight;
}
}
}
}
};
/**
* Hook method on focus item change
* should be declared in child to invoke
* @param {Node} item the new focused item
* @param {Node} previous the old focused item
* @return {boolean} true - prevent focus from changing, false|undefined - usual behaviour
*/
CScrollList.prototype.onFocus = null;
/**
* Hook method on item internal states change
* should be declared in child to invoke
* @param {Node} item the new focused item
* @param {string} option affected item state name
* @param {string|boolean} oldVal previous state value
* @param {string|boolean} newVal new state value
*/
CScrollList.prototype.onStateChange = null;
Object.defineProperty(CScrollList.prototype, 'itemsPerPage', {
get: function () {
if ( this._itemsPerPage ) {
return this._itemsPerPage;
} else {
return (this._itemsPerPage = Math.round(this.handleInner.offsetHeight / this.handleInner.firstChild.offsetHeight));
}
}
});
Object.defineProperty(CScrollList.prototype, 'itemHeight', {
get: function () {
if ( this.handleInner.firstChild ) {
return this.handleInner.firstChild.offsetHeight;
} else {
return 0;
}
}
});

View File

@ -0,0 +1,164 @@
/**
* Item tree list navigation module
* @class CTreeList
* @extends CScrollList
* @constructor
* @author Kopanev Igor
*/
function CTreeList ( parent, options ){
this.openedClass = "opened";
this.branchClass = "branch";
this.arrowClassName = "arrow";
this.placeholderClassName = "placeholder";
if (options !== undefined){
CTreeList.parameters.forEach(function(name){
if (options[name] !== undefined)
self[name] = options[name];
});
}
this.tree || (this.tree = []);
CScrollList.call(this, parent);
this.baseClass += " ctreelist-main";
}
// extends
CTreeList.prototype = Object.create(CScrollList.prototype);
CTreeList.prototype.constructor = CTreeList;
CTreeList.parameters = ["openedClass", "folderClass", "arrowClassName", "placeholderClassName", "contentField", "titleField", "data"];
CTreeList.prototype.SetTree = function ( tree , refresh ){
this.tree = tree;
if (refresh === true)
this.Refresh();
};
CTreeList.prototype.Refresh = function (){
var el, i;
this.Clear();
for( i = 0; i < this.tree.length; i++ ){
el = this.tree[i];
el.item = this.Add({data: el, level: 0});
if (Array.isArray(el[this.contentField])){
this.RenderLevel(el[this.contentField], el.item, 1);
}
}
};
CTreeList.prototype.RenderLevel = function ( data, parent, level ){
var el, i, attrs = {};
for ( i = 0; i < data.length; i++ ){
el = data[i];
el.parent = parent;
if (parent.hidden === true || parent.data.opened === false){
attrs.hidden = true;
}
el.item = this.Add({data: el, level: level, attrs: attrs});
if (Array.isArray(el[this.contentField])){
this.RenderLevel(el[this.contentField], el.item, level + 1);
}
}
};
CTreeList.prototype.Add = function ( options ) {
var item, items = [], i,
attrs = options.attrs || {};
if (options.level !== undefined){
for( i = 0; i < options.level; i++ ){
items.push(element('div', {className: this.placeholderClassName}));
}
}
attrs.data = options.data || {};
attrs.data.level = options.level;
items.push(options.data[this.titleField]);
item = CScrollList.prototype.Add.call(this, items, attrs);
if (Array.isArray(options.data[this.contentField])){
item.classList.add(this.branchClass);
if (options.data.opened === true){
item.classList.add(this.openedClass);
}
}
return item;
};
CTreeList.prototype.OpenBranch = function ( item, open, forced, subBranch ){
var self = this;
if (open === true){
item.classList.add(this.openedClass);
}else{
item.classList.remove(this.openedClass);
}
if (Array.isArray(item.data[this.contentField])){
item.data[this.contentField].forEach(function( element ){
if (element.item.visible !== false){
if (Array.isArray(element[self.contentField])){
if ((element.opened === true && open) || !open || forced)
self.OpenBranch(element.item, open, forced, true);
}
self.Hidden(element.item, !open);
}
});
if (forced || subBranch !== true) {
item.data.opened = open;
}
}
};
CTreeList.prototype.OpenAll = function ( open ){
for ( var i = 0, item; i < this.tree.length, item = this.tree[i]; i++ ){
if (item.type === MEDIA_TYPE_HELP_FOLDER){
this.OpenBranch(item.item, open, true);
}
}
};
CTreeList.prototype.Filter = function ( text ){
var count, word,
self = this,
words = text.split(' '),
leafs = [];
this.Each(function(item){
count = 0;
if (item.data[this.contentField] === undefined){
for (var i = 0; i < words.length, word = words[i]; i++){
if (item.data.sentence.toLowerCase().indexOf(word) !== -1){
count++;
}
}
if (count === words.length){
leafs.push(item);
}
}
item.visible = false;
self.Hidden(item, true);
});
leafs.forEach(function(leaf){
self.ShowLeaf(leaf, true);
});
this.Focused(this.FirstMatch(this.filterText), true);
};
CTreeList.prototype.ShowAll = function(){
var self = this;
this.Each(function(item){
self.Hidden(item, false);
item.visible = true;
});
this.Focused(this.FindOne(), true);
};
CTreeList.prototype.ShowLeaf = function( leaf, show ){
var parent = leaf, self = this;
if (leaf.data.type === MEDIA_TYPE_HELP_ARTICLE){
while (parent = parent.data.parent){
self.Hidden(parent, show !== true);
parent.visible = show;
}
}
leaf.visible = show;
self.Hidden(leaf, show !== true);
};

View File

@ -0,0 +1,529 @@
'use strict';
var MODAL_IMG_PATH = configuration.newRemoteControl ? PATH_IMG_SYSTEM + 'buttons/new/' : PATH_IMG_SYSTEM + 'buttons/old/',
/**
* @param parent
* @param options
* @constructor
* Phrases that should be translated:
* 'Retry', 'Cancel', 'Show log', 'Update', 'Status', 'Check image', 'Exit', 'Current version',
* 'New version', 'Description', 'Updating to version', 'Update status', 'Hide log', 'List of changes'
* '<span class='alert'>Warning!</span> Device will be rebooted after update'
*/
CUpdateModal = function ( parent, options ) {
var self = this;
CModalBox.call(this, parent || null);
this.isVisible = false;
this.name = 'CUpdateModal';
this.baseClass = 'cmodal-main cupdate-modal';
this.$status = element('td', {className: 'right'}); // status element
this.F1_func = null;
this.Info_func = null;
this.image_info = {}; // image info cache
CUpdateModal.parameters.forEach(function ( option ) {
if ( options[option] !== undefined ) {self[option] = options[option];}
});
this.bind(this.events);
this.bpanel = new CButtonPanel();
this.bpanel.Init(MODAL_IMG_PATH);
this.Exit_btn = this.bpanel.Add(KEYS.EXIT, 'exit.png', _('Cancel'), function () {
self.Update.Clear();
self.Show(false);
}, this.auto === true);
this.Info_btn = this.bpanel.Add(KEYS.INFO, 'info.png', _('Show log'), function () {
self.Info_func();
}, this.log !== true);
this.OK_btn = this.bpanel.Add(KEYS.F1, 'f1.png', _('Update'), function () {
self.F1_func();
}, true);
this.ProgressBar = new CProgressBar(this, element('div'));
this.SetHeader(this.header_text);
this.SetFooter(this.bpanel.handle);
this.EventHandler = function ( event ) {
eventPrepare(event);
if ( self.selectBox !== undefined && !self.selectBox.disabled && typeof self.selectBox.EventHandler === 'function' ) {
self.selectBox.EventHandler(event);
}
self.bpanel.EventHandler(event);
};
this.Init();
};
CUpdateModal.parameters = ['update_url', 'check', 'auto', 'images', 'log', 'select', 'header_text', 'info', 'events', 'warning']; // parameters list
// extending
CUpdateModal.prototype = Object.create(CModalBox.prototype);
CUpdateModal.prototype.constructor = CUpdateModal;
/**
* Trigger onHide event
*/
CUpdateModal.prototype.onHide = function () {
this.trigger('onHide');
};
/**
* Trigger onShow event
*/
CUpdateModal.prototype.onShow = function () {
this.trigger('onShow');
};
CUpdateModal.prototype.onInit = function () {
var self = this;
this.Update = new Update({
onReady: function () {
echo('CUpdateModal.Update: onReady listener');
self.image_info = self.Update.GetImageInfo(); // get Updating image info
self.image_description = self.image_info.description;
self.image_date = self.image_info.date;
if ( self.auto === true ) { // check autoupdate
self.UpdateStart(); // if auto start update immediately
} else {
self.ImageSelect(); // else show conformation window
}
self.trigger('onReady');
},
onCheck: function ( data ) {
echo('CUpdateModal.Update: onCheck listener');
self._addLogMessage(data.logMessage); // add log message
self.$status.innerHTML = data.statusMessage; // show current state of check
self.$status.className = 'right';
self.trigger('onCheck', data); // trigger onCheck event
self.bpanel.Rename(self.OK_btn, _('Update'));
},
onError: function ( data ) {
echo('CUpdateModal.Update: onError listener');
self._addLogMessage(data.logMessage, 'error'); // add log message
// Show buttons
self.bpanel.Hidden(self.OK_btn, false); // show OK button
self.bpanel.Hidden(self.Exit_btn, false); // show Exit button
self.bpanel.Rename(self.OK_btn, _('Retry'));
self.bpanel.Rename(self.Exit_btn, _('Exit'));
self.$status.innerHTML = data.errorMessage; // show Error message in status field
self.$status.classList.add('error');
if ( self.selectBox ) {
self.selectBox.disabled = false;
}
self.trigger('onError', data);
},
onProgress: function ( data ) {
echo('CUpdateModal.Update: onProgress listener');
self._addLogMessage(data.logMessage); // add log message
self.$status.innerHTML = data.statusMessage; // show current update state
self.ProgressBar.SetProgress(data.percent); // set progress bar percent
self.trigger('onProgress', data);
},
onStart: function () {
echo('CUpdateModal.Update: onStart listener');
if ( self.selectBox ) {
console.log(self.selectBox);
self.selectBox.disabled = true;
}
self.UpdateStart();
CModalBox.prototype.Show.call(self, true);
}
});
if ( this.log === true ) { // check if log activated
this.log = new CLog(document.body, {
autofocus: true,
time: true,
newest: true,
defaultType: 'success',
isVisible: false,
events: {
onShow: function () {
self.$status.parentNode.style.display = 'none';
},
onHide: function () {
self.$status.parentNode.style.display = '';
}
}
});
}
this.curr_info = this.Update.GetCurrentImageInfo(); // get info about current image
if ( typeof this.update_url === 'string' ) { // if update_url specified in params
this.check = true; // always need check that url
} else if ( Array.isArray(this.images) ) { // if specified images array
this.check = false; // check url not necessary
this.image_description = cutTextWithEllipsis(this.images[0].descr, 200);
this.image_date = new Date(this.images[0].date);
if ( this.select === true && this.images.length > 1 ) {
this.selectBox = new CSelectBox(this, {
parent: this.new_version = element('div'),
data: this.images,
nameField: 'title',
idField: 'url',
events: {
onChange: function () {
self.$curr_update_descr.innerHTML = cutTextWithEllipsis(this.GetSelected().descr, 200);
self.update_url = this.GetValue();
self.$curr_update_date.innerHTML = new Date(this.GetSelected().date).toDateString() + ' ' + new Date(this.GetSelected().date).toLocaleTimeString();
}
}
});
this.update_url = this.selectBox.GetValue();
} else { // if select not specified or images.length = 1
this.new_version = this.images[0].title; // select component not needed
this.new_version_date = new Date(this.images[0].date);
this.update_url = this.images[0].url;
}
}
};
/**
* If log component exist add message
* @param {string} message log message
* @param {string} type message type
* @private
*/
CUpdateModal.prototype._addLogMessage = function ( message, type ) {
if ( this.log !== undefined ) { this.log.Add(message, type); }
};
/**
* If log component exist adds it in elements array
* @param {Array} elements that will be displayed on the page
* @private
*/
CUpdateModal.prototype._addLog = function ( elements ) {
var self = this;
if ( typeof this.log !== 'undefined' ) {
elements.push(
element('tr', {className: 'row'}, [element('td', {className: 'log', colSpan: 2}, this.log.handle)])
);
this.Info_func = function () {
self.log.Show(!self.log.isVisible);
this.bpanel.Rename(this.Info_btn, self.log.isVisible ? _('Hide log') : _('Show log'));
self.content.querySelector('table').classList.toggle('hidden');
};
this.bpanel.Rename(this.Info_btn, self.log.isVisible ? _('Hide log') : _('Show log'));
this.bpanel.Hidden(this.Info_btn, false);
}
};
/**
* Manage the window visibility
* @param {boolean} show show or hide window
*/
CUpdateModal.prototype.Show = function ( show ) {
echo('CUpdateModal.prototype.Show:show? ' + show + ', auto? ' + this.auto + ', check? ' + this.check);
if ( show !== false ) {
if ( this.auto === true ) {
this.Update.Start(this.update_url);
return;
}
if ( this.check === true ) {
this.CheckStatus();
} else {
this.ImageSelect();
}
if ( this.log && this.log.isVisible ) {
this.log.Show(false);
}
}
CModalBox.prototype.Show.call(this, show);
};
/**
* Show check status content in the update window
*/
CUpdateModal.prototype.CheckStatus = function () {
echo('CUpdateModal.CheckStatus');
this.layer = this.CheckStatus;
var self = this,
elements = [
element('tr', {className: 'row'},
[
element('td', {className: 'left'}, _('Status') + ':'),
this.$status = element('td', {className: 'right'})
])
];
this._addLog(elements);
this.SetHeader(_('Check image'));
this.SetContent(element('table', {className: 'cmodal-update status'}, elements));
this.F1_func = function () {
self.$status.innerHtml = status;
self.$status.className = 'right';
self.bpanel.Hidden(this.OK_btn, true);
self.bpanel.Rename(this.Exit_btn, _('Cancel'));
self.Update.CheckUpdate(self.update_url);
self.trigger('onCheckStatus');
};
self.Update.CheckUpdate(self.update_url);
};
/**
* Show image select content in the update window
*/
CUpdateModal.prototype.ImageSelect = function () {
this.layer = this.ImageSelect;
var self = this, $new_version,
elements = [
element('tr', {className: 'row info'},
[
element('td', {className: 'left'}, _('Current version') + ':'),
element('td', {className: 'right', innerHTML: this.curr_info.version})
]),
element('tr', {className: 'row info'},
[
element('td', {className: 'left'}, _('Description') + ':'),
element('td', {className: 'right'}, element('div', {className:'ellipsis', innerHTML: this.curr_info.description || ''}))
]),
element('tr', {className: 'row info'},
[
element('td', {className: 'left'}, _('Date') + ':'),
element('td', {
className: 'right',
innerHTML: isNaN(this.curr_info.date) ? 'n/a' : this.curr_info.date.toDateString() + ' ' + this.curr_info.date.toLocaleTimeString()
})
]),
element('tr', {className: 'row padding info'},
[
element('td', {className: 'left'}, _('New version') + ':'),
element('td', {className: 'right'}, $new_version = element('div'))
]),
element('tr', {className: 'row info'},
[
element('td', {className: 'left'}, _('Description') + ':'),
element('td', {className: 'right'}, self.$curr_update_descr = element('div', {
className: 'ellipsis',
innerHTML: this.image_description || ''
}))
]),
element('tr', {className: 'row info'},
[
element('td', {className: 'left'}, _('Date') + ':'),
self.$curr_update_date = element('td', {
className: 'right',
innerHTML: this.image_date.toDateString() + ' ' + this.image_date.toLocaleTimeString()
})
])
];
if ( this.warning !== false ) {
elements.unshift(element('tr', {className: 'row info'},
[
element('td', {
className: 'center',
colSpan: 2,
innerHTML: _('<span class=\"alert\">Warning!</span> Device will be rebooted after update')
})
])
);
}
this.bpanel.Hidden(this.Info_btn, this.info !== true); // hide info button if necessary
this._addLog(elements);
this.SetContent(element('table', {className: 'cmodal-update'}, elements));
if ( this.new_version !== undefined ) {
if ( typeof this.new_version === 'string' ) {
$new_version.innerHTML = this.new_version;
self.$curr_update_date.innerHTML = this.new_version_date.toDateString() + ' ' + this.new_version_date.toLocaleTimeString();
} else {
elchild($new_version, this.new_version);
}
} else {
$new_version.innerHTML = this.image_info.version;
self.$curr_update_date.innerHTML = this.image_info.date.toDateString() + ' ' + this.image_info.date.toLocaleTimeString();
}
// Set header
this.SetHeader(this.header_text || '');
if ( this.info === true ) {
this.Info_func = function () {
self.ShowInfoWindow();
};
this.bpanel.Rename(this.Info_btn, _('Change log'));
}
this.F1_func = function () {
self.Update.Start(this.update_url);
};
this.bpanel.Hidden(this.OK_btn, false);
this.bpanel.Hidden(this.Exit_btn, false);
if ( this._binded !== true ) {
this.bind('onShow', function () {
if ( this.layer === this.ImageSelect && this.selectBox && this.selectBox.focus ) {
this.selectBox.focus();
}
});
this._binded = true;
}
};
/**
* Show elements after update start
*/
CUpdateModal.prototype.UpdateStart = function () {
this.layer = this.UpdateStart;
var self = this,
elements = [
element('tr', {className: 'row info'},
[
element('td', {
className: 'center',
colSpan: 2,
innerHTML: _('<span class=\"alert\">Warning!</span> Device will be rebooted after update')
})
]),
element('tr', {className: 'row info'},
[
element('td', {className: 'left'}, _('Current version') + ':'),
element('td', {className: 'right', innerHTML: this.curr_info.version})
]),
element('tr', {className: 'row info'},
[
element('td', {className: 'left'}, _('Description') + ':'),
element('td', {className: 'right'}, element('div', {
className: 'ellipsis',
innerHTML: this.curr_info.description || ''
}))
]),
element('tr', {className: 'row info'},
[
element('td', {className: 'left'}, _('Date') + ':'),
element('td', {
className: 'right',
innerHTML: isNaN(this.curr_info.date) ? 'n/a' : this.curr_info.date.toDateString() + ' ' + this.curr_info.date.toLocaleTimeString()
})
]),
element('tr', {className: 'row info padding'},
[
element('td', {className: 'left'}, _('New version') + ':'),
element('td', {className: 'right', innerHTML: this.image_info.version})
]),
element('tr', {className: 'row info'},
[
element('td', {className: 'left'}, _('Description') + ':'),
element('td', {className: 'right'}, self.$curr_update_descr = element('div', {
className: 'ellipsis',
innerHTML: this.image_description || ''
}))
]),
element('tr', {className: 'row info'},
[
element('td', {className: 'left'}, _('Date') + ':'),
element('td', {
className: 'right',
innerHTML: this.image_info.date.toDateString() + ' ' + this.image_info.date.toLocaleTimeString()
})
]),
element('tr', {className: 'row padding'},
[
element('td', {className: 'left'}, _('Update status') + ':'),
this.$status
])
];
this.bpanel.Hidden(this.Info_btn, true); // hide info button
this.bpanel.Hidden(self.Exit_btn, true); // hide Exit button
this._addLog(elements);
elements.push(
element('tr', {className: 'row'},
[
element('td', {className: 'center', colSpan: 2}, this.ProgressBar.handle)
])
);
this.SetContent(element('table', {className: 'cmodal-update'}, elements));
this.bpanel.Hidden(this.OK_btn, true);
this.bpanel.Hidden(this.Exit_btn, true);
if ( self.log.isVisible ) {
self.content.querySelector('table').classList.add('hidden');
} else {
self.content.querySelector('table').classList.remove('hidden');
}
this.trigger('onShowUpdateStart');
this.F1_func = function () {
self.Update.Start(self.update_url, self.$status.classList.contains('error')); // if error then force restart
self.$status.innerHtml = '';
self.$status.classList.remove('error');
self.bpanel.Hidden(this.OK_btn, true);
self.bpanel.Hidden(this.Exit_btn, true);
};
};
CUpdateModal.prototype.ShowInfoWindow = function () {
var modal = new CModalBox(this), $content,
self = this;
modal.SetHeader(_('Change log'));
modal.baseClass = 'cmodal-main image-info-modal';
modal.name = 'CModalImageInfo';
modal.bpanel = new CButtonPanel();
modal.bpanel.Init(MODAL_IMG_PATH);
modal.bpanel.Add(KEYS.EXIT, 'exit.png', _('Close'), function () {
modal.Show(false);
modal.Free();
});
modal.EventHandler = function ( event ) {
switch ( event.code ) {
case KEYS.UP:
$content.parentNode.scrollByLines(-2);
break;
case KEYS.DOWN:
$content.parentNode.scrollByLines(2);
break;
case KEYS.PAGE_DOWN:
$content.parentNode.scrollByPages(1);
break;
case KEYS.PAGE_UP:
$content.parentNode.scrollByPages(-1);
break;
}
modal.bpanel.EventHandler(event);
};
modal.SetFooter(modal.bpanel.handle);
modal.SetContent(element('div', {className: 'image-info-content'}, $content = element('div', {className: 'info'}, _('Information not found'))));
modal.Init();
if ( this.image_info[this.update_url] === undefined && this.update_url ) {
var name = this.update_url.split('/').pop();
ajax('GET', this.update_url.replace(name, '') + 'info/pub/get.php?name=' + name + '&lang=' + getCurrentLanguage(), function ( text, status ) {
if ( status === 200 ) {
self.image_info[self.update_url] = text;
} else {
self.image_info[self.update_url] = _('Information not found');
}
$content.innerHTML = self.image_info[self.update_url];
modal.Show(true);
$content.focus();
});
return;
}
$content.innerHTML = self.image_info[self.update_url];
modal.Show(true);
$content.focus();
};
Events.inject(CUpdateModal);

View File

@ -0,0 +1,312 @@
'use strict';
/**
* @param parent
* @param parameters
* @constructor
* @sample
* window.webfilter = new CWebInput(document.body, {
* input: document.getElementById("web-filter"),
* hint: "Enter url or text to search",
* events: {
* onEnter: function(value, type){
* console.log("Value: " + value);
* console.log("Type: " + type);
* }
* },
* autocomplete: true
* });
*/
function CWebInput ( parent, parameters ) {
var self = this, ac_attrs;
parameters = extend({
style: "web-input main-filter-input",
icons: [
{
name : "ico_search",
type : "left",
style: "black_search left"
},
{
name : "ico_web",
type : "left",
style: "web-ico left"
},
{
name : "ico_load",
type : "left",
style: "load-ico left",
src: "ico_waiting2.png"
},
{
name : "site_favicon",
type : "left",
style: "favicon left",
src: '',
attributes: {
onload: function() {
this.loaded = true;
},
onerror: function() {
this.loaded = false;
}
}
},
{
name : "ico_star_fade",
click: function ( event ) {
this.FillStar(true);
this.trigger("onStar");
event.stopPropagation();
},
style: "fade-star"
},
{
name : "ico_star_full",
click: function ( event ) {
this.FillStar(false);
this.trigger("onUnstar");
event.stopPropagation();
},
style: "full-star"
},
{
name : "f4",
click: function ( event ) {
this.trigger("onKey");
event.stopPropagation();
}
}
]
}, parameters);
if (parameters.parent !== undefined){
elchild(parameters.parent, parameters.parent = element('div', {className: "cweb-input-wrapper"}));
}else if (parameters.input !== undefined){
elchild(parameters.input.parentNode, parameters.parent = element('div', {className: "cweb-input-wrapper"}, parameters.input));
}
CInput.call(this, parent || null, parameters);
this.name = "CWebInput";
if ( parameters.stared ) {
this.FillStar(true);
}
if ( typeof parameters.autocomplete === "object" || parameters.autocomplete === true ) {
if ( parameters.autocomplete === true )
parameters.autocomplete = {};
ac_attrs = extend(CWebInput.autocomplete_defaults, parameters.autocomplete, true);
// if there is no function for getting data in parameters then assign default value
if ( ac_attrs.data === undefined )
ac_attrs.data = function ( res ) {
var data = null,
result = [];
if ( res ) {
try {
// format data for better look
data = JSON.parse(unescape(res))[1];
data.sort(function( a ){
return a[0].indexOf(self.GetValue()) === 0 ? -1 : 0;
});
if ( data ) {
data.forEach(function ( el ) {
result.push({
title: el[0],
hint: _("Search in Google") // add hints for google data
});
});
}
if ( validateUrl(self.GetValue()) ) // if valid link entered add appropriate item to the beginning of the list
result.unshift({title: self.GetValue(), type: 'url', hint: _("Open link")});
else if (result[0] === undefined || result[0].title.indexOf(self.GetValue()) === -1)
result.unshift({title: self.GetValue(), type: 'search', hint: _("Search in Google")});
} catch ( error ) {
echo(error);
}
}
return result;
};
ac_attrs.base = this.handle;
ac_attrs.input = this.input;
if (ac_attrs.events === undefined) ac_attrs.events = {};
parameters.autocomplete = new CAutocomplete(document.body, ac_attrs);
}
this.SetAutocomplete(parameters.autocomplete);
this.SetState('search');
elchild(parameters.parent, this.$progress_bar = element('div', {className: "progress-bar"}));
this.input.oninput = (function(oninput){
return function(event){
if (self.GetValue() === '')
self.SetState('search');
if (typeof oninput === 'function') {
oninput(event);
}
}
})(this.input.oninput);
}
// extending
CWebInput.prototype = Object.create(CInput.prototype);
CWebInput.prototype.constructor = CWebInput;
CWebInput.prototype.SetFavicon = function ( url ){
url = parseUri(url);
this.icons['site_favicon'].src = url.protocol + "://" + url.authority + "/favicon.ico";
};
CWebInput.prototype.SetAutocomplete = function ( autocomplete ){
CInput.prototype.SetAutocomplete.call(this, autocomplete);
var title, length, def, self = this;
if (typeof autocomplete === 'function' || typeof autocomplete === "object"){
this.autocomplete.bind({
onChange: function(){
title = this.GetValue();
def = this.GetDefault();
self.SetValue(title, true);
if (title.indexOf(def) === 0){
length = def.length;
self.SetValue(title, true);
self.input.selectionStart = length;
self.input.selectionEnd = title.length;
}
self.ShowHint(false);
},
onEnter: function ( data ){
self.trigger("onEnter", data);
}
});
}
};
/**
* Меняет тип инпута ( изменяет левую иконку )
* @param {string} type Тип инпута
*/
CWebInput.prototype.SetState = function ( type ){
var self = this;
if (CWebInput.states.indexOf(type) === -1) return;
CWebInput.states.forEach(function(type){
self.handle.classList.remove(type);
});
this.type = type ||CWebInput.states[0];
this.handle.classList.add(type);
};
// Все возможные типы которые можно назначить инпуту
CWebInput.states = ['search', 'web', 'load', 'favicon'];
// Параметры поумолчания для автодополнения
CWebInput.autocomplete_defaults = {
titleField: "title",
hintField: "hint",
typeField: "type",
size: 5,
/**
* @tutorial
*
* If URI will changed and autocomplete stops working, try to use this algorithm to fix:
* - open www.google.com
* - open preferred network sniffing tool (Wireshark, "Network" tab in Firefox/Chrome Developer tools, ...)
* - type something in search field and look at requested URI
*/
url: "https://www.google.com/complete/search?client=psy-ab&hl=uk&gs_rn=64&gs_ri=psy-ab&cp=1&gs_id=1tc&q="
};
CWebInput.prototype.FillStar = function ( fill ){
this.favorite = fill;
if (fill === true)
this.handle.classList.add("favorite");
else
this.handle.classList.remove("favorite");
};
CWebInput.prototype.ShowStar = function ( show ){
if (show === true)
this.handle.classList.add("star");
else
this.handle.classList.remove("star");
};
CWebInput.prototype.ShowFavIcon = function () {
if (this.icons["site_favicon"].loaded === true){
this.SetState("favicon");
}else{
this.SetState("web");
}
};
CWebInput.prototype.SetProgress = function ( progress ) {
if (progress !== undefined){
this.$progress_bar.style.width = progress + "%";
}else{
this.$progress_bar.style.width = "0px";
}
};
// true - stop next events
/**
* @return {boolean}
*/
CWebInput.prototype.EventHandler = function ( event ) {
eventPrepare(event, true, 'CWebInput');
var self = this,
event_res = false;
if ( this.trigger("onEvent", event)[0] === true ) return true;
switch ( event.code ) {
case KEYS.F4:
if ( this.IsFocused() ){
this.blur();
}else{
this.focus();
}
event_res = this.trigger("onKey")[0];
return true;
case KEYS.EXIT:
if (this.autocomplete && this.autocomplete.isVisible === true){
this.autocomplete.Show(false);
return true;
}
event_res = this.trigger("onExit")[0];
return true;
case KEYS.OK:
if ( this.IsFocused() ){
gSTB.HideVirtualKeyboard();
if (this.autocomplete.isVisible === true){
if (typeof this.autocomplete.EventHandler === 'function') {
this.autocomplete.EventHandler(event);
}
}else{
this.autocomplete.Abort();
echo("CWebInput:onEnter");
this.trigger("onEnter", {text: self.GetValue(), type: validateUrl(self.GetValue()) ? 'url' : 'search'});
this.autocomplete.Clear();
}
event.preventDefault();
return true;
}
case KEYS.RIGHT:
if ( this.input.selectionStart !== this.input.selectionEnd) {
this.input.oninput();
}
case KEYS.UP:
case KEYS.DOWN:
if (this.autocomplete && typeof this.autocomplete.EventHandler === 'function')
this.autocomplete.EventHandler(event);
return true;
}
if ( event_res === true ) return true;
};

256
mag/mini/system/debug.js Normal file
View File

@ -0,0 +1,256 @@
/**
* Debug instruments
* @author igork
*/
'use strict';
/**
* Dumps the given data (json format), its type and optional title to console
* @param data mixed value to be printed
* @param {string} [title] optional string for additional info
*/
var echo = function ( data, title ) {
if ( EMULATION ) { console.log(title ? title : 'log:', data); }
// console colors
var red = '\u001b[31m',
bold = '\u001b[1m',
cyan = '\u001b[36m',
green = '\u001b[32m',
reset = '\u001b[0m';
// info of the var type
var type = Object.prototype.toString.call(data).match(/\s([a-z|A-Z]+)/)[1];
// add custom colors (red for errors)
if ( type === 'Error' ) {
type = red + type + reset;
} else {
type = green + type + reset;
}
// prepare
if ( data instanceof Object || Array.isArray(data) ) {
// complex object
data = data.nodeName ? data.outerHTML : JSON.stringify(data, null, 4);
}
title = title || '';
// combine all together and print result
gSTB.Debug('[' + type + ']\t' + (title ? bold + title + green + ':\t' + reset : '') + data);
// ok
return data;
};
/**
* Reloads all css files found on the current page
* uses timestamp to make links unique
*/
function reloadStyles () {
var time = +new Date();
// get through all css links
Array.prototype.slice.call(document.head.getElementsByTagName('link')).forEach(function(tag){
// get base name, modify and apply
tag.href = tag.href.split('?_')[0] + '?_=' + time;
});
}
/**
* Reload all scripts on page
* @param {Array} [filter] Array of strings with parts of the urls scripts which should be exclude from reload
*/
function reloadAllScripts ( filter ) {
var scripts = document.head.getElementsByTagName('script'),
filtered = false,
script = null,
src = '';
for ( var i = 0; i < scripts.length; i++ ) {
script = scripts[i];
filtered = false;
src = undefined;
if ( script.type === 'text/javascript' && script.attributes.src !== undefined ) {
if ( !filtered ) {
src = script.src;
script.parentNode.removeChild(script);
if ( src.indexOf('?_') !== -1 ) {
src = src.substr(0, src.indexOf('?_'));
}
loadScript(src);
}
}
}
}
/**
* Reload scripts
* @param {string} src Part of the script url
*/
function reloadScripts ( src ) {
var scripts = document.head.getElementsByTagName('script'),
curr_time = (new Date()).getTime();
for ( var i = 0, script = null, src = ''; i < scripts.length; i++ ) {
script = scripts[i];
if ( script.type === 'text/javascript' && script.src.indexOf(src) !== -1 ) {
src = script.src + curr_time;
script.parentNode.removeChild(script);
loadScript(src);
}
}
}
/**
* Reload page with cache ignore
*/
function reloadPage () {
gSTB.Stop();
window.location.reload(true);
}
/**
* Impose image to the screen
* @param {string} image full url of image
* @param {number} [opacity=0.5] opacity level of impose image
*/
function imposeImageToScreen(image, opacity){
// prepare image
imposeImageToScreen.image = element('div');
imposeImageToScreen.image.style.width = WINDOW_WIDTH + 'px';
imposeImageToScreen.image.style.height = WINDOW_HEIGHT + 'px';
imposeImageToScreen.image.style.position = 'absolute';
imposeImageToScreen.image.style.background = 'url("' + image + '") no-repeat';
imposeImageToScreen.image.style.opacity = opacity || 1;
imposeImageToScreen.image.style.zIndex = 1000;
// add to DOM
document.body.appendChild(imposeImageToScreen.image);
}
/**
* Monitors all focus changes and dumps focused elements
*/
function focusTracker () {
// state inversion
focusTracker.state = !focusTracker.state;
if ( focusTracker.state ) {
// start
console.log('focus tracking: started');
// backup
focusTracker.focus = Element.prototype.focus;
focusTracker.blur = Element.prototype.blur;
focusTracker.select = HTMLInputElement.prototype.select;
// rewrite
Element.prototype.focus = function(){
console.log('focus', this);
// invoke the old native one
focusTracker.focus.call(this);
};
Element.prototype.blur = function(){
console.log('blur', this);
// invoke the old native one
focusTracker.blur.call(this);
};
HTMLInputElement.prototype.select = function(){
console.log('select', this);
// invoke the old native one
focusTracker.select.call(this);
};
} else {
// stop
console.log('focus tracking: stopped');
// restore
Element.prototype.focus = focusTracker.focus;
Element.prototype.blur = focusTracker.blur;
HTMLInputElement.prototype.select = focusTracker.select;
}
}
/**
* Set shortcuts for some debug tools
* Numpad 1 - reload page ignore caching
* Numpad 2 - reload styles
* Numpad 3 - reload all scripts on page
*/
(function(){
window.addEventListener('keydown', function developEventListenerKeydown ( event ) {
switch ( event.code ) {
case 97: // numpad 1
echo('reload page');
reloadPage();
break;
case 98: // numpad 2
echo('reload CSS');
reloadStyles();
break;
case 99: // numpad 3
reloadAllScripts(['target-script-min.js']);
break;
case 100: // numpad 4
echo('toggle grid');
// toggle visibility
if ( imposeImageToScreen.image ) {
// clear
document.body.removeChild(imposeImageToScreen.image);
delete imposeImageToScreen.image;
} else {
// add
imposeImageToScreen(PATH_SYSTEM + 'grid.' + WINDOW_HEIGHT + '.png');
}
break;
case 103: // numpad 7
// SpyJS enable/disable
if ( stbStorage.getItem('spyjs.active') !== '1' ) {
// suppose proxy is ready
//isSpyJs = true;
stbStorage.setItem('spyjs.active', 1);
console.log('SpyJS: enable');
console.log('SpyJS: set proxy to ' + location.hostname + ':' + 3546);
gSTB.SetWebProxy(location.hostname, 3546, '', '', '');
location.reload();
} else {
//isSpyJs = false;
stbStorage.setItem('spyjs.active', 0);
gSTB.ResetWebProxy();
console.log('SpyJS: disable');
location.reload();
}
break;
case 104: // numpad 8
// toggle tracking
focusTracker();
break;
case 220: // Print Screen
echo('\n\n\n<html><head>\n' + document.head.innerHTML + '\n</head>\n<body>\n' + document.body.innerHTML + '\n</body>\n</html>\n');
break;
}
});
})();
if ( !Function.prototype.bind ) {
Function.prototype.bind = function ( oThis ) {
if ( typeof this !== 'function' ) {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}

237
mag/mini/system/emiter.js Normal file
View File

@ -0,0 +1,237 @@
/**
* @module stb-emitter
*
* @author Stanislav Kalashnik <sk@infomir.eu>
* @license GNU GENERAL PUBLIC LICENSE Version 3
*/
'use strict';
/**
* Base Events Emitter implementation.
*
* @see http://nodejs.org/api/events.html
* @constructor
*
* @example
* var emitter = new Emitter();
*/
function Emitter () {
//if ( DEBUG ) {
// if ( typeof this !== 'object' ) { throw new Error(__filename + ': must be constructed via new'); }
//}
/**
* Inner hash table for event names and linked callbacks.
* Manual editing should be avoided.
*
* @member {Object.<string, function[]>}
*
* @example
* {
* click: [
* function click1 () { ... },
* function click2 () { ... }
* ],
* keydown: [
* function () { ... }
* ]
* }
**/
this.events = {};
}
Emitter.prototype = {
/**
* Bind an event to the given callback function.
* The same callback function can be added multiple times for the same event name.
*
* @param {string} name event identifier
* @param {function} callback function to call on this event
*
* @example
* emitter.addListener('click', function ( data ) { ... });
* // one more click handler
* emitter.addListener('click', function ( data ) { ... });
*/
addListener: function ( name, callback ) {
//if ( DEBUG ) {
// if ( arguments.length !== 2 ) { throw new Error(__filename + ': wrong arguments number'); }
// if ( typeof name !== 'string' || name.length === 0 ) { throw new Error(__filename + ': wrong or empty name'); }
// if ( typeof callback !== 'function' ) { throw new Error(__filename + ': wrong callback type'); }
//}
// initialization may be required
this.events[name] = this.events[name] || [];
// append this new event to the list
this.events[name].push(callback);
},
/**
* Add a one time listener for the event.
* This listener is invoked only the next time the event is fired, after which it is removed.
*
* @param {string} name event identifier
* @param {function} callback function to call on this event
*
* @example
* emitter.once('click', function ( data ) { ... });
*/
once: function ( name, callback ) {
// current execution context
var self = this;
//if ( DEBUG ) {
// if ( arguments.length !== 2 ) { throw new Error(__filename + ': wrong arguments number'); }
// if ( typeof name !== 'string' || name.length === 0 ) { throw new Error(__filename + ': wrong or empty name'); }
// if ( typeof callback !== 'function' ) { throw new Error(__filename + ': wrong callback type'); }
//}
// initialization may be required
this.events[name] = this.events[name] || [];
// append this new event to the list
this.events[name].push(function onceWrapper () {
callback.apply(this, arguments);
self.removeListener(name, onceWrapper);
});
},
/**
* Apply multiple listeners at once.
*
* @param {Object} callbacks event names with callbacks
*
* @example
* emitter.addListeners({
* click: function ( data ) {},
* close: function ( data ) {}
* });
*/
addListeners: function ( callbacks ) {
var name;
//if ( DEBUG ) {
// if ( arguments.length !== 1 ) { throw new Error(__filename + ': wrong arguments number'); }
// if ( typeof callbacks !== 'object' ) { throw new Error(__filename + ': wrong callbacks type'); }
// if ( Object.keys(callbacks).length === 0 ) { throw new Error(__filename + ': no callbacks given'); }
//}
for ( name in callbacks ) {
if ( callbacks.hasOwnProperty(name) ) {
this.addListener(name, callbacks[name]);
}
}
},
/**
* Remove all instances of the given callback.
*
* @param {string} name event identifier
* @param {function} callback function to remove
*
* @example
* emitter.removeListener('click', func1);
*/
removeListener: function ( name, callback ) {
//if ( DEBUG ) {
// if ( arguments.length !== 2 ) { throw new Error(__filename + ': wrong arguments number'); }
// if ( typeof name !== 'string' || name.length === 0 ) { throw new Error(__filename + ': wrong or empty name'); }
// if ( typeof callback !== 'function' ) { throw new Error(__filename + ': wrong callback type'); }
// if ( this.events[name] && !Array.isArray(this.events[name]) ) { throw new Error(__filename + ': corrupted inner data'); }
//}
// the event exists and should have some callbacks
if ( this.events[name] ) {
// rework the callback list to exclude the given one
this.events[name] = this.events[name].filter(function callbacksFilter ( fn ) { return fn !== callback; });
// event has no more callbacks so clean it
if ( this.events[name].length === 0 ) {
// as if there were no listeners at all
this.events[name] = undefined;
}
}
},
/**
* Remove all callbacks for the given event name.
* Without event name clears all events.
*
* @param {string} [name] event identifier
*
* @example
* emitter.removeAllListeners('click');
* emitter.removeAllListeners();
*/
removeAllListeners: function ( name ) {
//if ( DEBUG ) {
// if ( arguments.length !== 0 && (typeof name !== 'string' || name.length === 0) ) { throw new Error(__filename + ': wrong or empty name'); }
//}
// check input
if ( arguments.length === 0 ) {
// no arguments so remove everything
this.events = {};
} else if ( name ) {
//if ( DEBUG ) {
// if ( this.events[name] ) { throw new Error(__filename + ': event is not removed'); }
//}
// only name is given so remove all callbacks for the given event
// but object structure modification should be avoided
this.events[name] = undefined;
}
},
/**
* Execute each of the listeners in the given order with the supplied arguments.
*
* @param {string} name event identifier
*
* @example
* emitter.emit('init');
* emitter.emit('click', {src: panel1, dst: panel2});
* emitter.emit('load', error, data);
*
* // it's a good idea to emit event only when there are some listeners
* if ( this.events['click'] ) {
* this.emit('click', {event: event});
* }
*/
emit: function ( name ) {
var event = this.events[name],
i;
//if ( DEBUG ) {
// if ( arguments.length < 1 ) { throw new Error(__filename + ': wrong arguments number'); }
// if ( typeof name !== 'string' || name.length === 0 ) { throw new Error(__filename + ': wrong or empty name'); }
//}
// the event exists and should have some callbacks
if ( event ) {
//if ( DEBUG ) {
// if ( !Array.isArray(event) ) { throw new Error(__filename + ': wrong event type'); }
//}
for ( i = 0; i < event.length; i++ ) {
//if ( DEBUG ) {
// if ( typeof event[i] !== 'function' ) { throw new Error(__filename + ': wrong event callback type'); }
//}
// invoke the callback with parameters
event[i].apply(this, Array.prototype.slice.call(arguments, 1));
}
}
}
};
// correct constructor name
Emitter.prototype.constructor = Emitter;

View File

@ -0,0 +1,68 @@
'use strict';
/**
* PO files manipulation (gettext format)
* @namespace
* @author Stanislav Kalashnik <sk@infomir.eu>
*/
var gettext = (function(){
// declarations
var module = {};
var data = null; // all localized strings
/**
* Prepares and loads the given localization file
* @param {Object} options
* @param {Function} onReady callback on the given language load or on failure
*/
module.init = function ( options, onReady ) {
// defaults
options.name = options.name || 'en';
options.ext = options.ext || 'js';
options.path = options.path || 'lang';
// reset for english
data = null;
// load localization only for non-english language
if ( options.name !== 'en' ) {
loadScript(options.path + '/' + options.name + '.' + options.ext, onReady, onReady);
} else {
// and run callback
if ( typeof onReady === 'function' ) {
onReady();
}
}
};
/**
* Wrapper to receive the incoming data
* @param {Object} input
*/
module.load = function ( input ) {
data = input;
};
/**
* Localized string getter
* @param {string} key string to localize (in english)
* @return {string}
*/
module.data = function ( key ) {
if (data === null) {
return key;
} else {
return data[key] !== undefined ? data[key] : key;
}
};
// export
return module;
})();
// short alias
var _ = gettext.data;

2920
mag/mini/system/gstb.js Normal file

File diff suppressed because it is too large Load Diff

103
mag/mini/system/init.js Normal file
View File

@ -0,0 +1,103 @@
/**
* Global init procedure
* should be called in all html files
* if no gSTB object found switches to emulation mode
* if environment variable debug = 1 add some debug tools
* if set environment variable debug_name then add weinre to all pages
* @author Stanislav Kalashnik <sk@infomir.eu>
*/
'use strict';
// global flags
var DEBUG = true,
DEBUG_NAME = '', // primary name and primary address
DEBUG_SERVER = '', // in case they are not given then env vars and local storage will be used
EMULATION = false,
PATH_MODE_WEB = false, // portal is starting from web by http/https or locally
PATH_ROOT = null,
PATH_SYSTEM = null,
PATH_MEDIA = '/media/',
WINDOW_WIDTH = screen.width,
WINDOW_HEIGHT = screen.height,
URL_PARAMS = {}, // query params
initFound = false,
proxy;
PATH_ROOT = location.pathname.split('/');
PATH_ROOT[PATH_ROOT.length - 1] = '';
PATH_ROOT = PATH_ROOT.join('/');
PATH_ROOT = location.protocol + '//' + location.host + PATH_ROOT + PATH_BASE;
PATH_SYSTEM = PATH_ROOT + 'system/';
// any query params
if ( document.location.search ) {
// parse and fill the global list
document.location.search.substring(1).split('&').forEach(function(part){
part = part.split('=');
// valid number on params
if ( part.length === 2 ) { URL_PARAMS[part[0]] = decodeURIComponent(part[1]); }
});
}
// native or emulation
if ( window.gSTB === undefined ) {
DEBUG = EMULATION = true;
// load stored vars
DEBUG_SERVER = DEBUG_SERVER || localStorage.getItem('DEBUG_SERVER');
DEBUG_NAME = DEBUG_NAME || localStorage.getItem('DEBUG_NAME');
// resolution correction
WINDOW_WIDTH = parseInt(URL_PARAMS.width, 10) || window.outerWidth;
WINDOW_HEIGHT = parseInt(URL_PARAMS.height, 10) || window.outerHeight;
// stb global object is not present so emulation
document.write('<script type="text/javascript" src="' + PATH_SYSTEM + 'gstb.js"></script>\n');
document.write('<script type="text/javascript" src="' + PATH_SYSTEM + 'debug.js"></script>\n');
document.write('<script type="text/javascript" src="http://' + DEBUG_SERVER + ':8800/file/client.js"></script>\n');
document.write('<script type="text/javascript">' +
'if ( window.proxyClient ) {' +
'proxy = new proxyClient();' +
'if ( DEBUG_NAME && DEBUG_SERVER ) proxy.init({name:"' + DEBUG_NAME + '", host:"' + DEBUG_SERVER + '"}); }' +
'</script>\n');
// page loading finished
window.addEventListener('load', function(){
document.body.style.backgroundColor = 'black';
});
} else {
// new way of string handling
// all strings are in UTF-16
gSTB.SetNativeStringMode(true);
// check mode
(function(){
var vars = JSON.parse(gSTB.GetEnv(JSON.stringify({varList:['debug', 'debug_name', 'debug_server']})));
if ( !vars.errMsg && vars.result ) {
DEBUG = vars.result.debug === '1';
// no errors and valid string
if ( DEBUG && !URL_PARAMS.nodebug ) {
// general tools
document.write('<script type="text/javascript" src="' + PATH_SYSTEM + 'debug.js"></script>\n');
// rewrite default
DEBUG_NAME = DEBUG_NAME || vars.result.debug_name;
DEBUG_SERVER = DEBUG_SERVER || vars.result.debug_server;
if ( DEBUG_NAME && DEBUG_SERVER && stbStorage.getItem('spyjs.active') !== '1' ) {
// web inspector
document.write('<script type="text/javascript" src="http://' + DEBUG_SERVER + ':8000/target/target-script-min.js#' + DEBUG_NAME + '"></script>\n');
}
}
}
})();
}
// additional path to image
var PATH_IMG_SYSTEM = PATH_SYSTEM + 'img/' + WINDOW_WIDTH + '/',
PATH_IMG_PUBLIC = PATH_ROOT + 'public/img/' + WINDOW_WIDTH + '/';

248
mag/mini/system/jsondb.js Normal file
View File

@ -0,0 +1,248 @@
/**
* Json DataBase simple implementation
* @author Stanislav Kalashnik <sk@infomir.eu>
* @license GNU GENERAL PUBLIC LICENSE Version 3
*/
'use strict';
/**
* Main DataBase object
* @constructor
* @param {Object} [data={}] initial data provided
*/
function jsonDb ( data ) {
this.data = data || {};
}
/**
* Table handler
* gets the specified table by its name or creates one
* @param {string} name new or existing db table name
* @return {jsonDbTable|boolean} newly created table object or false on failure
*/
jsonDb.prototype.table = function ( name ) {
if ( name ) {
// data placeholder if not exist
if ( typeof this.data[name] !== 'object' ) {
this.data[name] = {};
}
// wrap data with object
return new jsonDbTable(this.data[name]);
}
return false;
};
/**
* DataBase table object
* should be called directly only from the jsonDb object
* @constructor
* @param {Object} [data={}] initial data provided
*/
function jsonDbTable ( data ) {
this.data = data || {};
}
/**
* Initializes the table inner data structure and sets table field names
* @param {string} id the first mandatory column
* @param {...String} fields free list of fields
* @return {boolean} operation success status
* @example
* // two columns table creation
* table.init('id', 'name');
*/
jsonDbTable.prototype.init = function ( id, fields ) {
if ( arguments.length > 0 ) {
// reset all to default (without breaking the link to db)
this.data.rows = {}; // all table records in the compact form {id:[fld1, fld2, fld3]}
this.data.idName = Array.prototype.shift.call(arguments);
this.data.rowIdx = 0; // last used table index (is used by genId method to get unique id)
this.data.fldIds = []; // list ['name', 'title']
this.data.fldIdx = {}; // hash {name:0, title:1}
// apply all other fields names
for ( var index = 0; index < arguments.length; index++ ) {
// plain list of names
this.data.fldIds[index] = arguments[index];
// hash map name to index
this.data.fldIdx[arguments[index]] = index;
}
return true;
}
return false;
};
/**
* Unique identifier counter
* produces an id on each call
* @return {string} id for add method
*/
jsonDbTable.prototype.genId = function () {
return (++this.data.rowIdx).toString();
};
/**
* Creates a new data record with the given id
* @param {string|number} id mandatory unique row identifier (converted to string)
* @param {...*} data free list of column values
* @return {boolean} operation success status
* @example
* // new record with id 2
* table.add(2, 'some name');
*/
jsonDbTable.prototype.add = function ( id, data ) {
var row = [], index;
// init new record
if ( id && this.data.rows[id] === undefined ) {
// iterate all table fields
for ( index = 0; index < this.data.fldIds.length; index++ ) {
// and get corresponding items from incoming data
row[index] = arguments[index+1] !== undefined ? arguments[index+1] : null;
}
// apply a filled new record
this.data.rows[id] = row;
return true;
}
return false;
};
/**
* Retrieves a data record by the given id
* @param {string|number} id mandatory unique row identifier
* @param {string} [field] row column name to return
* @return {*} row data item, its one field value or false on failure
* @example
* // get record data with id 2
* table.get(2);
* // get a single record data field with id 2
* table.get(2, 'name');
*/
jsonDbTable.prototype.get = function ( id, field ) {
var row = this.data.rows[id],
index, data = {};
// existing record
if ( row !== undefined ) {
// field name is given
if ( field !== undefined ) {
// and valid
if ( field in this.data.fldIdx ) {
// single field
return row[this.data.fldIdx[field]];
}
} else {
// explicit id return
data[this.data.idName] = id.toString();
// make a real object from a compact record
for ( index = 0; index < row.length; index++ ) {
data[this.data.fldIds[index]] = row[index];
}
// all fields
return data;
}
}
return false;
};
/**
* Updates the record find by id with the given fields
* in case there is no such record creates a new one
* @param {string|number} id mandatory unique row identifier
* @param {Object} data pairs of field names with values
* @return {boolean} operation success status
* @example
* // update record with id 2
* table.set(2, {name:'some other name'});
*/
jsonDbTable.prototype.set = function ( id, data ) {
var row = [], index, name;
// valid incoming data
if ( id && data && typeof data === 'object' ) {
// init a new record if necessary
if ( this.data.rows[id] === undefined ) {
// iterate all table fields and set to null
for ( index = 0; index < this.data.fldIds.length; index++ ) {
row[index] = null;
}
// apply a null-filled new record
this.data.rows[id] = row;
} else {
// link to existing item
row = this.data.rows[id];
}
// update the given fields
for ( name in data ) {
// fill some existing fields
if ( name in this.data.fldIdx ) {
row[this.data.fldIdx[name]] = data[name];
}
}
return true;
}
return false;
};
/**
* Clears a row by the given id
* @param {string|number} id mandatory unique row identifier
* @return {boolean} operation success status
* @example
* // remove record with id 2
* table.unset(2);
*/
jsonDbTable.prototype.unset = function ( id ) {
return delete this.data.rows[id];
};
/**
* Removes all records and resets the index
*/
jsonDbTable.prototype.clear = function () {
this.data.rows = {};
this.data.rowIdx = 0;
};
/**
* Collects all records matching the given filter
* @param {Object} where filter conditions to check ({} - all records, omitted - no records)
* @param {number} [limit=none] positive number of records to return (otherwise - no limitations)
* @return {Array} all found rows
* @example
* // get all records with the specific name
* table.find({name:'some name'});
* // get only 2 records with the specific name
* table.find({name:'some name'}, 2);
*/
jsonDbTable.prototype.find = function ( where, limit ) {
var rows = this.data.rows, item,
data = [], id, condition, match, index;
// check incoming filter
if ( where && typeof where === 'object' ) {
// iterate all table rows
for ( id in rows ) {
// suitable by default
match = true;
// check all the filter attributes (all should match)
for ( condition in where ) {
match = match && (rows[id][this.data.fldIdx[condition]] === where[condition]);
}
// fill result list with matched records
if ( match ) {
// wrap
item = {};
// explicit id return
item[this.data.idName] = id;
// make a real object from a compact record
for ( index = 0; index < rows[id].length; index++ ) {
item[this.data.fldIds[index]] = rows[id][index];
}
data.push(item);
// get enough check and exit
if ( limit !== undefined && limit === data.length ) {
return data;
}
}
}
}
return data;
};

63
mag/mini/system/keys.js Normal file
View File

@ -0,0 +1,63 @@
'use strict';
/**
* Global list of key codes
* with shift key pressed +1000
* with alt key pressed +2000
* @namespace
*/
var KEYS = {
BACK : 8, // Backspace
NUM1 : 49,
NUM2 : 50,
NUM3 : 51,
NUM4 : 52,
NUM5 : 53,
NUM6 : 54,
NUM7 : 55,
NUM8 : 56,
NUM9 : 57,
NUM0 : 48,
DELETE : 46,
CHANNEL_PREV : 1009, // Shift+Tab
CHANNEL_NEXT : 9, // Tab
OK : 13, // Enter
EXIT : 27, // Esc
UP : 38, // UP ARROW
DOWN : 40, // DOWN ARROW
LEFT : 37, // LEFT ARROW
RIGHT : 39, // RIGHT ARROW
PAGE_UP : 33, // Page Up
PAGE_DOWN : 34, // Page Down
END : 35,
HOME : 36,
VOLUME_UP : 107, // NUMPAD +
VOLUME_DOWN : 109, // NUMPAD -
F1 : 112, // F1
F2 : 113, // F2
F3 : 114, // F3
F4 : 115, // F4
REFRESH : 116, // F5
FRAME : 117, // F6
PHONE : 119, // F8
EPG : 119, // F8
SET : 120, // F9
TV : 121, // F10
MENU : 122, // F11
WEB : 123, // F12
MIC : 2032,
REWIND : 2066, // Alt+B
FORWARD : 2070, // Alt+F
APP : 2076, // Alt+L
USB_MOUNTED : 2080, // Alt+P
USB_UNMOUNTED: 2081, // Alt+Q
PLAY_PAUSE : 2082, // Alt+R
STOP : 2083, // Alt+S
POWER : 2085, // Alt+U
RECORD : 2087, // Alt+W
INFO : 2089, // Alt+Y
MUTE : 2192,
CLOCK : 2032,
AUDIO : 2071, // Alt+G
KEYBOARD : 2076 // Alt+L
};

View File

@ -0,0 +1,398 @@
'use strict';
/**
* Global list of time zones
* @namespace
* TODO: should be removed unused property "offset"
*/
var TIME_ZONES = [
{'id': 'Africa/Accra', 'offset': '+00:00'},
{'id': 'Africa/Addis_Ababa', 'offset': '+03:00', 'name': 'Africa/Addis Ababa'},
{'id': 'Africa/Algiers', 'offset': '+01:00'},
{'id': 'Africa/Asmera', 'offset': '+03:00'},
{'id': 'Africa/Bamako', 'offset': '+00:00'},
{'id': 'Africa/Bangui', 'offset': '+01:00'},
{'id': 'Africa/Banjul', 'offset': '+00:00'},
{'id': 'Africa/Bissau', 'offset': '+00:00'},
{'id': 'Africa/Blantyre', 'offset': '+02:00'},
{'id': 'Africa/Brazzaville', 'offset': '+01:00'},
{'id': 'Africa/Bujumbura', 'offset': '+02:00'},
{'id': 'Africa/Cairo', 'offset': '+02:00'},
{'id': 'Africa/Casablanca', 'offset': '+00:00'},
{'id': 'Africa/Ceuta', 'offset': '+01:00'},
{'id': 'Africa/Conakry', 'offset': '+00:00'},
{'id': 'Africa/Dakar', 'offset': '+00:00'},
{'id': 'Africa/Dar_es_Salaam', 'offset': '+03:00', 'name': 'Africa/Dar es Salaam'},
{'id': 'Africa/Djibouti', 'offset': '+03:00'},
{'id': 'Africa/Douala', 'offset': '+01:00'},
{'id': 'Africa/El_Aaiun', 'offset': '+00:00', 'name': 'Africa/El Aaiun'},
{'id': 'Africa/Freetown', 'offset': '+00:00'},
{'id': 'Africa/Gaborone', 'offset': '+02:00'},
{'id': 'Africa/Harare', 'offset': '+02:00'},
{'id': 'Africa/Johannesburg', 'offset': '+02:00'},
{'id': 'Africa/Kampala', 'offset': '+03:00'},
{'id': 'Africa/Khartoum', 'offset': '+03:00'},
{'id': 'Africa/Kigali', 'offset': '+02:00'},
{'id': 'Africa/Kinshasa', 'offset': '+01:00'},
{'id': 'Africa/Lagos', 'offset': '+01:00'},
{'id': 'Africa/Libreville', 'offset': '+01:00'},
{'id': 'Africa/Lome', 'offset': '+00:00'},
{'id': 'Africa/Luanda', 'offset': '+01:00'},
{'id': 'Africa/Lubumbashi', 'offset': '+02:00'},
{'id': 'Africa/Lusaka', 'offset': '+02:00'},
{'id': 'Africa/Malabo', 'offset': '+01:00'},
{'id': 'Africa/Maputo', 'offset': '+02:00'},
{'id': 'Africa/Maseru', 'offset': '+02:00'},
{'id': 'Africa/Mbabane', 'offset': '+02:00'},
{'id': 'Africa/Mogadishu', 'offset': '+03:00'},
{'id': 'Africa/Monrovia', 'offset': '+00:00'},
{'id': 'Africa/Nairobi', 'offset': '+03:00'},
{'id': 'Africa/Ndjamena', 'offset': '+01:00'},
{'id': 'Africa/Niamey', 'offset': '+01:00'},
{'id': 'Africa/Nouakchott', 'offset': '+00:00'},
{'id': 'Africa/Ouagadougou', 'offset': '+00:00'},
{'id': 'Africa/Porto-Novo', 'offset': '+01:00'},
{'id': 'Africa/Sao_Tome', 'offset': '+00:00', 'name': 'Africa/Sao Tome'},
{'id': 'Africa/Tripoli', 'offset': '+02:00'},
{'id': 'Africa/Tunis', 'offset': '+01:00'},
{'id': 'Africa/Windhoek', 'offset': '+01:00'},
{'id': 'America/Adak', 'offset': '-10:00'},
{'id': 'America/Anchorage', 'offset': '-09:00'},
{'id': 'America/Anguilla', 'offset': '-04:00'},
{'id': 'America/Antigua', 'offset': '-04:00'},
{'id': 'America/Araguaina', 'offset': '-03:00'},
{'id': 'America/Argentina/Buenos_Aires', 'offset': '-03:00', 'name': 'America/Argentina/Buenos Aires'},
{'id': 'America/Argentina/Catamarca', 'offset': '-03:00'},
{'id': 'America/Argentina/Cordoba', 'offset': '-03:00'},
{'id': 'America/Argentina/Jujuy', 'offset': '-03:00'},
{'id': 'America/Argentina/La_Rioja', 'offset': '-03:00', 'name': 'America/Argentina/La Rioja'},
{'id': 'America/Argentina/Mendoza', 'offset': '-03:00'},
{'id': 'America/Argentina/Rio_Gallegos', 'offset': '-03:00', 'name': 'America/Argentina/Rio Gallegos'},
{'id': 'America/Argentina/San_Juan', 'offset': '-03:00', 'name': 'America/Argentina/San Juan'},
{'id': 'America/Argentina/Tucuman', 'offset': '-03:00'},
{'id': 'America/Argentina/Ushuaia', 'offset': '-03:00'},
{'id': 'America/Aruba', 'offset': '-04:00'},
{'id': 'America/Asuncion', 'offset': '-04:00'},
{'id': 'America/Bahia', 'offset': '-03:00'},
{'id': 'America/Barbados', 'offset': '-04:00'},
{'id': 'America/Belem', 'offset': '-03:00'},
{'id': 'America/Belize', 'offset': '-06:00'},
{'id': 'America/Boa_Vista', 'offset': '-04:00', 'name': 'America/Boa Vista'},
{'id': 'America/Bogota', 'offset': '-05:00'},
{'id': 'America/Boise', 'offset': '-07:00'},
{'id': 'America/Cambridge_Bay', 'offset': '-07:00', 'name': 'America/Cambridge Bay'},
{'id': 'America/Campo_Grande', 'offset': '-04:00', 'name': 'America/Campo Grande'},
{'id': 'America/Cancun', 'offset': '-06:00'},
{'id': 'America/Caracas', 'offset': '-04:30'},
{'id': 'America/Cayenne', 'offset': '-03:00'},
{'id': 'America/Cayman', 'offset': '-05:00'},
{'id': 'America/Chicago', 'offset': '-06:00'},
{'id': 'America/Chihuahua', 'offset': '-07:00'},
{'id': 'America/Coral_Harbour', 'offset': '-05:00', 'name': 'America/Coral Harbour'},
{'id': 'America/Costa_Rica', 'offset': '-06:00', 'name': 'America/Costa Rica'},
{'id': 'America/Cuiaba', 'offset': '-04:00'},
{'id': 'America/Curacao', 'offset': '-04:00'},
{'id': 'America/Danmarkshavn', 'offset': '+00:00'},
{'id': 'America/Dawson', 'offset': '-08:00'},
{'id': 'America/Dawson_Creek', 'offset': '-07:00', 'name': 'America/Dawson Creek'},
{'id': 'America/Denver', 'offset': '-07:00'},
{'id': 'America/Detroit', 'offset': '-05:00'},
{'id': 'America/Dominica', 'offset': '-04:00'},
{'id': 'America/Edmonton', 'offset': '-07:00'},
{'id': 'America/Eirunepe', 'offset': '-04:00'},
{'id': 'America/El_Salvador', 'offset': '-06:00', 'name': 'America/El Salvador'},
{'id': 'America/Fortaleza', 'offset': '-03:00'},
{'id': 'America/Glace_Bay', 'offset': '-04:00', 'name': 'America/Glace Bay'},
{'id': 'America/Godthab', 'offset': '-03:00'},
{'id': 'America/Goose_Bay', 'offset': '-04:00', 'name': 'America/Goose Bay'},
{'id': 'America/Grand_Turk', 'offset': '-05:00', 'name': 'America/Grand Turk'},
{'id': 'America/Grenada', 'offset': '-04:00'},
{'id': 'America/Guadeloupe', 'offset': '-04:00'},
{'id': 'America/Guatemala', 'offset': '-06:00'},
{'id': 'America/Guayaquil', 'offset': '-05:00'},
{'id': 'America/Guyana', 'offset': '-04:00'},
{'id': 'America/Halifax', 'offset': '-04:00'},
{'id': 'America/Havana', 'offset': '-05:00'},
{'id': 'America/Hermosillo', 'offset': '-07:00'},
{'id': 'America/Indiana/Indianapolis', 'offset': '-05:00'},
{'id': 'America/Indiana/Knox', 'offset': '-06:00'},
{'id': 'America/Indiana/Marengo', 'offset': '-05:00'},
{'id': 'America/Indiana/Petersburg', 'offset': '-05:00'},
{'id': 'America/Indiana/Vevay', 'offset': '-05:00'},
{'id': 'America/Indiana/Vincennes', 'offset': '-05:00'},
{'id': 'America/Inuvik', 'offset': '-07:00'},
{'id': 'America/Iqaluit', 'offset': '-05:00'},
{'id': 'America/Juneau', 'offset': '-09:00'},
{'id': 'America/Kentucky/Louisville', 'offset': '-05:00'},
{'id': 'America/Kentucky/Monticello', 'offset': '-05:00'},
{'id': 'America/La_Paz', 'offset': '-04:00', 'name': 'America/La Paz'},
{'id': 'America/Lima', 'offset': '-05:00'},
{'id': 'America/Los_Angeles', 'offset': '-08:00', 'name': 'America/Los Angeles'},
{'id': 'America/Maceio', 'offset': '-03:00'},
{'id': 'America/Managua', 'offset': '-06:00'},
{'id': 'America/Manaus', 'offset': '-04:00'},
{'id': 'America/Martinique', 'offset': '-04:00'},
{'id': 'America/Mazatlan', 'offset': '-07:00'},
{'id': 'America/Menominee', 'offset': '-06:00'},
{'id': 'America/Merida', 'offset': '-06:00'},
{'id': 'America/Mexico_City', 'offset': '-06:00', 'name': 'America/Mexico City'},
{'id': 'America/Miquelon', 'offset': '-03:00'},
{'id': 'America/Moncton', 'offset': '-04:00'},
{'id': 'America/Monterrey', 'offset': '-06:00'},
{'id': 'America/Montevideo', 'offset': '-03:00'},
{'id': 'America/Montreal', 'offset': '-05:00'},
{'id': 'America/Montserrat', 'offset': '-04:00'},
{'id': 'America/Nassau', 'offset': '-05:00'},
{'id': 'America/New_York', 'offset': '-05:00', 'name': 'America/New York'},
{'id': 'America/Nipigon', 'offset': '-05:00'},
{'id': 'America/Nome', 'offset': '-09:00'},
{'id': 'America/Noronha', 'offset': '-02:00'},
{'id': 'America/North_Dakota/Center', 'offset': '-06:00', 'name': 'America/North Dakota/Center'},
{'id': 'America/Panama', 'offset': '-05:00'},
{'id': 'America/Pangnirtung', 'offset': '-05:00'},
{'id': 'America/Paramaribo', 'offset': '-03:00'},
{'id': 'America/Phoenix', 'offset': '-07:00'},
{'id': 'America/Port-au-Prince', 'offset': '-05:00'},
{'id': 'America/Port_of_Spain', 'offset': '-04:00', 'name': 'America/Port of Spain'},
{'id': 'America/Porto_Velho', 'offset': '-04:00', 'name': 'America/Porto Velho'},
{'id': 'America/Puerto_Rico', 'offset': '-04:00', 'name': 'America/Puerto Rico'},
{'id': 'America/Rainy_River', 'offset': '-06:00', 'name': 'America/Rainy River'},
{'id': 'America/Rankin_Inlet', 'offset': '-06:00', 'name': 'America/Rankin Inlet'},
{'id': 'America/Recife', 'offset': '-03:00'},
{'id': 'America/Regina', 'offset': '-06:00'},
{'id': 'America/Rio_Branco', 'offset': '-05:00', 'name': 'America/Rio Branco'},
{'id': 'America/Santiago', 'offset': '-04:00'},
{'id': 'America/Santo_Domingo', 'offset': '-04:00', 'name': 'America/Santo Domingo'},
{'id': 'America/Sao_Paulo', 'offset': '-03:00', 'name': 'America/Sao Paulo'},
{'id': 'America/Scoresbysund', 'offset': '-01:00'},
{'id': 'America/Shiprock', 'offset': '-07:00'},
{'id': 'America/Swift_Current', 'offset': '-06:00', 'name': 'America/Swift Current'},
{'id': 'America/Tegucigalpa', 'offset': '-06:00'},
{'id': 'America/Thule', 'offset': '-04:00'},
{'id': 'America/Thunder_Bay', 'offset': '-05:00', 'name': 'America/Thunder Bay'},
{'id': 'America/Tijuana', 'offset': '-08:00'},
{'id': 'America/Toronto', 'offset': '-05:00'},
{'id': 'America/Tortola', 'offset': '-04:00'},
{'id': 'America/Vancouver', 'offset': '-08:00'},
{'id': 'America/Whitehorse', 'offset': '-08:00'},
{'id': 'America/Winnipeg', 'offset': '-06:00'},
{'id': 'America/Yakutat', 'offset': '-09:00'},
{'id': 'America/Yellowknife', 'offset': '-07:00'},
{'id': 'Antarctica/Casey', 'offset': '+11:00'},
{'id': 'Antarctica/Davis', 'offset': '+07:00'},
{'id': 'Antarctica/DumontDUrville', 'offset': '+10:00'},
{'id': 'Antarctica/Mawson', 'offset': '+05:00'},
{'id': 'Antarctica/McMurdo', 'offset': '+12:00'},
{'id': 'Antarctica/Palmer', 'offset': '-04:00'},
{'id': 'Antarctica/Rothera', 'offset': '-03:00'},
{'id': 'Antarctica/South_Pole', 'offset': '+12:00', 'name': 'Antarctica/South Pole'},
{'id': 'Antarctica/Syowa', 'offset': '+03:00'},
{'id': 'Antarctica/Vostok', 'offset': '+06:00'},
{'id': 'Arctic/Longyearbyen', 'offset': '+01:00'},
{'id': 'Asia/Aden', 'offset': '+03:00'},
{'id': 'Asia/Almaty', 'offset': '+06:00'},
{'id': 'Asia/Amman', 'offset': '+02:00'},
{'id': 'Asia/Anadyr', 'offset': '+12:00'},
{'id': 'Asia/Aqtau', 'offset': '+05:00'},
{'id': 'Asia/Aqtobe', 'offset': '+05:00'},
{'id': 'Asia/Ashgabat', 'offset': '+05:00'},
{'id': 'Asia/Ashkhabad', 'offset': '+05:00'},
{'id': 'Asia/Baghdad', 'offset': '+03:00'},
{'id': 'Asia/Bahrain', 'offset': '+03:00'},
{'id': 'Asia/Baku', 'offset': '+04:00'},
{'id': 'Asia/Bangkok', 'offset': '+07:00'},
{'id': 'Asia/Beirut', 'offset': '+02:00'},
{'id': 'Asia/Bishkek', 'offset': '+06:00'},
{'id': 'Asia/Brunei', 'offset': '+08:00'},
{'id': 'Asia/Calcutta', 'offset': '+05:30'},
{'id': 'Asia/Choibalsan', 'offset': '+08:00'},
{'id': 'Asia/Chongqing', 'offset': '+08:00'},
{'id': 'Asia/Colombo', 'offset': '+05:30'},
{'id': 'Asia/Damascus', 'offset': '+02:00'},
{'id': 'Asia/Dhaka', 'offset': '+06:00'},
{'id': 'Asia/Dili', 'offset': '+09:00'},
{'id': 'Asia/Dubai', 'offset': '+04:00'},
{'id': 'Asia/Dushanbe', 'offset': '+05:00'},
{'id': 'Asia/Gaza', 'offset': '+02:00'},
{'id': 'Asia/Harbin', 'offset': '+08:00'},
{'id': 'Asia/Ho_Chi_Minh', 'offset': '+07:00', 'name': 'Asia/Ho Chi Minh'},
{'id': 'Asia/Hong_Kong', 'offset': '+08:00', 'name': 'Asia/Hong Kong'},
{'id': 'Asia/Hovd', 'offset': '+07:00'},
{'id': 'Asia/Irkutsk', 'offset': '+09:00'},
{'id': 'Asia/Istanbul', 'offset': '+02:00'},
{'id': 'Asia/Jakarta', 'offset': '+07:00'},
{'id': 'Asia/Jayapura', 'offset': '+09:00'},
{'id': 'Asia/Jerusalem', 'offset': '+02:00'},
{'id': 'Asia/Kabul', 'offset': '+04:30'},
{'id': 'Asia/Kamchatka', 'offset': '+12:00'},
{'id': 'Asia/Karachi', 'offset': '+05:00'},
{'id': 'Asia/Kashgar', 'offset': '+08:00'},
{'id': 'Asia/Katmandu', 'offset': '+05:45'},
{'id': 'Asia/Krasnoyarsk', 'offset': '+08:00'},
{'id': 'Asia/Kuala_Lumpur', 'offset': '+08:00', 'name': 'Asia/Kuala Lumpur'},
{'id': 'Asia/Kuching', 'offset': '+08:00'},
{'id': 'Asia/Kuwait', 'offset': '+03:00'},
{'id': 'Asia/Macau', 'offset': '+08:00'},
{'id': 'Asia/Magadan', 'offset': '+12:00'},
{'id': 'Asia/Makassar', 'offset': '+08:00'},
{'id': 'Asia/Manila', 'offset': '+08:00'},
{'id': 'Asia/Muscat', 'offset': '+04:00'},
{'id': 'Asia/Nicosia', 'offset': '+02:00'},
{'id': 'Asia/Novokuznetsk', 'offset': '+07:00'},
{'id': 'Asia/Novosibirsk', 'offset': '+07:00'},
{'id': 'Asia/Omsk', 'offset': '+07:00'},
{'id': 'Asia/Oral', 'offset': '+05:00'},
{'id': 'Asia/Phnom_Penh', 'offset': '+07:00', 'name': 'Asia/Phnom Penh'},
{'id': 'Asia/Pontianak', 'offset': '+07:00'},
{'id': 'Asia/Pyongyang', 'offset': '+09:00'},
{'id': 'Asia/Qatar', 'offset': '+03:00'},
{'id': 'Asia/Qyzylorda', 'offset': '+06:00'},
{'id': 'Asia/Rangoon', 'offset': '+06:30'},
{'id': 'Asia/Riyadh', 'offset': '+03:00'},
{'id': 'Asia/Saigon', 'offset': '+07:00'},
{'id': 'Asia/Sakhalin', 'offset': '+11:00'},
{'id': 'Asia/Samarkand', 'offset': '+05:00'},
{'id': 'Asia/Seoul', 'offset': '+09:00'},
{'id': 'Asia/Shanghai', 'offset': '+08:00'},
{'id': 'Asia/Taipei', 'offset': '+08:00'},
{'id': 'Asia/Tashkent', 'offset': '+05:00'},
{'id': 'Asia/Tbilisi', 'offset': '+04:00'},
{'id': 'Asia/Tehran', 'offset': '+03:30'},
{'id': 'Asia/Thimphu', 'offset': '+06:00'},
{'id': 'Asia/Tokyo', 'offset': '+09:00'},
{'id': 'Asia/Ulaanbaatar', 'offset': '+08:00'},
{'id': 'Asia/Urumqi', 'offset': '+08:00'},
{'id': 'Asia/Vientiane', 'offset': '+07:00'},
{'id': 'Asia/Vladivostok', 'offset': '+11:00'},
{'id': 'Asia/Yakutsk', 'offset': '+10:00'},
{'id': 'Asia/Yekaterinburg', 'offset': '+06:00'},
{'id': 'Asia/Yerevan', 'offset': '+04:00'},
{'id': 'Atlantic/Azores', 'offset': '-01:00'},
{'id': 'Atlantic/Bermuda', 'offset': '-04:00'},
{'id': 'Atlantic/Canary', 'offset': '+00:00'},
{'id': 'Atlantic/Cape_Verde', 'offset': '-01:00', 'name': 'Atlantic/Cape Verde'},
{'id': 'Atlantic/Faeroe', 'offset': '+00:00'},
{'id': 'Atlantic/Jan_Mayen', 'offset': '+01:00', 'name': 'Atlantic/Jan Mayen'},
{'id': 'Atlantic/Madeira', 'offset': '+00:00'},
{'id': 'Atlantic/Reykjavik', 'offset': '+00:00'},
{'id': 'Atlantic/South_Georgia', 'offset': '-02:00', 'name': 'Atlantic/South Georgia'},
{'id': 'Atlantic/Stanley', 'offset': '-03:00'},
{'id': 'Australia/Adelaide', 'offset': '+09:30'},
{'id': 'Australia/Brisbane', 'offset': '+10:00'},
{'id': 'Australia/Broken_Hill', 'offset': '+09:30', 'name': 'Australia/Broken Hill'},
{'id': 'Australia/Currie', 'offset': '+10:00'},
{'id': 'Australia/Darwin', 'offset': '+09:30'},
{'id': 'Australia/Hobart', 'offset': '+10:00'},
{'id': 'Australia/Lindeman', 'offset': '+10:00'},
{'id': 'Australia/Lord_Howe', 'offset': '+10:30', 'name': 'Australia/Lord Howe'},
{'id': 'Australia/Melbourne', 'offset': '+10:00'},
{'id': 'Australia/Perth', 'offset': '+08:00'},
{'id': 'Australia/Sydney', 'offset': '+10:00'},
{'id': 'Europe/Amsterdam', 'offset': '+01:00'},
{'id': 'Europe/Andorra', 'offset': '+01:00'},
{'id': 'Europe/Athens', 'offset': '+02:00'},
{'id': 'Europe/Belfast', 'offset': '+00:00'},
{'id': 'Europe/Belgrade', 'offset': '+01:00'},
{'id': 'Europe/Berlin', 'offset': '+01:00'},
{'id': 'Europe/Bratislava', 'offset': '+01:00'},
{'id': 'Europe/Brussels', 'offset': '+01:00'},
{'id': 'Europe/Bucharest', 'offset': '+02:00'},
{'id': 'Europe/Budapest', 'offset': '+01:00'},
{'id': 'Europe/Chisinau', 'offset': '+02:00'},
{'id': 'Europe/Copenhagen', 'offset': '+01:00'},
{'id': 'Europe/Dublin', 'offset': '+00:00'},
{'id': 'Europe/Gibraltar', 'offset': '+01:00'},
{'id': 'Europe/Guernsey', 'offset': '+00:00'},
{'id': 'Europe/Helsinki', 'offset': '+02:00'},
{'id': 'Europe/Isle_of_Man', 'offset': '+00:00', 'name': 'Europe/Isle of Man'},
{'id': 'Europe/Istanbul', 'offset': '+02:00'},
{'id': 'Europe/Jersey', 'offset': '+00:00'},
{'id': 'Europe/Kaliningrad', 'offset': '+03:00'},
{'id': 'Europe/Kiev', 'offset': '+02:00'},
{'id': 'Europe/Lisbon', 'offset': '+00:00'},
{'id': 'Europe/Ljubljana', 'offset': '+01:00'},
{'id': 'Europe/London', 'offset': '+00:00'},
{'id': 'Europe/Luxembourg', 'offset': '+01:00'},
{'id': 'Europe/Madrid', 'offset': '+01:00'},
{'id': 'Europe/Malta', 'offset': '+01:00'},
{'id': 'Europe/Mariehamn', 'offset': '+02:00'},
{'id': 'Europe/Minsk', 'offset': '+03:00'},
{'id': 'Europe/Monaco', 'offset': '+01:00'},
{'id': 'Europe/Moscow', 'offset': '+03:00'},
{'id': 'Europe/Nicosia', 'offset': '+02:00'},
{'id': 'Europe/Oslo', 'offset': '+01:00'},
{'id': 'Europe/Paris', 'offset': '+01:00'},
{'id': 'Europe/Podgorica', 'offset': '+01:00'},
{'id': 'Europe/Prague', 'offset': '+01:00'},
{'id': 'Europe/Riga', 'offset': '+02:00'},
{'id': 'Europe/Rome', 'offset': '+01:00'},
{'id': 'Europe/Samara', 'offset': '+04:00'},
{'id': 'Europe/San_Marino', 'offset': '+01:00', 'name': 'Europe/San Marino'},
{'id': 'Europe/Sarajevo', 'offset': '+01:00'},
{'id': 'Europe/Simferopol', 'offset': '+02:00'},
{'id': 'Europe/Skopje', 'offset': '+01:00'},
{'id': 'Europe/Sofia', 'offset': '+02:00'},
{'id': 'Europe/Stockholm', 'offset': '+01:00'},
{'id': 'Europe/Tallinn', 'offset': '+02:00'},
{'id': 'Europe/Tirane', 'offset': '+01:00'},
{'id': 'Europe/Tiraspol', 'offset': '+02:00'},
{'id': 'Europe/Uzhgorod', 'offset': '+02:00'},
{'id': 'Europe/Vaduz', 'offset': '+01:00'},
{'id': 'Europe/Vatican', 'offset': '+01:00'},
{'id': 'Europe/Vienna', 'offset': '+01:00'},
{'id': 'Europe/Vilnius', 'offset': '+02:00'},
{'id': 'Europe/Volgograd', 'offset': '+04:00'},
{'id': 'Europe/Warsaw', 'offset': '+01:00'},
{'id': 'Europe/Zagreb', 'offset': '+01:00'},
{'id': 'Europe/Zaporozhye', 'offset': '+02:00'},
{'id': 'Europe/Zurich', 'offset': '+01:00'},
{'id': 'Indian/Antananarivo', 'offset': '+03:00'},
{'id': 'Indian/Chagos', 'offset': '+06:00'},
{'id': 'Indian/Christmas', 'offset': '+07:00'},
{'id': 'Indian/Cocos', 'offset': '+06:30'},
{'id': 'Indian/Comoro', 'offset': '+03:00'},
{'id': 'Indian/Kerguelen', 'offset': '+05:00'},
{'id': 'Indian/Mahe', 'offset': '+04:00'},
{'id': 'Indian/Maldives', 'offset': '+05:00'},
{'id': 'Indian/Mauritius', 'offset': '+04:00'},
{'id': 'Indian/Mayotte', 'offset': '+03:00'},
{'id': 'Indian/Reunion', 'offset': '+04:00'},
{'id': 'Pacific/Apia', 'offset': '+13:00'},
{'id': 'Pacific/Auckland', 'offset': '+12:00'},
{'id': 'Pacific/Chatham', 'offset': '+12:45'},
{'id': 'Pacific/Easter', 'offset': '-06:00'},
{'id': 'Pacific/Efate', 'offset': '+11:00'},
{'id': 'Pacific/Enderbury', 'offset': '+13:00'},
{'id': 'Pacific/Fakaofo', 'offset': '+13:00'},
{'id': 'Pacific/Fiji', 'offset': '+12:00'},
{'id': 'Pacific/Funafuti', 'offset': '+12:00'},
{'id': 'Pacific/Galapagos', 'offset': '-06:00'},
{'id': 'Pacific/Gambier', 'offset': '-09:00'},
{'id': 'Pacific/Guadalcanal', 'offset': '+11:00'},
{'id': 'Pacific/Guam', 'offset': '+10:00'},
{'id': 'Pacific/Honolulu', 'offset': '-10:00'},
{'id': 'Pacific/Johnston', 'offset': '-10:00'},
{'id': 'Pacific/Kiritimati', 'offset': '+14:00'},
{'id': 'Pacific/Kosrae', 'offset': '+11:00'},
{'id': 'Pacific/Kwajalein', 'offset': '+12:00'},
{'id': 'Pacific/Majuro', 'offset': '+12:00'},
{'id': 'Pacific/Marquesas', 'offset': '-09:30'},
{'id': 'Pacific/Midway', 'offset': '-11:00'},
{'id': 'Pacific/Nauru', 'offset': '+12:00'},
{'id': 'Pacific/Niue', 'offset': '-11:00'},
{'id': 'Pacific/Norfolk', 'offset': '+11:30'},
{'id': 'Pacific/Noumea', 'offset': '+11:00'},
{'id': 'Pacific/Pago_Pago', 'offset': '-11:00', 'name': 'Pacific/Pago Pago'},
{'id': 'Pacific/Palau', 'offset': '+09:00'},
{'id': 'Pacific/Pitcairn', 'offset': '-08:00'},
{'id': 'Pacific/Ponape', 'offset': '+11:00'},
{'id': 'Pacific/Port_Moresby', 'offset': '+10:00', 'name': 'Pacific/Port Moresby'},
{'id': 'Pacific/Rarotonga', 'offset': '-10:00'},
{'id': 'Pacific/Saipan', 'offset': '+10:00'},
{'id': 'Pacific/Tahiti', 'offset': '-10:00'},
{'id': 'Pacific/Tarawa', 'offset': '+12:00'},
{'id': 'Pacific/Tongatapu', 'offset': '+13:00'},
{'id': 'Pacific/Truk', 'offset': '+10:00'},
{'id': 'Pacific/Wake', 'offset': '+12:00'},
{'id': 'Pacific/Wallis', 'offset': '+12:00'}
];

804
mag/mini/system/tools.js Normal file
View File

@ -0,0 +1,804 @@
/**
* Set of base functionality tools
* @author Stanislav Kalashnik <sk@infomir.eu>
*/
'use strict';
/* jshint unused:false */
// debug print blank placeholder for release builds
if ( window.echo === undefined ) { window.echo = function ( data, title ) {}; }
/**
* Assigns a list of attribute values to the given object
* @param {Node|Element|HTMLElement} obj html element
* @param {Object} attr list of attributes with values
* @return {Node|Element|HTMLElement} the same as the given one
* @example
* elattr(myimg, {src:'face.png', className:'main'});
*/
function elattr ( obj, attr ) {
// check if Node and valid attr list
if ( obj && obj.nodeType && attr && attr instanceof Object ) {
// assign attributes
for ( var key in attr ) {
if ( attr.hasOwnProperty(key) ) { obj[key] = attr[key]; }
}
}
return obj;
}
/**
* Creates a DOM element with given options
* @param {string} name html element name (a, img, div, ...)
* @param {Object} [attr] list of attributes with values
* @param {Node|Element|HTMLElement|Array|string|number} [data] inner html value
* @return {Node|Element|HTMLElement}
* @example
* element('div', {}, 'some text');
* element('div', {}, [element('span'), element('br')]);
* element('link', {rel:'stylesheet', type:'text/css', href:'http://some.url/'});
*/
function element ( name, attr, data ) {
var tag = document.createElement(name);
// set attributes
elattr(tag, attr);
// add to the dom
elchild(tag, data);
// Node is ready
return tag;
}
/**
* Adds the given value to the obj as a child recursively
* @param {Node|Element|HTMLElement} obj element to be appended
* @param value data to add (simple text values, Nodes, array of Nodes)
* @return {Node|Element|HTMLElement} owner element of all added data
* @example
* elchild(mydiv, 'Hello world'); // simple text value
* elchild(mydiv, someotherdiv); // Node
* elchild(mydiv, [div1, div2, div3]); // Node list
* elchild(mydiv, [div1, 'hello', 'world']); // combined case
*/
function elchild ( obj, value ) {
// check input
if ( obj && value ) {
// Node
if ( value.nodeType ) { obj.appendChild(value); }
// array of Nodes of simple values
else if ( Array.isArray(value) ) {
for ( var i = 0; i < value.length; i++ ) { elchild(obj, value[i]); }
}
// simple values
else {
obj.appendChild(document.createTextNode(value));
}
}
return obj;
}
/**
* Removes all child elements from the given object
* @param {Node|Element|HTMLElement} obj html element to be updated
* @return {Node|Element|HTMLElement} cleared element
*/
function elclear ( obj ) {
// check input
if ( obj !== null && obj.nodeName && obj.hasChildNodes() ) {
// clearing
while ( obj.hasChildNodes() ) { obj.removeChild(obj.firstChild); }
}
return obj;
}
/**
* Loads the given JavaScript file dynamically
* head injection method is used
* @param {string} src file to be loaded
* @param {Function} [onload] optional on ready handler
* @param {Function} [onerror] optional on error handler
*/
function loadScript ( src, onload, onerror ) {
// Node init
var elem = document.createElement('script');
elem.type = 'text/javascript';
elem.src = src;
// set handlers if given
if ( typeof onload === 'function' ) { elem.onload = onload; }
if ( typeof onerror === 'function' ) { elem.onerror = onerror; }
// push to dom
document.head.appendChild(elem);
}
/**
* Returns the active language (from the available lang list)
* @param {Array} [langList] the set of available valid language names
* @return {string} lowercase lang name
*/
function getCurrentLanguage ( langList ) {
var langEnv = JSON.parse(gSTB.GetEnv('{"varList":["language"]}')),
langVal = 'en';
// reset valid languages if necessary
langList = langList || ['en', 'ru', 'uk', 'de', 'tr'/*, 'el', 'es', 'bg'*/];
// no errors and valid string
if ( !langEnv.errMsg && langEnv.result && langEnv.result.language && typeof langEnv.result.language === 'string' ) {
// make sure it is lowercase
langEnv.result.language = langEnv.result.language.toLowerCase();
// the lang is from valid list
if ( langList.indexOf(langEnv.result.language) !== -1 ) { langVal = langEnv.result.language; }
}
return langVal;
}
/**
* Returns the list of environment variables
* @param {Array} list array of variables names
* @returns {Object|boolean} return hash if all alright and false if there is some error
*/
function getEnvList ( list ) {
var data;
try {
data = JSON.parse(gSTB.GetEnv(JSON.stringify({varList: list})));
if ( data.errMsg !== '' ) {
throw new Error(data.errMsg);
}
return data.result;
} catch ( e ) {
echo(e);
}
return false;
}
/**
* Return specified environment variable
* @param {string} name variable name
* @returns {Object|boolean} return string with if all alright
*/
function getEnv ( name ) {
var data = getEnvList([name]);
if ( data !== false && data[name] !== undefined ) {
return data[name];
}
return data;
}
/**
* Set some environment variable
* @param {string} key variable name
* @param {string|number} value variable value
* @returns {boolean} true if all alright and false if there is some error
*/
function setEnv ( key, value ) {
var vars = {};
vars[key] = value;
return setEnvList(vars);
}
/**
* Set several environment variables
* @param {Object} hash object where key is the variable name and the value is the variable value
* @returns {boolean} true if all alright and false if there is some error
*/
function setEnvList ( hash ) {
try {
return gSTB.SetEnv(JSON.stringify(hash));
} catch ( e ) {
echo(e);
}
return false;
}
/**
* Prepare global event object - add the real key code
* should be called only once at the beginning of events chain
* with shift key pressed +1000
* with alt key pressed +2000
* @param {Event} event object to be altered
* @param {boolean} [stopBubble=true] stops all propagation of the event in the bubbling phase
* @param {string} [label] optional text info for debug
* @returns {boolean} true - valid event; false - phantom and should be skipped
*/
function eventPrepare ( event, stopBubble, label ) {
// prevent double invoke
if ( event.code !== undefined ) { return true; }
// determine real key code
event.code = event.keyCode || event.which;
// filter phantoms
if ( event.code === 0 ) { return false; }
// apply key modifiers
if ( event.shiftKey ) { event.code += 1000; }
if ( event.altKey ) { event.code += 2000; }
// stop deprecated usb event
if ( event.code === KEYS.USB_MOUNTED || event.code === KEYS.USB_UNMOUNTED ) { return false; }
// stop bubbling if necessary
if ( stopBubble !== false ) { event.stopPropagation(); }
// debug
echo(event.code + '\t' + event.shiftKey + '\t' + event.ctrlKey + '\t' + event.altKey + '\t' + event.srcElement.id + '\t' + event.target.id + '\t' + (label || ''),
'keyDown [code/shift/ctrl/alt/src/target]');
return true;
}
// global flag to prevent ajax queries
ajax.stop = false;
/**
* Ajax request
* @param {string} method "post", "get" or "head"
* @param {string} url address
* @param {Function} callback on
* @param {Object} [headers] list of optional headers like "charset", "Content-Type" and so on
* @param {string} [type=text] data parsing mode: plain text (default), xml, json
* @param {boolean} [async=true] send asynchronous request
* @param {number} [abortTimeout=60000] timeout before request abort (ms)
*
* @return {XMLHttpRequest} request object in case response headers are necessary
*
* @example
* ajax('get', 'https://google.com/', function(data, status){console.info(data, status);}, {charset:'utf-8'})
*/
function ajax ( method, url, callback, headers, type, async, abortTimeout ) {
var jdata = null,
timeout = null,
xhr = new XMLHttpRequest(),
title = 'AJAX ' + method.toUpperCase() + ' ' + url,
hname, aborted;
async = async !== false;
abortTimeout = abortTimeout || 60000;
xhr.onreadystatechange = function () {
if ( xhr.readyState === 4 ) {
if ( aborted ) {
return;
}
clearTimeout(timeout);
if ( ajax.stop ) {
echo(xhr.status, title);
if ( typeof callback === 'function' ) {
callback(null, null, null);
}
} else {
echo('status:' + xhr.status + ', length:' + xhr.responseText.length, title);
if ( type === 'json' && xhr.status === 200 ) {
try {
jdata = JSON.parse(xhr.responseText);
} catch ( e ) {
echo(e, 'AJAX JSON.parse');
jdata = null;
}
}
if ( typeof callback === 'function' ) {
callback(type === 'xml' ? xhr.responseXML : (type === 'json' ? jdata : xhr.responseText), xhr.status, xhr);
}
}
}
};
xhr.open(method, url, async);
// set headers if present
if ( headers ) {
for ( hname in headers ) {
if ( headers.hasOwnProperty(hname) ) {
xhr.setRequestHeader(hname, headers[hname]);
}
}
}
// abort after some time (60s)
timeout = setTimeout(function () {
echo('ABORT on timeout', title);
if ( typeof callback === 'function' ) {
callback(null, 0);
// no readystatechange event should be dispatched after xhr.abort() (https://xhr.spec.whatwg.org/#dom-xmlhttprequest-abort)
// so we need to fix this bug and prevent multiple call of same callback for our stb
aborted = true;
}
xhr.abort();
}, abortTimeout);
xhr.send();
echo('sent', title);
return xhr;
}
/**
* Preloads a list of images and executes the given callback at the end
* @param {Array} imgList images to load
* @param {Function} [callback]
*/
function imageLoader ( imgList, callback ) {
var count = 1, // loading image number
onload = function () {
// all images are loaded and there is a valid callback
if ( imgList.length === count && typeof callback === 'function' ) {
callback();
} else {
count++;
}
};
// create set of loading images
if ( Array.isArray(imgList) && imgList.length > 0 ) {
imgList.forEach(function ( item ) {
var img = new Image();
img.src = item;
img.onload = onload;
img.onerror = function(){
throw ('Image ' + item + ' load fail');
};
});
} else if ( imgList.length === 0 ) {
// nothing was given so fire at once
callback();
}
}
/**
* get the list of all storages (external usb and internal hdd)
*/
function getStorageInfo () {
var snList = {}; // set of all serial numbers with amount of partitions on each
// get mount points
var info = JSON.parse(gSTB.GetStorageInfo('{}'));
// valid non-empty data
if ( Array.isArray(info.result) && info.errMsg === '' && info.result.length > 0 ) {
info.result.forEach(function ( item ) {
// SD card-reader support
item.mediaType = item.sn === '000022272228' ? 3 : item.mediaType;
item.label = item.label.trim();
if ( snList[item.sn] ) {
snList[item.sn]++;
} else {
snList[item.sn] = 1;
}
});
info.result.forEach(function ( item ) {
if ( !item.label ) {
item.label = item.vendor + ' ' + item.model.replace(/\//, '');
if ( snList[item.sn] > 1 ) {
item.label += ' #' + item.partitionNum;
}
}
if ( item.isReadOnly ) {
item.label += ' (' + _('Read only') + ')';
}
});
// sort by mount path
info.result.sort(function ( a, b ) {
return a.mountPath < b.mountPath ? -1 : 1;
});
// final list of all combined data
window.STORAGE_INFO = info.result;
} else {
// reset if error
window.STORAGE_INFO = [];
}
// get hdd data
try {
window.HDD_INFO = JSON.parse(gSTB.RDir('get_hdd_info') || '[]');
} catch ( e ) {
echo(e, 'get_hdd_info');
window.HDD_INFO = [];
}
echo(STORAGE_INFO, 'STORAGE_INFO');
echo(HDD_INFO, 'HDD_INFO');
}
/**
* URL parsing tool
* (c) Steven Levithan <stevenlevithan.com>
* MIT License
*/
function parseUri ( str ) {
var o = parseUri.options,
m = o.parser[o.strictMode ? 'strict' : 'loose'].exec(str),
uri = {},
i = 14;
while ( i-- ) { uri[o.key[i]] = m[i] || ''; }
uri[o.q.name] = {};
uri[o.key[12]].replace(o.q.parser, function ( $0, $1, $2 ) {
if ( $1 ) { uri[o.q.name][$1] = $2; }
});
return uri;
}
parseUri.options = {
strictMode: false,
key : ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'],
q : {
name : 'queryKey',
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
parser : {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose : /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}
};
/**
* Validate whether the passed string is valid URL.
*
* @param {string} url - URL for validate
* @param {boolean} [useSolution=false] - may solution be appeared in URL
* @param {boolean} [isSolutionOptional=false] - is solution optional or not
* @param {boolean} [isProtocolOptional=true] - is protocol scheme optional or not
*
* @return {boolean} validation result
*/
function validateUrl ( url, useSolution, isSolutionOptional, isProtocolOptional ) {
var allowedSolutions = '((ffmpeg|auto|rtp|rtsp|mp3|mpegps|mpegts|mp4|ifm|fm|ffrt|ffrt2|ffrt3|ffrt4)(\\s))',
allowedProtocols = '(http|https|udp|srt|rtp|rtsp|mms|mmsh|mmst|rtmp|igmp)';
useSolution = typeof useSolution === 'boolean' ? useSolution : false;
isSolutionOptional = typeof isSolutionOptional === 'boolean' ? isSolutionOptional : false;
isProtocolOptional = typeof isProtocolOptional === 'boolean' ? isProtocolOptional : true;
return new RegExp(
'^' + (useSolution ? allowedSolutions : '') + (isSolutionOptional ? '?' : '') +
'((' + allowedProtocols + ':\\/\\/)' + (isProtocolOptional ? '?' : '') +
"@?(([a-zA-Z0-9\\u0400-\\u04FF$\\-_.+!*'(),;:&=]|%[0-9a-fA-F\\u0400-\\u04FF]{2})+@)?(((25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9]|[1-9][0-9]|[0-9])(\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9][0-9]|[1-9][0-9]|[0-9])){3})|localhost|([a-zA-Z\\u0400-\\u04FF0-9\\-\\u00C0-\\u017F]+\\.)+([a-zA-Z\\u0400-\\u04FF]{2,}))(:[0-9]+)?|((file:\/\/\/|\/)[a-zA-Z\\u0400-\\u04FF0-9\\-\\u00C0-\\u017F]+))(\\/(([a-zA-Z\\u0400-\\u04FF0-9$\\-_.+!*'(),;:@&=\\s]|%[0-9a-fA-F\\u0400-\\u04FF]{2})*(\\/([a-zA-Z\\u0400-\\u04FF0-9$\\-_.+!*'(),;:@&=\\s]|%[0-9a-fA-F\\u0400-\\u04FF]{2})*)*)?(\\?([a-zA-Z\\u0400-\\u04FF0-9$\\-_.+!*'(),;:@&=\\/?]|%[0-9a-fA-F\\u0400-\\u04FF]{2})*)?(\\#([a-zA-Z\\u0400-\\u04FF0-9$\\-_.+!*'(),;:@&=\\/?]|%[0-9a-fA-F\\u0400-\\u04FF]{2})*)?)?$"
).test(url);
}
/**
* Combines two objects and write result to target object
* @param {Object} target object to which the data will be transferred
* @param {Object} source object from which the data will be transferred
* @param [override = true] if set to false target object not rewriting, result of combining returns from function
* @returns {Object} combined object
*/
function extend (target, source, override) {
var _target = (override === false ? extend({}, target) : target || {});
for (var prop in source) {
if ( typeof _target[prop] === 'object' && typeof source[prop] === 'object' && !Array.isArray(_target[prop]) && !Array.isArray(source[prop]) ) {
_target[prop] = extend(_target[prop], source[prop], override);
} else {
_target[prop] = source[prop];
}
}
return _target;
}
/**
* Converts the given string to utf8 and then to base64
* @param {string} data string to convert
* @return {string}
*/
function base64Encode ( data ) {
return btoa(unescape(encodeURIComponent(data)));
}
/**
* Converts the given string from base64 to utf8 and then to utf16
* @param {string} data string to convert
* @return {string}
*/
function base64Decode ( data ) {
try {
return decodeURIComponent(escape(atob(data)));
} catch(error) {
echo(error, 'base64Decode Error');
}
}
if ( !('classList' in document.documentElement) ) {
var prototype = Array.prototype,
indexOf = prototype.indexOf,
slice = prototype.slice,
push = prototype.push,
splice = prototype.splice,
join = prototype.join;
window.DOMTokenList = function(el) {
this._element = el;
if (el.className !== this._classCache) {
this._classCache = el.className;
if (!this._classCache) { return; }
var classes = this._classCache.replace(/^\s+|\s+$/g,'').split(/\s+/),
i;
for (i = 0; i < classes.length; i++) {
push.call(this, classes[i]);
}
}
};
window.DOMTokenList.prototype = {
add: function(token) {
if(this.contains(token)) {
return;
}
push.call(this, token);
this._element.className = slice.call(this, 0).join(' ');
},
contains: function(token) {
return indexOf.call(this, token) !== -1;
},
item: function(index) {
return this[index] || null;
},
remove: function(token) {
var i = indexOf.call(this, token);
if (i === -1) {
return;
}
splice.call(this, i, 1);
this._element.className = slice.call(this, 0).join(' ');
},
toString: function() {
return join.call(this, ' ');
},
toggle: function(token) {
if (!this.contains(token)) {
this.add(token);
} else {
this.remove(token);
}
return this.contains(token);
}
};
Object.defineProperty(Element.prototype, 'classList',{
get: function () {
return new window.DOMTokenList(this);
}
});
}
/**
* Object represents simple event model
* @type {{ bind: Function, trigger: Function, inject: Function}}
*/
var Events = {
/**
* Assign new event to the current object
* @param {string|Object} event Event name or Object where the key is event name and value is handler
* @param {Function} callback Function that will be executed when event was triggered
*/
bind: function ( event, callback ) {
this._events || (this._events = {});
if ( typeof event === 'object' ) {
for ( var name in event ) {
this.bind(name, event[name]);
}
} else if ( typeof event === 'string' && typeof callback === 'function' ) {
if ( this._events[event] === undefined ) {
this._events[event] = [];
}
this._events[event].push(callback);
}
},
/**
* Trigger some event
* @param {string} event Name of events which will be triggered
*/
trigger: function ( event, data ) {
var result, results = [], self = this;
if ( event !== undefined && this._events !== undefined && this._events[event] !== undefined ) {
this._events[event].forEach(function ( ev ) {
result = ev.call(self, data);
if ( result !== undefined ) { results.push(result); }
}
);
}
return results;
},
/**
* Remove event handlers for specified event
* @param {string} event Name of removed event
*/
unbind: function(event){
delete this._events[event];
},
/**
* Inject current functionality to another object or function
* @param {Object|Function} obj Object which is embedded functionality
*/
inject: function( obj ){
if (typeof obj === 'function'){
extend(obj.prototype, this);
}else if (typeof obj === 'object'){
extend(obj, this);
}
}
};
/**
* Cuts to the specified number of chars and append an ellipsis
* @param {string} text String to cut
* @param {number} length The length to which you want to truncate the string
* @returns {string}
*/
function cutTextWithEllipsis ( text, length ) {
if ( text.length > length ) {
return text.toString().slice(0, length) + '...';
}
return text;
}
/**
* Parse date string and return date without time zone
* @param {string} dateStr date string in EST or UTC format
*/
var timeZoneOffsets = {'A':{'index':-1,'minutes':60},'ACDT':{'index':-1,'minutes':60},'ACST':{'index':-1,'minutes':570},
'ACT':{'index':1,'minutes':300},'ACWST':{'index':-1,'minutes':525},'ADT':{'index':1,'minutes':180},
'AEDT':{'index':-1,'minutes':61},'AEST':{'index':-1,'minutes':60},'AFT':{'index':-1,'minutes':270},
'AKDT':{'index':1,'minutes':480},'AKST':{'index':1,'minutes':540},'ALMT':{'index':-1,'minutes':360},
'AMST':{'index':1,'minutes':180},'AMT':{'index':1,'minutes':240},'ANAST':{'index':-1,'minutes':62},
'ANAT':{'index':-1,'minutes':62},'AQTT':{'index':-1,'minutes':300},'ART':{'index':1,'minutes':180},
'AST':{'index':1,'minutes':240},'AWDT':{'index':-1,'minutes':540},'AWST':{'index':-1,'minutes':480},
'AZOST':{'index':0,'minutes':0},'AZOT':{'index':1,'minutes':60},'AZST':{'index':-1,'minutes':300},
'AZT':{'index':-1,'minutes':240},'B':{'index':-1,'minutes':120},'BNT':{'index':-1,'minutes':480},
'BOT':{'index':1,'minutes':240},'BRST':{'index':1,'minutes':120},'BRT':{'index':1,'minutes':180},
'BST':{'index':-1,'minutes':60},'BTT':{'index':-1,'minutes':360},'C':{'index':-1,'minutes':180},
'CAST':{'index':-1,'minutes':480},'CAT':{'index':-1,'minutes':120},'CCT':{'index':-1,'minutes':390},
'CDT':{'index':1,'minutes':300},'CEST':{'index':-1,'minutes':120},'CET':{'index':-1,'minutes':60},
'CHADT':{'index':-1,'minutes':63},'CHAST':{'index':-1,'minutes':62},'CHOT':{'index':-1,'minutes':480},
'CHUT':{'index':-1,'minutes':600},'CKT':{'index':1,'minutes':60},'CLST':{'index':1,'minutes':180},
'CLT':{'index':1,'minutes':240},'COT':{'index':1,'minutes':300},'CST':{'index':1,'minutes':360},
'CVT':{'index':1,'minutes':60},'CXT':{'index':-1,'minutes':420},'ChST':{'index':-1,'minutes':60},
'D':{'index':-1,'minutes':240},'DAVT':{'index':-1,'minutes':420},'E':{'index':-1,'minutes':300},
'EASST':{'index':1,'minutes':300},'EAST':{'index':1,'minutes':360},'EAT':{'index':-1,'minutes':180},
'ECT':{'index':1,'minutes':300},'EDT':{'index':-1,'minutes':61},'EEST':{'index':-1,'minutes':180},
'EET':{'index':-1,'minutes':120},'EGST':{'index':0,'minutes':0},'EGT':{'index':1,'minutes':60},
'EST':{'index':1,'minutes':300},'ET':{'index':1,'minutes':300},'F':{'index':-1,'minutes':360},
'FET':{'index':-1,'minutes':180},'FJST':{'index':-1,'minutes':63},'FJT':{'index':-1,'minutes':62},
'FKST':{'index':1,'minutes':180},'FKT':{'index':1,'minutes':240},'FNT':{'index':1,'minutes':120},
'G':{'index':-1,'minutes':420},'GALT':{'index':1,'minutes':360},'GAMT':{'index':1,'minutes':540},
'GET':{'index':-1,'minutes':240},'GFT':{'index':1,'minutes':180},'GILT':{'index':-1,'minutes':62},
'GMT':{'index':0,'minutes':0},'GST':{'index':-1,'minutes':240},'GYT':{'index':1,'minutes':240},
'H':{'index':-1,'minutes':480},'HAA':{'index':1,'minutes':180},'HAC':{'index':1,'minutes':300},
'HADT':{'index':1,'minutes':540},'HAE':{'index':1,'minutes':240},'HAP':{'index':1,'minutes':420},
'HAR':{'index':1,'minutes':360},'HAST':{'index':1,'minutes':60},'HAT':{'index':1,'minutes':150},
'HAY':{'index':1,'minutes':480},'HKT':{'index':-1,'minutes':480},'HLV':{'index':1,'minutes':270},
'HNA':{'index':1,'minutes':240},'HNC':{'index':1,'minutes':360},'HNE':{'index':1,'minutes':300},
'HNP':{'index':1,'minutes':480},'HNR':{'index':1,'minutes':420},'HNT':{'index':1,'minutes':210},
'HNY':{'index':1,'minutes':540},'HOVT':{'index':-1,'minutes':420},'I':{'index':-1,'minutes':540},
'ICT':{'index':-1,'minutes':420},'IDT':{'index':-1,'minutes':180},'IOT':{'index':-1,'minutes':360},
'IRDT':{'index':-1,'minutes':270},'IRKST':{'index':-1,'minutes':540},'IRKT':{'index':-1,'minutes':540},
'IRST':{'index':-1,'minutes':210},'IST':{'index':-1,'minutes':60},'JST':{'index':-1,'minutes':540},
'K':{'index':-1,'minutes':60},'KGT':{'index':-1,'minutes':360},'KOST':{'index':-1,'minutes':660},
'KRAST':{'index':-1,'minutes':480},'KRAT':{'index':-1,'minutes':480},'KST':{'index':-1,'minutes':540},
'KUYT':{'index':-1,'minutes':240},'L':{'index':-1,'minutes':61},'LHDT':{'index':-1,'minutes':61},
'LHST':{'index':-1,'minutes':60},'LINT':{'index':-1,'minutes':64},'M':{'index':-1,'minutes':62},
'MAGST':{'index':-1,'minutes':62},'MAGT':{'index':-1,'minutes':62},'MART':{'index':1,'minutes':570},
'MAWT':{'index':-1,'minutes':300},'MDT':{'index':1,'minutes':360},'MESZ':{'index':-1,'minutes':120},
'MEZ':{'index':-1,'minutes':60},'MHT':{'index':-1,'minutes':62},'MMT':{'index':-1,'minutes':390},
'MSD':{'index':-1,'minutes':240},'MSK':{'index':-1,'minutes':240},'MST':{'index':1,'minutes':420},
'MUT':{'index':-1,'minutes':240},'MVT':{'index':-1,'minutes':300},'MYT':{'index':-1,'minutes':480},
'N':{'index':1,'minutes':60},'NCT':{'index':-1,'minutes':61},'NDT':{'index':1,'minutes':150},
'NFT':{'index':-1,'minutes':61},'NOVST':{'index':-1,'minutes':420},'NOVT':{'index':-1,'minutes':360},
'NPT':{'index':-1,'minutes':345},'NRT':{'index':-1,'minutes':720},'NST':{'index':1,'minutes':210},
'NUT':{'index':1,'minutes':61},'NZDT':{'index':-1,'minutes':63},'NZST':{'index':-1,'minutes':62},
'O':{'index':1,'minutes':120},'OMSST':{'index':-1,'minutes':420},'OMST':{'index':-1,'minutes':420},
'ORAT':{'index':-1,'minutes':300},'P':{'index':1,'minutes':180},'PDT':{'index':1,'minutes':420},
'PET':{'index':1,'minutes':300},'PETST':{'index':-1,'minutes':62},'PETT':{'index':-1,'minutes':62},
'PGT':{'index':-1,'minutes':60},'PHOT':{'index':-1,'minutes':63},'PHT':{'index':-1,'minutes':480},
'PKT':{'index':-1,'minutes':300},'PMDT':{'index':1,'minutes':120},'PMST':{'index':1,'minutes':180},
'PONT':{'index':-1,'minutes':61},'PST':{'index':1,'minutes':480},'PT':{'index':1,'minutes':480},
'PWT':{'index':-1,'minutes':540},'PYST':{'index':1,'minutes':180},'PYT':{'index':1,'minutes':240},
'Q':{'index':1,'minutes':240},'QYZT':{'index':1,'minutes':360},'R':{'index':1,'minutes':300},
'RET':{'index':-1,'minutes':240},'S':{'index':1,'minutes':360},'SAKT':{'index':1,'minutes':600},
'SAMT':{'index':-1,'minutes':240},'SAST':{'index':-1,'minutes':120},'SBT':{'index':-1,'minutes':61},
'SCT':{'index':-1,'minutes':240},'SGT':{'index':-1,'minutes':480},'SRET':{'index':-1,'minutes':660},
'SRT':{'index':1,'minutes':180},'SST':{'index':1,'minutes':61},'T':{'index':1,'minutes':420},
'TAHT':{'index':1,'minutes':60},'TFT':{'index':-1,'minutes':300},'TJT':{'index':-1,'minutes':300},
'TKT':{'index':-1,'minutes':63},'TLT':{'index':-1,'minutes':540},'TMT':{'index':-1,'minutes':300},
'TOT':{'index':-1,'minutes':780},'TVT':{'index':-1,'minutes':62},'U':{'index':1,'minutes':480},
'ULAT':{'index':-1,'minutes':480},'UTC':{'index':0,'minutes':0},'UYST':{'index':1,'minutes':120},
'UYT':{'index':1,'minutes':180},'UZT':{'index':-1,'minutes':300},'V':{'index':1,'minutes':540},
'VET':{'index':1,'minutes':270},'VLAST':{'index':-1,'minutes':61},'VLAT':{'index':-1,'minutes':61},
'VUT':{'index':-1,'minutes':61},'W':{'index':1,'minutes':60},'WARST':{'index':1,'minutes':180},
'WAST':{'index':-1,'minutes':120},'WAT':{'index':-1,'minutes':60},'WEST':{'index':-1,'minutes':60},
'WESZ':{'index':-1,'minutes':60},'WET':{'index':0,'minutes':0},'WEZ':{'index':0,'minutes':0},
'WFT':{'index':-1,'minutes':62},'WGST':{'index':1,'minutes':120},'WGT':{'index':1,'minutes':180},
'WIB':{'index':-1,'minutes':420},'WIT':{'index':-1,'minutes':540},'WITA':{'index':-1,'minutes':480},
'WST':{'index':-1,'minutes':63},'WT':{'index':0,'minutes':0},'X':{'index':1,'minutes':61},
'Y':{'index':1,'minutes':62},'YAKST':{'index':-1,'minutes':60},'YAKT':{'index':-1,'minutes':60},
'YAPT':{'index':-1,'minutes':60},'YEKST':{'index':-1,'minutes':360},'YEKT':{'index':-1,'minutes':360},
'Z':{'index':0,'minutes':0},'-12':{'index':-1,'minutes':720},'-11':{'index':-1,'minutes':660},
'-10':{'index':-1,'minutes':600},'-09':{'index':-1,'minutes':540},'-08':{'index':-1,'minutes':480},
'-07':{'index':-1,'minutes':420},'-06':{'index':-1,'minutes':360},'-05':{'index':-1,'minutes':300},
'-04':{'index':-1,'minutes':240},'-03':{'index':-1,'minutes':180},'-02':{'index':-1,'minutes':120},
'-01':{'index':-1,'minutes':60},'+01':{'index':1,'minutes':60},'+02':{'index':1,'minutes':120},
'+03':{'index':1,'minutes':180},'+04':{'index':1,'minutes':240},'+05':{'index':1,'minutes':300},
'+06':{'index':1,'minutes':360},'+07':{'index':1,'minutes':420},'+08':{'index':1,'minutes':480},
'+09':{'index':1,'minutes':540},'+10':{'index':1,'minutes':600},'+11':{'index':1,'minutes':660},
'+12':{'index':1,'minutes':720}};
/**
* Get a Date object from given string
* @param {string} dateStr String which contains date
* @return {Date} Object if dateStr is normal date, else return Invalid Date object
* TODO: if each incoming dateStr will be in the format such as 'Thu Nov 13 18:23:05 EET 2014',
* where EET - is timezone, and there is so much this function calls,
* parsing dateStr can be a little faster with using split lines on the ' ' to array,
* where 4 index is timezone
*/
function parseDate ( dateStr ) {
if ( !dateStr ) { return new Date(NaN);}
var date = new Date(dateStr.match(/\w{3}\s\w{3}\s\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}/) + ' ' + dateStr.match(/\d{4}/)),
timeZone = dateStr.match(/(\w{1,4}|((\+|-)\d{1,2}))\s+\d{4}/)[1],
offset = timeZoneOffsets[timeZone];
date.setMinutes(date.getMinutes() - (date.getTimezoneOffset())); // remove influence of device local time zone
if ( offset.index !== 0 ) {
date.setMinutes(date.getMinutes() + (offset.minutes || 0) * offset.index);
}
return date;
}
function getMediaType ( file ) {
var extension = file.split('.').pop().toLowerCase();
if ( extension && configuration.registersTypes.indexOf(extension) !== -1 ) {
for ( var i = 0, type; i < MEDIA_TYPES.length, type = MEDIA_TYPES[i]; i++ ) {
if ( MEDIA_EXTENSION[type].indexOf(extension) !== -1 ) { return type; }
}
}
return MEDIA_TYPE_NONE;
}
/**
* Generic randomize function
*/
Array.prototype.shuffle = function () {
var n = this.length,
i, tmp;
while ( n-- ) {
i = Math.floor(n * Math.random());
tmp = this[i];
this[i] = this[n];
this[n] = tmp;
}
return this;
};
function readJSON ( path, fileName ) {
var result,
xmlhttp = new XMLHttpRequest();
path = path + '/' + fileName;
try {
xmlhttp.open('GET', path, false);
xmlhttp.send(null);
result = xmlhttp.responseText;
} catch ( e ) {
echo(e, 'some xmlhttp error');
result = '';
}
echo(result, 'file ' + path);
return result;
}

353
mag/mini/system/update.js Normal file
View File

@ -0,0 +1,353 @@
'use strict';
/**
* List of fatal error statuses
*/
Update.CrashStatuses = [1, 2, 3, 4, 7, 17, 19, 20, 22, 25, 27/*, 28, 29*/];
/**
* Ready to update status
* @type {number}
*/
Update.OkStatus = 21;
/**
* Text representations of update statuses
*/
Update.StatusMessages = {
1: "Signature init error",
2: "Wrong device model",
3: "Section size exceeds partition size on FLASH",
4: "Required FLASH section not found. Aborting update",
5: "Updating kernel",
6: "Updating image",
7: "Internal error",
8: "Inspecting firmware",
9: "Updating environment variables",
10: "Updating Bootstrap section",
11: "Skipping Bootstrap section",
12: "Updating User FS section",
13: "Skipping User FS section",
14: "Updating second boot",
15: "Updating logotype",
16: "Update finished OK",
17: "Wrong signature",
18: "Erasing flash section",
19: "Flash write error",
20: "File write error",
21: "Idle",
22: "Invalid file header",
23: "Inspecting update file",
24: "File check finished",
25: "File not found",
26: "Initialising",
27: "Read error"/*,
28: "Loader error",
29: "Storage not ready"*/
};
/**
* Component that is responsible for updating the device
* @param {Object} events List of event handlers (existing events: onCheck, onProgress, onError, onStart, onReady)
* @constructor
*/
function Update ( events ) {
this.bind(events);
this.check_url = this.update_url = '';
this.checked = false;
}
/**
* Update device by TFTP
* @param {number} [timeout] delay before start updating
* @returns {boolean} return true if validation is passed
*/
Update.StartTFTP = function ( timeout ) {
var bootstrap_url = parseUri(Update.DefaultUpdateUrls.bootstrap_url),
lan_params = ["ipaddr", "gatewayip", "dnsip", "netmask", "ipaddr_conf"],
lan_config = getEnvList(lan_params),
type = "up_dhcp_tftp",
count = 0;
// check lan configuration type
if ( lan_config.ipaddr_conf && lan_params.ipaddr_conf !== "0.0.0.0" ) { // manual configuration
for ( var i = 0; i < lan_params.length; i++ ) { // check full lan manual configuration
if ( lan_config[lan_params[i]] ) {
count++;
}
}
if ( count === lan_params.length ) { // manualy lan configuration
type = "up_ip_tftp";
} else {
return false;
}
}
if ( !this.CheckUpdateByTFTP() ) {
return false;
}
// if all alright start updating
if ( setEnvList({
serverip_conf: bootstrap_url.host,
tftp_path_conf: bootstrap_url.relative.substr(1) // remove first "/" in path needed for tftp
}) ) {
setTimeout(function () {
gSTB.ExecAction("UpdateSW " + type);
}, timeout || 0);
}
return true;
};
/**
* Checks whether updated via tftp
* @return {boolean}
*/
Update.CheckUpdateByTFTP = function () {
var update_url = parseUri(Update.DefaultUpdateUrls.update_url),
ip_regexp = /^((?:[0-9]{1,3}\.){3}[0-9]{1,3})$/, // for ip 192.168.1.221
bootstrap_regexp = /^(tftp):\/\/((?:[0-9]{1,3}\.){3}[0-9]{1,3})(:[0-9]+)?(\/.+)+$/; // for url tftp://192.168.1.221/some/path
// validate update_url
if ( ["igmp", "tftp", "http"].indexOf(update_url.protocol) !== -1 ) { // check protocol fo update_url
if ( update_url.protocol === "igmp" && update_url.relative !== '' ) { // check realtive path for igmp
return false;
}
if ( update_url.protocol === "tftp" || update_url.protocol === "igmp" ) { // check if host is valid ip for tftp and igmp protocols
if ( !update_url.host.match(ip_regexp) ) { return false; }
}
} else {
return false;
}
if ( gSTB.GetDeviceModelExt().substr(0, 4) === "Aura" ) { return false; }
// validate bootstrap_url
return !!Update.DefaultUpdateUrls.bootstrap_url.match(bootstrap_regexp);
};
/**
* Update device by multicast
* @param {number} [timeout] delay before start updating
* @returns {boolean} return true if validation is passed
*/
Update.StartMulticast = function ( timeout ) {
var bootstrap_url = parseUri(Update.DefaultUpdateUrls.bootstrap_url);
if ( this.CheckUpdateByMulticast ) { // validate update_url and bootstrap_url
// if all data valid start updating
if ( setEnvList({mcip_conf: bootstrap_url.host, mcport_conf: bootstrap_url.port}) ) {
setTimeout(function () { gSTB.ExecAction("UpdateSW up_mc_url"); }, timeout || 0);
return true;
}
}
return false;
};
/**
* Checks whether updated via multicast
* @returns {boolean}
*/
Update.CheckUpdateByMulticast = function () {
var regexp = /^(igmp):\/\/((?:[0-9]{1,3}\.){3}[0-9]{1,3})(:[0-9]+)?$/; // for url like igmp://192.168.1.221:444
if ( !configuration.allowMulticastUpdate ) { return false; }
//if ( gSTB.GetDeviceModelExt().substr(0, 4) === "Aura" ) { return false; }
return (!!Update.DefaultUpdateUrls.bootstrap_url.match(regexp) && !!Update.DefaultUpdateUrls.update_url.match(regexp));
};
Update.DefaultUpdateUrls = getEnvList(['update_url', 'bootstrap_url']);
/**
* Start update by http or from USB
* @param {string} update_url valid path to the update image
* @param {Boolean} force force update start
*/
Update.prototype.Start = function ( update_url, force ) {
var status = stbUpdate.getStatus(), // get current update status
activeBank = stbUpdate.getActiveBank(), // get active NAND number
modelTemp = gSTB.RDir("Model").toUpperCase(), // get device model
realActiveBank; // check if active bank corrupted and we loaded in emergency mode
echo('Update.Start(update_url=' + update_url + ', force=' + force + ');');
if ( (status == Update.OkStatus || force) && this.update_url === update_url ) { // check status
echo('Update.Start(); => ready for update. Status:' + status + ', by force? ' + force);
this.trigger("onStart"); // trigger onStart event
echo('Update.Start(); => after onStart');
this.SystemButtons(false);
if ( modelTemp !== 'MAG250' && modelTemp !== 'MAG270' && modelTemp !== 'MAG275' ) { // check device model
if ( activeBank != -1 && stbUpdate.GetFlashBankCount() != 2 ) { // check memory banks on old devices
echo('Update.Start(); => trigger("onError") : bank error');
this.trigger("onError", { // trigger onError event
errorMessage: _('Unable to update active memory bank'),
logMessage: 'Unable to update active memory bank',
status: 30
});
return;
}
}
if ( realActiveBank = stbStorage.getItem('nandEmergencyLoadingLogs') ) {
realActiveBank = (JSON.parse(realActiveBank) || '').bootmedia;
if ( realActiveBank === 'bank0' ) { activeBank = 0; }
if ( realActiveBank === 'bank1' ) { activeBank = 1; }
}
echo('Update.Start(); => start stbUpdate.startUpdate for bank ' + (activeBank == 0 ? 1 : 0));
if ( activeBank == 0 ) { // write to non active nand
stbUpdate.startUpdate(1, update_url);
} else {
stbUpdate.startUpdate(0, update_url);
}
} else {
echo('Update.Start(); => not ready for update. Status:' + status + ', is forced? ' + force + ', url is wrong? ' + (this.update_url !== update_url));
echo('this.update_url (' + this.update_url + ') === update_url (' + update_url + ')');
this.CheckUpdate(update_url, true); // if image not checked yet do this
}
this.CheckProgress();
};
/**
* Called by timeout function which check update progress state
*/
Update.prototype.CheckProgress = function () {
var status = stbUpdate.getStatus(), // get current update status code
percent = stbUpdate.getPercents(),
self = this;
if ( !self.CheckError(status) ) return; // stop check progress if some error
echo('Update.CheckProgress() => auto start if ready? ' + this.auto_start);
if ( this.auto_start === true && status === Update.OkStatus ) { // if all ok and auto_start set in true start update after image check
this.auto_start = false; // set autostart to false to prevent two update starts
this.Start(this.update_url);
}
// fix percents - if update have been finished successfully percents value should be 100%
if ( status === 16 ) {
percent = 100;
}
echo('Update.CheckProgress() => set new progress value ' + stbUpdate.getPercents());
this.trigger("onProgress", { // trigger onProgress event
percent: percent,
statusMessage: percent === 100 ? _(Update.StatusMessages[16]) : _(Update.StatusMessages[status]),
logMessage: percent === 100 ? Update.StatusMessages[16] : Update.StatusMessages[status],
status: percent === 100 ? 16 : status // after finish box will send status 21:"idle" again. Hide it from user (anyway next step - box reload).
});
this.progress_timeout = setTimeout(function () {self.CheckProgress()}, 1000); // set timeout for new progress check
};
/**
* Check status of whether the error status
* @param {number} status current status code
* @return {boolean} false if this is error status and true is all ok
*/
Update.prototype.CheckError = function ( status ) {
echo('Update.CheckError(status); => status code: ' + status);
if ( Update.CrashStatuses.indexOf(status) !== -1 ) { // check status presence in CrashStatuses list
echo('Update.CheckError(status); => error status found!: ' + status);
this.trigger("onError", { // if given the error status then trigger onError event
errorMessage: _(Update.StatusMessages[status]),
logMessage: Update.StatusMessages[status],
status: status
});
this.SystemButtons(true);
return false;
}
return true;
};
/**
* Called by timeout function which check checking image state
* @param {string|null} update_url valid path to the update image (local or http://..), or null if check already started
* @param {boolean} [start_on_ready] specifies start update if returned Update.OkStatus
* @constructor
*/
Update.prototype.CheckUpdate = function ( update_url, start_on_ready ) {
var status, self;
echo('Update.CheckUpdate(update_url=' + update_url + ', start_on_ready=' + start_on_ready + ');');
if ( typeof update_url === "string" ) { // if update_url is string start check
stbUpdate.startCheck(this.check_url = update_url);
}
status = stbUpdate.getStatus(); // get current check status
self = this;
echo('Update.CheckUpdate(); => stop check if some error or got ready status. Current status: ' + status);
if ( !this.CheckError(status) ) return; // stop check if some error
this.trigger("onCheck", { // if all ok trigger onCheck event
statusMessage: _(Update.StatusMessages[status]),
logMessage: Update.StatusMessages[status],
status: status
});
if ( status === Update.OkStatus ) { // check all ok status
this.update_url = this.check_url;
this.trigger("onReady"); // if all ok trigger onReady event
echo('Update.CheckUpdate(); => check is ok. Start autoupdate? ' + start_on_ready);
if ( start_on_ready === true ) {
this.Start(this.update_url); // start updating if parameter start_on_ready set to true
}
return;
}
clearTimeout(this.check_update_timeout);
this.check_update_timeout = setTimeout(function () { // set timeout for new check
self.CheckUpdate(null, start_on_ready);
}, 100);
};
/**
* Return info about image by which it check
* Work only if current status is Update.OkStatus
* @returns {Object|null}
*/
Update.prototype.GetImageInfo = function () {
echo('Update.GetImageInfo(); => is info about new image ready? status:' + stbUpdate.getStatus());
if ( stbUpdate.getStatus() === Update.OkStatus ) {
echo('Update.GetImageInfo(); => {date:' + parseDate(stbUpdate.getImageDateStr()) + ', description:' + stbUpdate.getImageDescStr() + ', version:' + stbUpdate.getImageVersionStr() + '}');
return {
date: parseDate(stbUpdate.getImageDateStr()),
description: stbUpdate.getImageDescStr(),
version: stbUpdate.getImageVersionStr()
}
} else {
return null;
}
};
/**
* Return information about current image
* @returns {Object}
*/
Update.prototype.GetCurrentImageInfo = function () {
if ( this.curr_info === undefined ) {
var info = JSON.parse(gSTB.GetEnv(JSON.stringify({varList: ['Image_Desc', 'Image_Date', 'Image_Version']}))).result;
this.curr_info = {
date: parseDate(info.Image_Date),
description: info.Image_Desc,
version: info.Image_Version
}
}
/*
TODO: check this code on MAG352
var obj = {
date: new Date(NaN);
}
JSON.stringify(obj);
*/
// echo('Update.GetCurrentImageInfo(); => Return:' + JSON.stringify(this.curr_info));
return this.curr_info;
};
/**
* Brings Update to its original appearance
*/
Update.prototype.Clear = function () {
clearTimeout(this.check_update_timeout);
clearTimeout(this.progress_timeout);
this.auto_start = false;
this.update_url = '';
};
Update.prototype.SystemButtons = function ( enable ) {
gSTB.EnableServiceButton(enable);
gSTB.EnableVKButton(enable);
gSTB.EnableAppButton(enable);
gSTB.EnableTvButton(enable);
};
Events.inject(Update); // inject Events functionality

501
mag/mini/system/vlist.js Normal file
View File

@ -0,0 +1,501 @@
/**
* Virtual file list component
*
* @author Stanislav Kalashnik <sk@infomir.eu> Igor Zaporozhets <deadbyelpy@gmail.com>
*/
'use strict';
/**
* Base virtual list implementation.
* Based on list from stb framework. but event model, and scroll bar type have been removed
* @link https://github.com/DarkPark/stb/blob/master/app/js/ui/list.js
*
* @constructor
*
* @param {Object} [config={}] init parameters (all inherited from the parent)
* @param {Array} [config.data=[]] component data to visualize
* @param {number} [config.size=5] amount of visible items on a page
* @param {boolean} [config.scrollBar=null] ScrollBar
*/
function VList ( parent ) {
/**
* Link to the currently focused DOM element.
*
* @type {Element}
*/
this.activeItem = null;
/**
* Position of the visible window to render.
*
* @type {number}
*/
this.indexView = null;
/**
* Component data to visualize.
*
* @type {Array}
*/
this.data = [];
this.scrollBar = null;
/**
* Amount of visible items on a page.
*
* @type {number}
*/
this.size = 7;
this.states = {};
// set list size by screen height
switch ( screen.height ) {
case 480:
this.size = 5;
break;
case 576:
this.size = 5;
break;
case 720:
this.size = 7;
break;
case 1080:
this.size = 7;
break;
}
// parent init
CBase.call(this, parent);
}
// inheritance
VList.prototype = Object.create(CBase.prototype);
VList.prototype.constructor = VList;
/**
* Return items count.
*
* @deprecated created for other list compatibility
*
* @return {Number} total items count
*/
VList.prototype.Length = function () {
return this.data.length;
};
/**
* Fill the given item with data.
*
* @param {Element} $item item DOM link
* @param {*} attrs associated with this item data
*/
VList.prototype.renderItem = function ( $item, attrs ) {
$item.innerText = attrs.name;
};
/**
* Fill the given item with data.
*
* @param {Element} $item item DOM link
* @param {*} attrs associated with this item data
*/
VList.prototype.focus = function ( ) {
this.$body.focus();
};
/**
* Default method to move focus according to pressed keys.
*
* @param {Event} event generated event source of movement
*/
VList.prototype.EventHandler = function ( event ) {
switch ( event.code ) {
case KEYS.UP:
case KEYS.DOWN:
case KEYS.RIGHT:
case KEYS.LEFT:
case KEYS.PAGE_UP:
case KEYS.PAGE_DOWN:
case KEYS.HOME:
case KEYS.END:
// cursor move only on arrow keys
this.move(event.code);
break;
case KEYS.OK:
// there are some listeners
this.activeItem.onclick();
break;
}
event.preventDefault();
};
/**
* Initialize list UI.
*
* @param {Object} config config object
* @constructor
*/
VList.prototype.Init = function ( config ) {
var self = this,
onClick = false,
item, i;
// global store
if ( config.handle === undefined ) {
this.handleInner = this.$body = this.$node = document.createElement('div');
} else {
this.handleInner = this.$body = this.$node = config.handle;
}
// non-empty list
if (this.size > 0) {
// clear old items
this.$body.innerText = null;
}
if ( config.onClick !== undefined ) {
onClick = config.onClick;
}
this.$node.classList.add('vlist-main');
// create new items
for (i = 0; i < this.size; i++) {
item = document.createElement('a');
item.index = i;
if ( onClick ) {
item.onclick = onClick;
}
//item.addEventListener('click', onClick);
this.$body.appendChild(item);
}
// navigation by mouse
this.$body.addEventListener('mousewheel', function ( event ) {
// scrolling by Y axis
if ( event.wheelDeltaY ) {
self.move(event.wheelDeltaY > 0 ? KEYS.UP : KEYS.DOWN);
}
});
if ( config.scrollBar && this.scrollBar === null ) {
this.scrollBar = config.scrollBar;
this.scrollBar.init({viewSize: self.size, realSize: self.data.length});
}
};
/**
* Draw the visible window.
*
* @param {number} index start position to render
*
* @return {boolean} operation status
*
*/
VList.prototype.renderView = function ( index ) {
var $item, i, itemData, prevIndex, currIndex;
// has the view window position changed
if ( this.indexView !== index ) {
// sync global pointer
this.indexView = currIndex = index;
// rebuild all visible items
for ( i = 0; i < this.size; i++ ) {
// shortcuts
$item = this.$body.children[i];
itemData = this.data[index];
// real item or stub
if ( itemData !== undefined ) {
// correct inner data/index and render
$item.data = itemData;
$item.index = index;
this.renderItem($item, itemData);
// apply CSS
if ( itemData.markable ) {
$item.classList.add('mark');
} else {
$item.classList.remove('mark');
}
} else {
// nothing to render
$item.data = $item.index = undefined;
if ( $item.ready ) {
$item.$body.innerText = '';
$item.$body.style.background = '';
}
}
index++;
}
if ( this.scrollBar !== null ) {
this.scrollBar.scrollTo(this.indexView);
}
// full rebuild
return true;
}
// nothing was done
return false;
};
/**
* Move focus to the given direction.
*
* @param {number} direction arrow key code
*/
VList.prototype.move = function ( direction ) {
if ( direction === KEYS.UP ) {
// still can go backward
if ( this.activeItem && this.activeItem.index > 0 ) {
if ( this.activeItem === this.$body.firstChild ) {
this.renderView(this.indexView - 1);
} else {
this.focusItem(this.activeItem.previousSibling);
}
} else if ( this.onOverflow ) {
this.onOverflow({direction: direction});
}
}
if ( direction === KEYS.DOWN ) {
// still can go forward
if ( this.activeItem && this.activeItem.index < this.data.length - 1 ) {
if ( this.activeItem === this.$body.lastChild ) {
this.renderView(this.indexView + 1);
} else {
this.focusItem(this.activeItem.nextSibling);
}
} else if ( this.onOverflow ) {
this.onOverflow({direction: direction});
}
}
if ( direction === KEYS.PAGE_UP ) {
// determine jump size
if ( this.indexView < this.size ) {
// first page
this.renderView(0);
} else {
// second page and further
this.renderView(this.indexView - this.size + 1);
}
this.focusItem(this.$body.firstChild);
}
if ( direction === KEYS.PAGE_DOWN ) {
// data is bigger then one page
if ( this.data.length > this.size ) {
// determine jump size
if ( this.indexView > this.data.length - this.size * 2 ) {
// last page
this.renderView(this.data.length - this.size);
} else {
// before the last page
this.renderView(this.indexView + this.size - 1);
}
this.focusItem(this.$body.lastChild);
} else {
// not the last item on the page
this.focusItem(this.$body.children[this.data.length - 1]);
}
}
if ( direction === KEYS.HOME || direction === KEYS.LEFT ) {
this.renderView(0);
this.focusItem(this.$body.firstChild);
}
if ( direction === KEYS.END || direction === KEYS.RIGHT ) {
// data is bigger then one page
if ( this.data.length > this.size ) {
this.renderView(this.data.length - this.size);
this.focusItem(this.$body.lastChild);
} else {
// not the last item on the page
this.focusItem(this.$body.children[this.data.length - 1]);
}
}
};
/**
* Highlight the given DOM element as focused.
* Remove focus from the previously focused item and generate associated event.
*
* @param {Node|Element} $item element to focus
*
* @return {boolean} operation status
*/
VList.prototype.focusItem = function ( $item ) {
var $prev = this.activeItem;
// different element
if ( $item !== undefined && $prev !== $item ) {
// some item is focused already
if ( $prev !== null ) {
// style
$prev.classList.remove('focused');
}
// reassign
this.activeItem = $item;
this.activeItem.data = this.data[this.activeItem.index];
// correct CSS
$item.classList.add('focused');
return true;
}
// nothing was done
return false;
};
/**
* Create new item and put it in the list
* @param {string} name item label
* @param {Object} attrs set of item data parameters
* @param {Object} [states] set of additional parameters (stared)
*
* @return {Node|null}
*/
VList.prototype.Add = function ( name, attrs, states ) {
var prop;
attrs.name = name;
if ( states ) {
for ( prop in states ) {
attrs.prop = states[prop];
}
}
this.data.push(attrs);
};
/**
* Clear inner data.
* If u want update list, call this.renderView();
*/
VList.prototype.Clear = function () {
this.data = [];
};
/**
* Set inner item flags and decoration.
*
* @param {Object} item the element to be processed
* @param {string} option item inner flag name
* @param {boolean} state flag of the operation (true if change is made)
*
* @return {boolean} operation status
*/
VList.prototype.SetState = function ( item, option, state ) {
state = !!state;
// current and new states are different
if ( item[option] !== state ) {
// check if exist
if ( !this.states[option] ) { this.states[option] = []; }
var index = this.states[option].indexOf(item);
//update internal list
if ( state ) {
// add to the list
if ( index === -1 ) { this.states[option].push(item); }
} else {
// remove
if ( index !== -1 ) { this.states[option].splice(index, 1); }
}
// flag
item[option] = state;
return true;
}
// nothing has changed
return false;
};
/**
* Syntax sugar for render items from begin and focus first element.
*
*/
VList.prototype.Render = function () {
this.indexView = -1;
this.renderView(0);
this.focusItem(this.$body.firstChild);
};
/**
* Set item state and appearance as marked.
*
* @return {object} next data object
*/
VList.prototype.Next = function () {
return this.data[this.activeItem.index + 1];
};
/**
* Return active (focused) item of the list.
*
* @deprecated created for other list compatibility
*
* @return {HTMLElement}
*/
VList.prototype.Current = function () {
return this.activeItem;
};
/**
* Set position some list element
* @param {Object} data data item from inner items
* @param {boolean} [manageFocus] - set actual focus
*/
VList.prototype.SetPosition = function ( data, manageFocus ) {
var index = this.data.indexOf( data ),
i, viewIndex;
if ( index === -1 || index === undefined ) {
return false;
}
this.indexView = -1;
i = data.index || 0;
if ( i < this.size ) {
viewIndex = 0;
} else if ( i > this.data.length - this.size ) {
viewIndex = this.data.length - this.size;
i -= viewIndex;
}
this.renderView(viewIndex);
if ( manageFocus ) {
this.focusItem(this.$body.children[i]);
}
return true;
};
/**
* Go through all the items
*
* @deprecated created for other list compatibility
*
* @param {Function} callback iteration callback function
*/
VList.prototype.Each = function ( callback ) {
this.data.forEach(callback);
};

View File

@ -0,0 +1,208 @@
/**
* @author Stanislav Kalashnik <sk@infomir.eu> Igor Zaporozhets <deadbyelpy@gmail.com>
* @license GNU GENERAL PUBLIC LICENSE Version 3
*/
'use strict';
/**
* Virtual scroll bar implementation.
* Based on scroll bar from stb framework. but event model, and scroll bar type have been removed
* @link https://github.com/DarkPark/stb/blob/master/app/js/ui/scroll.bar.js
*
* @constructor
*
* @param {Object} [config={}] init parameters (all inherited from the parent)
* @param {number} [config.value=0] initial thumb position
* @param {number} [config.realSize=100] actual scroll size
* @param {number} [config.viewSize=10] visible area size
*
* @example
* var scrollBar = new VScrollBar({
* viewSize: 5,
* realSize: 25,
* value: 4
* });
*/
function VScrollBar ( config ) {
/**
* DOM outer handle.
*
* @type {Element}
*/
this.$node = null;
/**
* DOM inner handle.
* In simple cases is the same as $node.
*
* @type {Element}
*/
this.$body = null;
/**
* Visible area size.
*
* @type {number}
*/
this.viewSize = 10;
/**
* Scroll area actual height or width (if scroll is horizontal).
*
* @type {number}
*/
this.realSize = 100;
/**
* Scroll thumb position.
*
* @type {number}
*/
this.value = 0;
/**
* Geometry of the scroll thumb element.
*
* @type {ClientRect}
*/
this.thumbRect = null;
/**
* Geometry of the scroll track element.
*
* @type {ClientRect}
*/
this.trackRect = null;
// sanitize
config = config || {};
// outer handle
if ( config.$node !== undefined ) {
// apply
this.$node = config.$node;
} else {
// empty div in case nothing is given
this.$node = document.createElement('div');
}
// inner handle
if ( config.$body !== undefined ) {
// apply
this.$body = config.$body;
} else {
// inner and outer handlers are identical
this.$body = this.$node.appendChild(document.createElement('div'));
}
// correct CSS class names
this.$node.classList.add('scrollBar');
this.$body.classList.add('thumb');
// component setup
this.init(config);
}
/**
* Init or re-init realSize/viewSize/value parameters.
*
* @param {Object} config init parameters (subset of constructor config params)
*/
VScrollBar.prototype.init = function ( config ) {
config = config || {};
if ( DEBUG ) {
if ( arguments.length !== 1 ) { throw 'wrong arguments number'; }
if ( typeof config !== 'object' ) { throw 'wrong config type'; }
}
// set actual scroll size
if ( config.realSize !== undefined ) {
if ( DEBUG ) {
if ( Number(config.realSize) !== config.realSize ) { throw 'config.realSize value must be a number'; }
}
// apply
this.realSize = config.realSize;
}
// set visible area size
if ( config.viewSize !== undefined ) {
if ( DEBUG ) {
if ( Number(config.viewSize) !== config.viewSize ) { throw 'config.viewSize value must be a number'; }
if ( config.viewSize <= 0 ) { throw 'config.viewSize value must be greater than 0'; }
}
// apply
this.viewSize = config.viewSize;
}
// show or hide thumb
if ( this.viewSize >= this.realSize ) {
this.$body.classList.add('hidden');
} else {
this.$body.classList.remove('hidden');
}
// set thumb position
if ( config.value !== undefined ) {
// apply
this.scrollTo(config.value);
}
// set thumb size
this.$body.style.height = (this.viewSize / this.realSize * 100) + '%';
// geometry
this.thumbRect = this.$body.getBoundingClientRect();
this.trackRect = this.$node.getBoundingClientRect();
};
/**
* Set position of the given value.
* Does nothing in case when scroll is in the end and passed value is more than scroll bar length.
*
* @param {number} value new value to set
* @return {boolean} operation result
*
* @fires module:stb/ui/scroll.bar~VScrollBar#done
* @fires module:stb/ui/scroll.bar~VScrollBar#change
*/
VScrollBar.prototype.scrollTo = function ( value ) {
if ( DEBUG ) {
if ( arguments.length !== 1 ) { throw 'wrong arguments number'; }
if ( Number(value) !== value ) { throw 'value must be a number'; }
if ( this.realSize > this.viewSize && value > this.realSize - this.viewSize ) { throw 'value is greater than this.realSize-this.viewSize'; }
if ( value < 0 ) { throw 'value is less then 0'; }
}
// value has changed
if ( this.value !== value ) {
// track and thumb geometry was not set
if ( this.thumbRect.height === 0 || this.thumbRect.width === 0 ) {
// apply
this.trackRect = this.$node.getBoundingClientRect();
this.thumbRect = this.$body.getBoundingClientRect();
}
// set scroll bar width
this.$body.style.marginTop = ((this.trackRect.height - this.thumbRect.height) * value / (this.realSize - this.viewSize)) + 'px';
// is it the end?
if ( value >= this.realSize ) {
value = this.realSize;
}
// set new value
this.value = value;
return true;
}
// nothing was done
return false;
};

138
mag/mini/system/wamp.js Normal file
View File

@ -0,0 +1,138 @@
/**
* @author Stanislav Kalashnik <sk@infomir.eu>
* @license GNU GENERAL PUBLIC LICENSE Version 3
*/
'use strict';
/** @private */
var messageId = 0,
callbacks = {};
/**
* Lightweight WAMP implementation based on WebSockets.
*
* @param {WebSocket} socket link to socket connection to wrap
*
* @see http://wamp-proto.org/
* @constructor
*/
function Wamp ( socket ) {
var self = this;
// parent constructor call
Emitter.call(this);
this.socket = socket;
if ( 'on' in socket ) {
// server-side
socket.on('message', function ( message ) {
self.router(message);
});
} else if ( 'onmessage' in socket ) {
// desktop browser
socket.onmessage = function ( event ) {
self.router(event.data);
};
}
}
// inheritance
Wamp.prototype = Object.create(Emitter.prototype);
Wamp.prototype.constructor = Wamp;
/**
* Internal method to handle messages.
*
* @param {string} message JSON data
*
* @private
*/
Wamp.prototype.router = function ( message ) {
var self = this;
console.log('router');
console.log(message);
try {
message = JSON.parse(message);
} catch ( e ) {
this.socket.send(JSON.stringify({
jsonrpc: '2.0',
error: {code: -32700, message: 'Parse error'},
id: null
}));
return;
}
if ( 'id' in message && !('method' in message) ) {
// incoming answer for previous request
if ( message.id in callbacks ) {
callbacks[message.id](message.error, message.result);
delete callbacks[message.id];
} else {
// no callback registered for this id
}
} else if ( !('id' in message) && 'method' in message ) {
// incoming notification
if ( this.events[message.method] ) {
this.emit(message.method, message.params);
}
} else if ( 'id' in message && 'method' in message ) {
// execute incoming method and report to sender
if ( this.events[message.method] ) {
this.emit(message.method, message.params, function ( error, result ) {
self.socket.send(JSON.stringify({
jsonrpc: '2.0',
error: error,
result: result,
id: message.id
}));
});
} else {
// wrong method
this.socket.send(JSON.stringify({
jsonrpc: '2.0',
error: {code: -32601, message: 'Method not found'},
id: message.id
}));
}
} else {
// wrong request
this.socket.send(JSON.stringify({
jsonrpc: '2.0',
error: {code: -32600, message: 'Invalid Request'},
id: null
}));
}
};
/**
* Send message to execute remotely or notify (without `callback` argument).
*
* @param {string} method procedure or event name
* @param {*} [params] procedure associated data
* @param {function} [callback] remote call results handler
*/
Wamp.prototype.call = function ( method, params, callback ) {
var message = {
jsonrpc: '2.0',
method: method
};
if ( params ) {
message.params = params;
}
// execution mode with callback
// notification mode otherwise
if ( typeof callback === 'function' ) {
message.id = ++messageId;
callbacks[messageId] = callback;
}
console.log(JSON.stringify(message));
this.socket.send(JSON.stringify(message));
};

56
mag/mini/system/wm.js Normal file
View File

@ -0,0 +1,56 @@
'use strict';
var WINDOWS = {
PORTAL: 'portal',
HELP: 'help',
DOWNLOAD_MANAGER: 'dlman',
PVR: 'recordsManager',
BROWSER: 'ibman',
BROWSER_VIEW: 'ibmanView',
PORTALS_LOADER: 'portalsLoader',
SYSTEM_SETTINGS: 'systemSettings'
};
function getWindowKey ( windowName ) {
return 'window.' + windowName + '.id';
}
function getWindowIdByName ( windowName ) {
return stbStorage.getItem(getWindowKey(windowName));
}
function openWindow ( windowName, url ) {
var windowKey = getWindowKey(windowName), // get storage key
windowID = stbStorage.getItem(windowKey), // get window id from storage
windowInfo;
if ( windowID ) { // if window is opened
try {
windowInfo = JSON.parse(stbWindowMgr.windowInfo(windowID)); // get window info
if ( windowInfo.result.url.indexOf(url) === -1 ) {
// set black screen and reload
stbWebWindow.messageSend(windowID, 'window.load', url);
}
// browser window hasn't show method
if ( windowName === WINDOWS.BROWSER ) {
// window recreation
windowID = stbWindowMgr.openWebFace(url);
stbStorage.setItem(windowKey, windowID);
} else {
// create a new one
stbWindowMgr.windowShow(windowID);
}
} catch ( e ) {
echo(e);
stbWindowMgr.openWebFace(url);
}
} else {
// browser window has special init method
windowID = windowName === WINDOWS.BROWSER ? stbWindowMgr.openWebFace(url) : stbWindowMgr.windowInit(JSON.stringify({url:url, backgroundColor:'#000'}));
stbStorage.setItem(windowKey, windowID);
}
return windowID;
}
function openWindowHelp ( path ) {
openWindow(WINDOWS.HELP, PATH_ROOT + 'public/app/help/index.html?path=' + path);
}