321 lines
9.7 KiB
JavaScript
321 lines
9.7 KiB
JavaScript
modules.define(
|
||
'chitalka',
|
||
[
|
||
'y-block',
|
||
'jquery',
|
||
'inherit',
|
||
'y-extend',
|
||
'chitalka-ui',
|
||
'storage'
|
||
],
|
||
function (
|
||
provide,
|
||
YBlock,
|
||
$,
|
||
inherit,
|
||
extend,
|
||
ChitalkaUI,
|
||
Storage
|
||
) {
|
||
|
||
var doc = $(document);
|
||
|
||
var reportUnimplemented = function (method) {
|
||
throw new Error('UNIMPLEMENTED METHOD: ' + method);
|
||
};
|
||
|
||
/**
|
||
* Расширение объекта Math для вычисления медианы массива
|
||
*
|
||
* @param {Array} array
|
||
* @returns {Number} медиана
|
||
*/
|
||
Math.median = function (array) {
|
||
if (!array) {
|
||
return;
|
||
}
|
||
|
||
var entries = array.length;
|
||
var median;
|
||
|
||
if (entries % 2 === 0) {
|
||
median = (array[entries / 2] + array[entries / 2 - 1]) / 2;
|
||
} else {
|
||
median = array[(entries - 1) / 2];
|
||
}
|
||
|
||
return median;
|
||
};
|
||
|
||
/**
|
||
* Выбирает из массива массив медиан в заданном количестве
|
||
*
|
||
* @param {Array} array
|
||
* @param {Number} q количество
|
||
*
|
||
* @return {Array}
|
||
*/
|
||
var limitArrayByMedians = function (array, q) {
|
||
var result = [];
|
||
|
||
if (!Array.isArray(array)) {
|
||
return result;
|
||
}
|
||
if (array.length <= q) {
|
||
return array;
|
||
}
|
||
|
||
var median = Math.median(array);
|
||
var index = array.indexOf(median);
|
||
var start = Math.round(index - q / 2);
|
||
|
||
return array.splice(start, q);
|
||
};
|
||
|
||
/**
|
||
* Хэлпер для сортировки массивов чисел
|
||
*
|
||
* @param {Number} a
|
||
* @param {Number} b
|
||
* @returns {Number} 1 - a >=b, else -1
|
||
*/
|
||
var numSort = function (a, b) {
|
||
a = parseInt(a, 10);
|
||
b = parseInt(b, 10);
|
||
|
||
return a >= b ? 1 : -1;
|
||
};
|
||
|
||
var Chitalka = inherit(YBlock, {
|
||
__constructor: function () {
|
||
this.__base.apply(this, arguments);
|
||
|
||
var params = extend({
|
||
keyboard: false,
|
||
touch: false,
|
||
controls: false,
|
||
|
||
fontSize: [9, 21],
|
||
|
||
// Длина свайпа в пикселах
|
||
swipeLength: 20
|
||
}, this._getOptions());
|
||
|
||
this._defaultFontSize = 15;
|
||
this._settings = new Storage('settings');
|
||
|
||
// Если читалка не доступна, то кидаем событие и больше
|
||
// ничего не делаем
|
||
if (!this._isAvailable()) {
|
||
this.emit('unavailable');
|
||
|
||
return;
|
||
}
|
||
|
||
if (params.keyboard) {
|
||
this._initKeyboardEvents();
|
||
}
|
||
|
||
if (params.touch) {
|
||
this._initTouchEvents();
|
||
}
|
||
|
||
this._fontSizeLimits = params.fontSize;
|
||
|
||
this._setUpSpeed();
|
||
this._initUI();
|
||
},
|
||
|
||
/**
|
||
* Выставить скорость чтения книги
|
||
*/
|
||
_setUpSpeed: function () {
|
||
this._speed = Math.median(this._settings.get('speeds')) || 500;
|
||
},
|
||
|
||
_isAvailable: function () {
|
||
return false;
|
||
},
|
||
|
||
/**
|
||
* Активирует реакцию читалки на события с клавиатуры
|
||
*/
|
||
_initKeyboardEvents: function () {
|
||
this._bindTo(doc, 'keydown', this._onKeyDown);
|
||
},
|
||
|
||
/**
|
||
* Активирует реакцию читалки на события блока «Controls»
|
||
*/
|
||
_initUI: function () {
|
||
this._ui = ChitalkaUI.find(doc).init(this);
|
||
|
||
//var controls = Controls.find(this.getDomNode());
|
||
},
|
||
|
||
/**
|
||
* Активация обработки тач-событий (в частности события swipe)
|
||
* в функции выполняется навешивание соответствующих событий
|
||
*/
|
||
_initTouchEvents: function () {
|
||
},
|
||
|
||
_onKeyDown: function (e) {
|
||
switch (e.keyCode) {
|
||
// Fn + Right
|
||
case 35:
|
||
this.lastPage();
|
||
break;
|
||
|
||
// Fn + Left
|
||
case 36:
|
||
this.firstPage();
|
||
break;
|
||
|
||
// Left
|
||
case 37:
|
||
this.previousPage();
|
||
e.preventDefault();
|
||
break;
|
||
|
||
// Right
|
||
case 39:
|
||
this.nextPage();
|
||
e.preventDefault();
|
||
break;
|
||
|
||
// +
|
||
case 61:
|
||
case 187:
|
||
this.zoomIn();
|
||
|
||
if (e.metaKey) {
|
||
e.preventDefault();
|
||
}
|
||
break;
|
||
|
||
// -
|
||
case 173:
|
||
case 189:
|
||
this.zoomOut();
|
||
if (e.metaKey) {
|
||
e.preventDefault();
|
||
}
|
||
break;
|
||
|
||
// reset
|
||
case 48:
|
||
if (e.metaKey) {
|
||
this.zoomReset();
|
||
}
|
||
break;
|
||
}
|
||
},
|
||
|
||
/**
|
||
* События перемещения по книге
|
||
*/
|
||
firstPage: function () {
|
||
reportUnimplemented('firstPage');
|
||
},
|
||
previousPage: function () {
|
||
reportUnimplemented('previousPage');
|
||
},
|
||
nextPage: function () {
|
||
reportUnimplemented('nextPage');
|
||
},
|
||
lastPage: function () {
|
||
reportUnimplemented('lastPage');
|
||
},
|
||
|
||
/**
|
||
* Функция сохранения скорости в аккумулируемый объект
|
||
*
|
||
* @param {Number} speed
|
||
*/
|
||
_storeSpeed: function (speed) {
|
||
this._speedAccumulator = this._speedAccumulator || [];
|
||
|
||
this._speedAccumulator.push(speed);
|
||
|
||
this._speedAccumulator = this._speedAccumulator.sort(numSort);
|
||
},
|
||
|
||
/**
|
||
* Функция проверки скорости и её корректировки
|
||
* общий принцип работы:
|
||
* есть два массива
|
||
* this._speedAccumulator – аккумулирует чтение текущей книги
|
||
* speeds, который хранится в сторадже settings – хранит 10 меток скорости для пользователя
|
||
* метки – это медианы, которые всегда вычисляются из аккумулятора
|
||
* как только пользователь прочитывает 10 и более страниц, мы начинаем считать медиану и
|
||
* править speeds и класть туда новую скорость, вычисленную из аккумулятора
|
||
* При этом глобальная скорость чтения значительно изменится только если пользователь прочитает
|
||
* 15 страниц значительно быстрее/медленнее чем раньше.
|
||
* Во всех остальных случаях медиана поменяется совсем незначительно
|
||
*/
|
||
_checkSpeed: function () {
|
||
var speedEntries = this._speedAccumulator.length;
|
||
if (speedEntries > 10) {
|
||
this._speedAccumulator = this._speedAccumulator.sort(numSort);
|
||
|
||
var median = Math.median(this._speedAccumulator);
|
||
|
||
// Отсекаем совсем неадекватные скорости
|
||
if (median < 100000 && median > 10) {
|
||
this._speedAccumulator = this._speedAccumulator.sort(numSort);
|
||
if (!this._settings.get('speeds')) {
|
||
this._settings.save({
|
||
speeds: this._speedAccumulator
|
||
});
|
||
} else {
|
||
var speeds = limitArrayByMedians(this._settings.get('speeds'), 10);
|
||
|
||
if (speeds.length < 10) {
|
||
speeds.push(median);
|
||
} else {
|
||
if (median <= speeds[5]) {
|
||
speeds.pop();
|
||
speeds.unshift(median);
|
||
} else {
|
||
speeds.shift();
|
||
speeds.push(median);
|
||
}
|
||
}
|
||
speeds = speeds.sort(numSort);
|
||
|
||
this._settings.save({
|
||
speeds: speeds
|
||
});
|
||
}
|
||
this._speed = Math.median(this._settings.get('speeds'));
|
||
}
|
||
}
|
||
},
|
||
|
||
/**
|
||
* Вернуть текущую скорость чтения
|
||
* @returns {Number}
|
||
*/
|
||
getSpeed: function () {
|
||
return this._speed;
|
||
},
|
||
|
||
/**
|
||
* События зума книги
|
||
*/
|
||
zoomIn: function () {
|
||
reportUnimplemented('zoomIn');
|
||
},
|
||
zoomOut: function () {
|
||
reportUnimplemented('zoomOut');
|
||
},
|
||
zoomReset: function () {
|
||
reportUnimplemented('zoomReset');
|
||
}
|
||
|
||
});
|
||
|
||
provide(Chitalka);
|
||
});
|