UNPKG

umbot

Version:

Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф

471 lines (470 loc) 18.2 kB
"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;