umbot
Version:
Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф
471 lines (470 loc) • 18.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Navigation = void 0;
const utils_1 = require("../../utils");
/**
* Класс для навигации по элементам меню или списка.
* Предоставляет функциональность для:
* - Постраничной навигации по элементам
* - Поиска элементов по тексту или номеру
* - Управления отображением элементов
* - Обработки команд навигации
*
* @typeParam ElementType тип элементов, по которым будет происходить навигация
* @class Navigation
*
* @example
* ```ts
* // Создание экземпляра с максимальным количеством видимых элементов
* const navigation = new Navigation<{id: number, name: string}>(3);
*
* // Инициализация элементов
* const elements = [
* { id: 1, name: 'Элемент 1' },
* { id: 2, name: 'Элемент 2' },
* { id: 3, name: 'Элемент 3' },
* { id: 4, name: 'Элемент 4' }
* ];
*
* // Получение элементов текущей страницы
* const pageElements = navigation.getPageElements(elements, 'вперед');
*
* // Выбор элемента по тексту
* const selected = navigation.selectedElement(elements, 'Элемент 2', ['name']);
* ```
*/
class Navigation {
/**
* Список стандартных команд навигации вперед.
* Используется при isUsedStandardText = true
* @defaultValue ['дальше', 'вперед']
*/
STANDARD_NEXT_TEXT = ['дальше', 'вперед'];
/**
* Список стандартных команд навигации назад.
* Используется при isUsedStandardText = true
* @defaultValue ['назад']
*/
STANDARD_OLD_TEXT = ['назад'];
/**
* Флаг использования стандартных команд навигации.
* Если true, используются стандартные команды из STANDARD_NEXT_TEXT и STANDARD_OLD_TEXT
* @defaultValue true
*/
isUsedStandardText;
/**
* Массив с возможными командами для навигации вперед.
* Дополняет или заменяет стандартные команды в зависимости от isUsedStandardText
* @defaultValue []
* @example
* ```ts
* navigation.nextText = ['следующая', 'продолжить'];
* ```
*/
nextText;
/**
* Массив с возможными командами для навигации назад.
* Дополняет или заменяет стандартные команды в зависимости от isUsedStandardText
* @defaultValue []
* @example
* ```ts
* navigation.oldText = ['предыдущая', 'вернуться'];
* ```
*/
oldText;
/**
* Массив элементов для обработки.
* @defaultValue []
*/
elements;
/**
* Максимальное количество отображаемых элементов на странице.
* @defaultValue 5
*/
maxVisibleElements;
/**
* Текущая страница.
* Рекомендуется получать это значение после завершения всех операций
* @defaultValue 0
*/
thisPage;
/**
* Создает экземпляр класса Navigation.
* @param {number} maxVisibleElements Максимальное количество отображаемых элементов на странице
* @example
* ```ts
* // Создание с 3 элементами на странице
* const navigation = new Navigation(3);
*
* // Создание с 10 элементами на странице
* const navigation = new Navigation(10);
* ```
*/
constructor(maxVisibleElements = 5) {
this.isUsedStandardText = true;
this.nextText = [];
this.oldText = [];
this.elements = [];
this.maxVisibleElements = maxVisibleElements;
this.thisPage = 0;
}
/**
* Определяет желание пользователя двигаться вперед.
* Проверяет наличие команд навигации вперед в тексте
*
* @param {string} text Пользовательский запрос
* @return {boolean} true если обнаружена команда навигации вперед
* @example
* ```ts
* const isNext = navigation.isNext('покажи дальше'); // true
* const isNext = navigation.isNext('вернись назад'); // false
* ```
*/
isNext(text) {
let nextText;
if (this.isUsedStandardText) {
nextText = [...this.nextText, ...this.STANDARD_NEXT_TEXT];
}
else {
nextText = this.nextText;
}
return utils_1.Text.isSayText(nextText, text);
}
/**
* Определяет желание пользователя двигаться назад.
* Проверяет наличие команд навигации назад в тексте
*
* @param {string} text Пользовательский запрос
* @return {boolean} true если обнаружена команда навигации назад
* @example
* ```ts
* const isOld = navigation.isOld('вернись назад'); // true
* const isOld = navigation.isOld('покажи дальше'); // false
* ```
*/
isOld(text) {
let oldText;
if (this.isUsedStandardText) {
oldText = [...this.oldText, ...this.STANDARD_OLD_TEXT];
}
else {
oldText = this.oldText;
}
return utils_1.Text.isSayText(oldText, text);
}
/**
* Валидация текущей страницы.
* Проверяет и корректирует значение thisPage в пределах допустимого диапазона
*
* @param {number} maxPage Максимальное количество страниц
*/
_validatePage(maxPage) {
const maxValue = maxPage ?? this.getMaxPage();
if (this.thisPage >= maxValue) {
this.thisPage = maxValue - 1;
}
if (this.thisPage < 0) {
this.thisPage = 0;
}
}
/**
* Определяет желание пользователя перейти на определенную страницу.
* Ищет в тексте указание конкретной страницы в формате "N страница"
*
* @param {string} text Пользовательский запрос
* @return {boolean} true если обнаружено указание страницы
* @example
* ```ts
* const isNumberPage = navigation.numberPage('покажи 2 страницу'); // true
* const isNumberPage = navigation.numberPage('следующая страница'); // false
* ```
*/
numberPage(text) {
const data = /((-|)\d+) страни/imu.exec(text);
if (data) {
this.thisPage = +data[1] - 1;
this._validatePage();
return true;
}
return false;
}
/**
* Осуществляет переход на следующую страницу.
* Проверяет команду навигации вперед и обновляет thisPage
*
* @param {string} text Пользовательский запрос
* @return {boolean} true если переход выполнен
*/
_nextPage(text) {
if (this.isNext(text)) {
this.thisPage++;
this._validatePage();
return true;
}
return false;
}
/**
* Осуществляет переход на предыдущую страницу.
* Проверяет команду навигации назад и обновляет thisPage
*
* @param {string} text Пользовательский запрос
* @return {boolean} true если переход выполнен
*/
_oldPage(text) {
if (this.isOld(text)) {
this.thisPage--;
this._validatePage();
return true;
}
return false;
}
/**
* Возвращает массив элементов текущей страницы.
* Обрабатывает команды навигации и возвращает элементы в пределах maxVisibleElements
*
* ⚠️ Внимание: При передаче `text` с командой навигации ("Дальше"/"Назад")
* автоматически изменяет `this.thisPage` (текущую страницу).
*
*
* @param elements Массив элементов для обработки
* @param {string} text Пользовательский запрос
* @return Массив элементов текущей страницы
* @example
* ```ts
* const elements = [
* { id: 1, name: 'Элемент 1' },
* { id: 2, name: 'Элемент 2' },
* { id: 3, name: 'Элемент 3' },
* { id: 4, name: 'Элемент 4' }
* ];
*
* // Получение элементов первой страницы
* const pageElements = navigation.getPageElements(elements);
* // [{ id: 1, name: 'Элемент 1' }, { id: 2, name: 'Элемент 2' }]
*
* // Переход на следующую страницу
* const nextPageElements = navigation.getPageElements(null, 'вперед');
* // [{ id: 3, name: 'Элемент 3' }, { id: 4, name: 'Элемент 4' }]
* ```
*/
getPageElements(elements = null, text = '') {
const showElements = [];
if (elements) {
this.elements = elements;
}
this._nextPage(text);
this._oldPage(text);
const start = this.thisPage * this.maxVisibleElements;
const end = start + this.maxVisibleElements;
if (this.elements.length >= start) {
for (let i = start; i < end; i++) {
if (this.elements[i] !== undefined) {
showElements.push(this.elements[i]);
}
}
}
return showElements;
}
/**
* Выбор элемента из списка по тексту или номеру.
* Поддерживает поиск по тексту с учетом схожести и выбор по номеру
*
* @param elements Массив элементов для обработки
* @param {string} text Пользовательский запрос
* @param {TKeys} keys Ключи для поиска по объектам
* @param {number} thisPage Текущая страница
* @return Выбранный элемент или null
* @example
* ```ts
* const elements = [
* { id: 1, name: 'Элемент 1' },
* { id: 2, name: 'Элемент 2' }
* ];
*
* // Выбор по номеру
* const selected = navigation.selectedElement(elements, 'выбери 1');
* // { id: 1, name: 'Элемент 1' }
*
* // Выбор по тексту
* const selected = navigation.selectedElement(elements, 'Элемент 2', ['name']);
* // { id: 2, name: 'Элемент 2' }
* ```
*/
selectedElement(elements = null, text = '', keys = null, thisPage = null) {
if (thisPage !== null) {
this.thisPage = thisPage;
}
if (elements) {
this.elements = elements;
}
let number = null;
const data = /(\d+)/imu.exec(text);
if (data) {
number = +data[0][0];
}
const start = this.thisPage * this.maxVisibleElements;
let index = 1;
let selectElement = null;
let maxPercent = 0;
const end = start + this.maxVisibleElements;
const setMaxElement = (index, res) => {
if (res.status && res.percent > maxPercent) {
selectElement = this.elements[index];
maxPercent = res.percent;
}
};
for (let i = start; i < end; i++) {
if (this.elements[i] === undefined) {
continue;
}
if (index === number) {
return this.elements[i];
}
const elementsTypeof = typeof this.elements[i];
if (keys === null || elementsTypeof === 'string') {
const r = utils_1.Text.textSimilarity(this.elements[i] + '', text, 75);
setMaxElement(i, r);
}
else if (elementsTypeof === 'object') {
if (typeof keys === 'object') {
keys.forEach((key) => {
const value = this.elements[i][key];
if (value) {
const r = utils_1.Text.textSimilarity(value, text, 75);
setMaxElement(i, r);
}
});
}
else {
const value = this.elements[i][keys];
if (value) {
const r = utils_1.Text.textSimilarity(value, text, 75);
setMaxElement(i, r);
}
}
}
index++;
if (maxPercent > 90) {
return selectElement;
}
}
return selectElement;
}
/**
* Возвращает массив команд навигации.
* Формирует список доступных команд для навигации по страницам
*
* @param {boolean} isNumber Включить команды с номерами страниц
* @return {string[]} Массив команд навигации
* @example
* ```ts
* // Получение базовых команд
* const commands = navigation.getPageNav();
* // ['👈 Назад', 'Дальше 👉']
*
* // Получение команд с номерами страниц
* const commands = navigation.getPageNav(true);
* // ['1', '2', '3']
* ```
*/
getPageNav(isNumber = false) {
const maxPage = this.getMaxPage();
this._validatePage(maxPage);
if (maxPage === 1) {
return ['[1]'];
}
const buttons = [];
if (isNumber) {
const index = Math.max(this.thisPage - 2, 0);
let count = 0;
if (index === 1) {
buttons.push('1');
}
else if (index) {
buttons.push('1 ...');
}
for (let i = index; i < maxPage; i++) {
if (i === this.thisPage) {
buttons.push(`[${i + 1}]`);
}
else {
buttons.push(`${i + 1}`);
}
count++;
if (count > 4) {
if (i === maxPage - 2) {
buttons.push(`${maxPage}`);
}
else if (i < maxPage - 2) {
buttons.push(`... ${maxPage}`);
}
break;
}
}
}
else {
if (this.thisPage) {
buttons.push('👈 Назад');
}
if (this.thisPage + 1 < maxPage) {
buttons.push('Дальше 👉');
}
}
return buttons;
}
/**
* Возвращает информацию о текущей странице.
* Формирует строку с информацией о текущей позиции
*
* @return {string} Информация о текущей странице
* @example
* ```ts
* const info = navigation.getPageInfo();
* // "Страница 1 из 3"
* ```
*/
getPageInfo() {
if (this.elements[this.thisPage * this.maxVisibleElements] === undefined ||
this.thisPage < 0) {
this.thisPage = 0;
}
let pageInfo = this.thisPage + 1 + ' страница из ';
const maxPage = this.getMaxPage();
if (maxPage > 1) {
pageInfo += maxPage;
}
else {
pageInfo = '';
}
return pageInfo;
}
/**
* Возвращает максимальное количество страниц.
* Вычисляет количество страниц на основе количества элементов
*
* @param elements Массив элементов
* @return Максимальное количество страниц
* @example
* ```ts
* const elements = [
* { id: 1, name: 'Элемент 1' },
* { id: 2, name: 'Элемент 2' },
* { id: 3, name: 'Элемент 3' }
* ];
*
* const maxPage = navigation.getMaxPage(elements);
* // 2 (при maxVisibleElements = 2)
* ```
*/
getMaxPage(elements = null) {
if (elements) {
this.elements = elements;
}
if (typeof this.elements === 'object') {
const countEl = this.elements.length;
return Math.ceil(countEl / this.maxVisibleElements);
}
return 0;
}
}
exports.Navigation = Navigation;