From c9b12981cfc96465f6442c328ccaf7cc70bbd97d Mon Sep 17 00:00:00 2001 From: Mikhail Chechnev Date: Sun, 24 Sep 2023 22:22:48 +0300 Subject: [PATCH] =?UTF-8?q?WEB-=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20-=20=D0=B8=D0=BD=D0=B8=D1=86=D0=B8=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.json | 19 + .gitignore | 5 + .prettierrc | 7 + app.config.js | 20 + app.text.js | 57 + app/app.js | 162 + app/components/p8p_app_error_page.js | 42 + app/components/p8p_app_message.js | 233 + app/components/p8p_app_progress.js | 52 + app/components/p8p_app_workspace.js | 128 + app/components/p8p_data_grid.js | 162 + app/components/p8p_fullscreen_dialog.js | 65 + app/components/p8p_panels_menu.js | 231 + app/components/p8p_table.js | 726 ++++ app/context/application.js | 149 + app/context/application_reducer.js | 68 + app/context/backend.js | 104 + app/context/messaging.js | 109 + app/context/messaging_reducer.js | 84 + app/context/navigation.js | 123 + app/core/client.js | 216 + app/core/utils.js | 69 + app/index.js | 19 + app/panels/dummy/dummy.js | 32 + app/panels/dummy/index.js | 16 + app/panels/prj_fin/index.js | 16 + app/panels/prj_fin/prj_fin.js | 59 + app/panels/prj_fin/projects.js | 339 ++ app/panels/prj_fin/stage_arts.js | 201 + app/panels/prj_fin/stage_contracts.js | 256 ++ app/panels/prj_fin/stages.js | 408 ++ app/panels/prj_jobs/index.js | 16 + app/panels/prj_jobs/prj_jobs.js | 163 + app/root.js | 37 + css/fonts-material-icons.css | 23 + css/fonts-roboto.css | 148 + css/p8-panels.css | 0 dist/p8-panels.js | 349 ++ fonts/material-icons.woff2 | Bin 0 -> 128352 bytes fonts/roboto-cyrillic-300-normal.woff2 | Bin 0 -> 9576 bytes fonts/roboto-cyrillic-400-normal.woff2 | Bin 0 -> 9628 bytes fonts/roboto-cyrillic-500-normal.woff2 | Bin 0 -> 9840 bytes fonts/roboto-cyrillic-700-normal.woff2 | Bin 0 -> 9644 bytes fonts/roboto-cyrillic-ext-300-normal.woff2 | Bin 0 -> 15000 bytes fonts/roboto-cyrillic-ext-400-normal.woff2 | Bin 0 -> 15344 bytes fonts/roboto-cyrillic-ext-500-normal.woff2 | Bin 0 -> 14968 bytes fonts/roboto-cyrillic-ext-700-normal.woff2 | Bin 0 -> 14684 bytes fonts/roboto-latin-300-normal.woff2 | Bin 0 -> 15740 bytes fonts/roboto-latin-400-normal.woff2 | Bin 0 -> 15744 bytes fonts/roboto-latin-500-normal.woff2 | Bin 0 -> 15920 bytes fonts/roboto-latin-700-normal.woff2 | Bin 0 -> 15860 bytes fonts/roboto-latin-ext-300-normal.woff2 | Bin 0 -> 11796 bytes fonts/roboto-latin-ext-400-normal.woff2 | Bin 0 -> 11872 bytes fonts/roboto-latin-ext-500-normal.woff2 | Bin 0 -> 11800 bytes fonts/roboto-latin-ext-700-normal.woff2 | Bin 0 -> 11824 bytes img/default_preview.png | Bin 0 -> 41606 bytes img/prj_fin.png | Bin 0 -> 69525 bytes img/prj_jobs.jpg | Bin 0 -> 159147 bytes index.html | 18 + package-lock.json | 4595 ++++++++++++++++++++ package.json | 40 + webpack.config.js | 46 + 62 files changed, 9612 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 app.config.js create mode 100644 app.text.js create mode 100644 app/app.js create mode 100644 app/components/p8p_app_error_page.js create mode 100644 app/components/p8p_app_message.js create mode 100644 app/components/p8p_app_progress.js create mode 100644 app/components/p8p_app_workspace.js create mode 100644 app/components/p8p_data_grid.js create mode 100644 app/components/p8p_fullscreen_dialog.js create mode 100644 app/components/p8p_panels_menu.js create mode 100644 app/components/p8p_table.js create mode 100644 app/context/application.js create mode 100644 app/context/application_reducer.js create mode 100644 app/context/backend.js create mode 100644 app/context/messaging.js create mode 100644 app/context/messaging_reducer.js create mode 100644 app/context/navigation.js create mode 100644 app/core/client.js create mode 100644 app/core/utils.js create mode 100644 app/index.js create mode 100644 app/panels/dummy/dummy.js create mode 100644 app/panels/dummy/index.js create mode 100644 app/panels/prj_fin/index.js create mode 100644 app/panels/prj_fin/prj_fin.js create mode 100644 app/panels/prj_fin/projects.js create mode 100644 app/panels/prj_fin/stage_arts.js create mode 100644 app/panels/prj_fin/stage_contracts.js create mode 100644 app/panels/prj_fin/stages.js create mode 100644 app/panels/prj_jobs/index.js create mode 100644 app/panels/prj_jobs/prj_jobs.js create mode 100644 app/root.js create mode 100644 css/fonts-material-icons.css create mode 100644 css/fonts-roboto.css create mode 100644 css/p8-panels.css create mode 100644 dist/p8-panels.js create mode 100644 fonts/material-icons.woff2 create mode 100644 fonts/roboto-cyrillic-300-normal.woff2 create mode 100644 fonts/roboto-cyrillic-400-normal.woff2 create mode 100644 fonts/roboto-cyrillic-500-normal.woff2 create mode 100644 fonts/roboto-cyrillic-700-normal.woff2 create mode 100644 fonts/roboto-cyrillic-ext-300-normal.woff2 create mode 100644 fonts/roboto-cyrillic-ext-400-normal.woff2 create mode 100644 fonts/roboto-cyrillic-ext-500-normal.woff2 create mode 100644 fonts/roboto-cyrillic-ext-700-normal.woff2 create mode 100644 fonts/roboto-latin-300-normal.woff2 create mode 100644 fonts/roboto-latin-400-normal.woff2 create mode 100644 fonts/roboto-latin-500-normal.woff2 create mode 100644 fonts/roboto-latin-700-normal.woff2 create mode 100644 fonts/roboto-latin-ext-300-normal.woff2 create mode 100644 fonts/roboto-latin-ext-400-normal.woff2 create mode 100644 fonts/roboto-latin-ext-500-normal.woff2 create mode 100644 fonts/roboto-latin-ext-700-normal.woff2 create mode 100644 img/default_preview.png create mode 100644 img/prj_fin.png create mode 100644 img/prj_jobs.jpg create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 webpack.config.js diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..53c3c28 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,19 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:react-hooks/recommended"], + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "plugins": ["react", "react-hooks"], + "settings": { + "react": { "version": "detect" } + }, + "rules": {} +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..45ec153 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Dependency directories +node_modules/ + +# VS Code +.vscode/ \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..e8a426a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "useTabs": false, + "tabWidth": 4, + "printWidth": 150, + "trailingComma": "none", + "arrowParens": "avoid" +} diff --git a/app.config.js b/app.config.js new file mode 100644 index 0000000..99cf299 --- /dev/null +++ b/app.config.js @@ -0,0 +1,20 @@ +/* + Парус 8 - Панели мониторинга + Настройки приложения +*/ + +//--------- +//Константы +//--------- + +//Системеые параметры +const SYSTEM = { + //Адрес сервера приложений "ПАРУС 8 Онлайн" + SERVER: "../../DicAcc/" +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { SYSTEM }; diff --git a/app.text.js b/app.text.js new file mode 100644 index 0000000..dfad828 --- /dev/null +++ b/app.text.js @@ -0,0 +1,57 @@ +/* + Парус 8 - Панели мониторинга + Текстовые ресурсы и константы +*/ + +//---------------- +//Интерфейс модуля +//---------------- + +//Заголовки +export const TITLES = { + MAIN_MENU: "Доступные панели", //Главное меню + INFO: "Информация", //Информационный блок + WARN: "Предупреждение", //Блок предупреждения + ERR: "Ошибка", //Информация об ошибке + DEFAULT_PANELS_GROUP: "Без привязки к группе" //Заголовок группы панелей по умолчанию +}; + +//Текст +export const TEXTS = { + LOADING: "Ожидайте...", //Ожидание завершения процесса + NO_DATA_FOUND: "Данных не найдено" //Отсутствие данных +}; + +//Текст кнопок +export const BUTTONS = { + NAVIGATE_HOME: "Домой", //Переход к домашней странице + NAVIGATE_BACK: "Назад", //Возврат назад по навигации + NAVIGATE: "Перейти", //Переход к разделу/панели/адресу + OK: "ОК", //Ок + CANCEL: "Отмена", //Отмена + CLOSE: "Закрыть", //Сокрытие + CLEAR: "Очистить", //Очистка + ORDER_ASC: "По возрастанию", //Сортировка по возрастанию + ORDER_DESC: "По убыванию", //Сортировка по убыванию + FILTER: "Фильтр", //Фильтрация + MORE: "Ещё" //Догрузка данных +}; + +//Текст элементов ввода +export const INPUTS = { + VALUE: "Значение", + VALUE_FROM: "С", + VALUE_TO: "По" +}; + +//Типовые сообщения об ошибках +export const ERROR = { + UNDER_CONSTRUCTION: "Панель в разработке", + P8O_API_UNAVAILABLE: '"ПАРУС 8 Онлайн" недоступен', + DEFAULT: "Неожиданная ошибка" +}; + +//Типовые сообщения для ошибок HTTP +export const ERROR_HTTP = { + 404: "Адрес не найден" +}; diff --git a/app/app.js b/app/app.js new file mode 100644 index 0000000..eb25e64 --- /dev/null +++ b/app/app.js @@ -0,0 +1,162 @@ +/* + Парус 8 - Панели мониторинга + Приложение +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useContext, useEffect } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { createHashRouter, RouterProvider, useRouteError } from "react-router-dom"; //Роутер +import { ApplicationСtx } from "./context/application"; //Контекст приложения +import { NavigationContext, NavigationCtx, getRootLocation } from "./context/navigation"; //Контекст навигации +import { P8PAppErrorPage } from "./components/p8p_app_error_page"; //Страница с ошибкой +import { P8PAppWorkspace } from "./components/p8p_app_workspace"; //Рабочее пространство панели +import { P8PPanelsMenuGrid, PANEL_SHAPE } from "./components/p8p_panels_menu"; //Меню панелей +import { TITLES, BUTTONS, ERROR, ERROR_HTTP } from "../app.text"; //Текстовые ресурсы и константы + +//-------------------------- +//Вспомогательные компоненты +//-------------------------- + +//Обработка ошибок роутинга +const RouterError = ({ homePath }) => { + //Подключение к контексту навигации + const { navigateTo } = useContext(NavigationCtx); + + //Извлечем ошибку роутинга + const routeError = useRouteError(); + + //Отработка нажатия на кнопку навигации + const handleNavigate = () => navigateTo({ path: `${homePath.startsWith("/") ? "" : "/"}${homePath}` }); + + //Генерация содержимого + return ( + + ); +}; + +//Контроль свойств - обработка ошибок роутинга +RouterError.propTypes = { + homePath: PropTypes.string.isRequired +}; + +//Главное меню приложения +const MainMenu = ({ panels = [] } = {}) => { + //Подключение к контексту навигации + const { navigatePanel } = useContext(NavigationCtx); + + //Отработка действия навигации элемента меню + const handleItemNavigate = panel => navigatePanel(panel); + + //Генерация содержимого + return ( + + ); +}; + +//Контроль свойств - главное меню приложения +MainMenu.propTypes = { + panels: PropTypes.arrayOf(PANEL_SHAPE).isRequired +}; + +//Рабочее пространство панели +const Workspace = ({ panels = [], selectedPanel, children } = {}) => { + //Подключение к контексту навигации + const { navigateRoot, navigatePanel } = useContext(NavigationCtx); + + //Отработка действия навигации домой + const handleHomeNavigate = () => navigateRoot(); + + //Отработка действия навигации элемента меню + const handleItemNavigate = panel => navigatePanel(panel); + + //Генерация содержимого + return ( + + {children} + + ); +}; + +//Контроль свойств - главное меню приложения +Workspace.propTypes = { + panels: PropTypes.arrayOf(PANEL_SHAPE).isRequired, + selectedPanel: PANEL_SHAPE, + children: PropTypes.element +}; + +//Обёртывание элемента в контекст навигации +const wrapNavigationContext = children => {children}; + +//----------- +//Тело модуля +//----------- + +//Приложение +const App = () => { + //Собственное состояние + const [routes, setRoutes] = useState([]); + + //Подключение к контексту приложения + const { appState } = useContext(ApplicationСtx); + + //Инициализация роутера + const content = routes.length > 0 ? : null; + + //При изменении состояния загрузки панелей + useEffect(() => { + if (appState.panelsLoaded) { + //Сборка "веток" для панелей + let routes = [ + { + path: getRootLocation(), + element: wrapNavigationContext(), + errorElement: wrapNavigationContext() + } + ]; + for (const panel of appState.panels) { + // eslint-disable-next-line no-undef + const p = require(`./panels/${panel.path}`); + routes.push({ + path: `${panel.url}/*`, + element: wrapNavigationContext( + + + + ), + errorElement: wrapNavigationContext() + }); + } + setRoutes(routes); + } + }, [appState.panels, appState.panelsLoaded]); + + //Генерация содержимого + return content; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { App }; diff --git a/app/components/p8p_app_error_page.js b/app/components/p8p_app_error_page.js new file mode 100644 index 0000000..936c403 --- /dev/null +++ b/app/components/p8p_app_error_page.js @@ -0,0 +1,42 @@ +/* + Парус 8 - Панели мониторинга + Компонент: Страница ошибки +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Box } from "@mui/material"; //Контейнер +import { P8PAppInlineError } from "./p8p_app_message"; //Сообщения + +//----------- +//Тело модуля +//----------- + +//Страница ошибки +const P8PAppErrorPage = ({ errorMessage, onNavigate, navigateCaption }) => { + //Генерация содержимого + return ( + +
+ +
+
+ ); +}; + +//Контроль свойств - Страница ошибки +P8PAppErrorPage.propTypes = { + errorMessage: PropTypes.string.isRequired, + onNavigate: PropTypes.func, + navigateCaption: PropTypes.string +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PAppErrorPage }; diff --git a/app/components/p8p_app_message.js b/app/components/p8p_app_message.js new file mode 100644 index 0000000..1f46114 --- /dev/null +++ b/app/components/p8p_app_message.js @@ -0,0 +1,233 @@ +/* + Парус 8 - Панели мониторинга + Компонент: Сообщение +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import Dialog from "@mui/material/Dialog"; //базовый класс диалога Material UI +import DialogTitle from "@mui/material/DialogTitle"; //Заголовок диалога +import DialogContent from "@mui/material/DialogContent"; //Содержимое диалога +import DialogContentText from "@mui/material/DialogContentText"; //Текст содержимого диалога +import DialogActions from "@mui/material/DialogActions"; //Область действий диалога +import Typography from "@mui/material/Typography"; //Текст +import Button from "@mui/material/Button"; //Кнопки +import Container from "@mui/material/Container"; //Контейнер +import Box from "@mui/material/Box"; //Обёртка + +//--------- +//Константы +//--------- + +//Варианты исполнения +const P8P_APP_MESSAGE_VARIANT = { + INFO: "information", + WARN: "warning", + ERR: "error" +}; + +//Стили +const STYLES = { + DEFAULT: { + wordBreak: "break-word" + }, + INFO: { + titleText: {}, + bodyText: {} + }, + WARN: { + titleText: { + color: "orange" + }, + bodyText: { + color: "orange" + } + }, + ERR: { + titleText: { + color: "red" + }, + bodyText: { + color: "red" + } + }, + INLINE_MESSAGE: { + with: "100%", + textAlign: "center" + } +}; + +//----------- +//Тело модуля +//----------- + +//Сообщение +const P8PAppMessage = ({ variant, title, titleText, cancelBtn, onCancel, cancelBtnCaption, okBtn, onOk, okBtnCaption, open, text }) => { + //Подбор стиля и ресурсов + let style = STYLES.INFO; + switch (variant) { + case P8P_APP_MESSAGE_VARIANT.INFO: { + style = STYLES.INFO; + break; + } + case P8P_APP_MESSAGE_VARIANT.WARN: { + style = STYLES.WARN; + break; + } + case P8P_APP_MESSAGE_VARIANT.ERR: { + style = STYLES.ERR; + break; + } + } + + //Заголовок + let titlePart; + if (title && titleText) + titlePart = ( + + {titleText} + + ); + + //Кнопка Отмена + let cancelBtnPart; + if (cancelBtn && cancelBtnCaption && variant === P8P_APP_MESSAGE_VARIANT.WARN) + cancelBtnPart = ; + + //Кнопка OK + let okBtnPart; + if (okBtn && okBtnCaption) + okBtnPart = ( + + ); + + //Все действия + let actionsPart; + if (cancelBtnPart || okBtnPart) + actionsPart = ( + + {okBtnPart} + {cancelBtnPart} + + ); + + //Генерация содержимого + return ( + (onCancel ? onCancel() : null)} + > + {titlePart} + + + {text} + + + {actionsPart} + + ); +}; + +//Контроль свойств - Сообщение +P8PAppMessage.propTypes = { + variant: PropTypes.string.isRequired, + title: PropTypes.bool, + titleText: PropTypes.string, + cancelBtn: PropTypes.bool, + onCancel: PropTypes.func, + cancelBtnCaption: PropTypes.string, + okBtn: PropTypes.bool, + onOk: PropTypes.func, + okBtnCaption: PropTypes.string, + open: PropTypes.bool, + text: PropTypes.string +}; + +//Встроенное сообщение +const P8PAppInlineMessage = ({ variant, text, okBtn, onOk, okBtnCaption }) => { + //Генерация содержимого + return ( + + + + {text} + + {okBtn && okBtnCaption ? ( + + + + ) : null} + + + ); +}; + +//Контроль свойств - Встроенное сообщение +P8PAppInlineMessage.propTypes = { + variant: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + okBtn: PropTypes.bool, + onOk: PropTypes.func, + okBtnCaption: PropTypes.string +}; + +//Формирование типового сообщения +const buildVariantMessage = (props, variant) => { + //Извлекаем необходимые свойства + let { open, titleText } = props; + + //Генерация содержимого + return ; +}; + +//Формирование типового встроенного сообщения +const buildVariantInlineMessage = (props, variant) => { + //Генерация содержимого + return ; +}; + +//Сообщение об ошибке +const P8PAppMessageErr = props => buildVariantMessage(props, P8P_APP_MESSAGE_VARIANT.ERR); + +//Сообщение предупреждения +const P8PAppMessageWarn = props => buildVariantMessage(props, P8P_APP_MESSAGE_VARIANT.WARN); + +//Сообщение информации +const P8PAppMessageInfo = props => buildVariantMessage(props, P8P_APP_MESSAGE_VARIANT.INFO); + +//Встраиваемое сообщение об ошибке +const P8PAppInlineError = props => buildVariantInlineMessage(props, P8P_APP_MESSAGE_VARIANT.ERR); + +//Встраиваемое cообщение предупреждения +const P8PAppInlineWarn = props => buildVariantInlineMessage(props, P8P_APP_MESSAGE_VARIANT.WARN); + +//Встраиваемое сообщение информации +const P8PAppInlineInfo = props => buildVariantInlineMessage(props, P8P_APP_MESSAGE_VARIANT.INFO); + +//---------------- +//Интерфейс модуля +//---------------- + +export { + P8P_APP_MESSAGE_VARIANT, + P8PAppMessage, + P8PAppMessageErr, + P8PAppMessageWarn, + P8PAppMessageInfo, + P8PAppInlineMessage, + P8PAppInlineError, + P8PAppInlineWarn, + P8PAppInlineInfo +}; diff --git a/app/components/p8p_app_progress.js b/app/components/p8p_app_progress.js new file mode 100644 index 0000000..fb208ce --- /dev/null +++ b/app/components/p8p_app_progress.js @@ -0,0 +1,52 @@ +/* + Парус 8 - Панели мониторинга + Компонент: Индикатор процесса +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import Dialog from "@mui/material/Dialog"; //базовый класс диалога Material UI +import DialogTitle from "@mui/material/DialogTitle"; //Заголовок диалога +import DialogContent from "@mui/material/DialogContent"; //Содержимое диалога +import DialogContentText from "@mui/material/DialogContentText"; //Текст содержимого диалога +import LinearProgress from "@mui/material/LinearProgress"; //Индикатор + +//----------- +//Тело модуля +//----------- + +//Индикатора прогресса +const P8PAppProgress = props => { + //Извлекаем необходимые свойства + let { open, title, text } = props; + + //Генерация содержимого + return ( +
+ + {title ? {title} : null} + + {text} + + + +
+ ); +}; + +//Контроль свойств - Индикатора прогресса +P8PAppProgress.propTypes = { + open: PropTypes.bool, + title: PropTypes.string, + text: PropTypes.string.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PAppProgress }; diff --git a/app/components/p8p_app_workspace.js b/app/components/p8p_app_workspace.js new file mode 100644 index 0000000..d541c40 --- /dev/null +++ b/app/components/p8p_app_workspace.js @@ -0,0 +1,128 @@ +/* + Парус 8 - Панели мониторинга + Компонент: Рабочее пространство +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { + AppBar, + CssBaseline, + Icon, + Box, + Toolbar, + IconButton, + Typography, + Drawer, + List, + ListItemButton, + ListItemIcon, + ListItemText +} from "@mui/material"; //Интерфейсные компоненты +import { P8PPanelsMenuDrawer, PANEL_SHAPE } from "./p8p_panels_menu"; + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + ROOT_BOX: { display: "flex" }, + APP_BAR: { position: "fixed" }, + APP_BAR_BUTTON: { mr: 2 }, + MAIN: { flexGrow: 1 } +}; + +//----------- +//Тело модуля +//----------- + +//Рабочее пространство +const P8PAppWorkspace = ({ children, panels = [], selectedPanel, closeCaption, homeCaption, onHomeNavigate, onItemNavigate } = {}) => { + //Собственное состояния + const [open, setOpen] = useState(false); + + //Отработка открытия бокового меню + const handleDrawerOpen = () => { + setOpen(true); + }; + + //Отработка закрытия бового меню + const handleDrawerClose = () => { + setOpen(false); + }; + + //Отработка нажатия на домашнюю страницу + const handleHomeClick = () => (onHomeNavigate ? onHomeNavigate() : null); + + //Отработка нажатия на элемент бокового меню + const handleItemNavigate = panel => { + handleDrawerClose(); + onItemNavigate ? onItemNavigate(panel) : null; + }; + + //Генерация содержимого + return ( + + + + + + {open ? "chevron_left" : "menu"} + + + {selectedPanel?.caption} + + + + + + + + close + + + + + + home + + + + + + +
+ + {children} +
+
+ ); +}; + +//Контроль свойств - Рабочее пространство +P8PAppWorkspace.propTypes = { + children: PropTypes.element, + panels: PropTypes.arrayOf(PANEL_SHAPE).isRequired, + selectedPanel: PANEL_SHAPE, + closeCaption: PropTypes.string.isRequired, + homeCaption: PropTypes.string.isRequired, + onHomeNavigate: PropTypes.func, + onItemNavigate: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PAppWorkspace }; diff --git a/app/components/p8p_data_grid.js b/app/components/p8p_data_grid.js new file mode 100644 index 0000000..da9f9f1 --- /dev/null +++ b/app/components/p8p_data_grid.js @@ -0,0 +1,162 @@ +/* + Парус 8 - Панели мониторинга + Компонент: Таблица данных +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { deepCopyObject } from "../core/utils"; //Вспомогательные процедуры и функции +import { P8PTable, P8P_TABLE_SIZE, P8P_TABLE_DATA_TYPE, P8P_FILTER_SHAPE } from "./p8p_table"; //Таблица + +//--------- +//Константы +//--------- + +//Размеры отступов +const P8PDATA_GRID_SIZE = P8P_TABLE_SIZE; + +//Типы данных +const P8PDATA_GRID_DATA_TYPE = P8P_TABLE_DATA_TYPE; + +//Формат фильтра +const P8PDATA_GRID_FILTER_SHAPE = P8P_FILTER_SHAPE; + +//----------- +//Тело модуля +//----------- + +//Таблица данных +const P8PDataGrid = ({ + columnsDef, + filtersInitial, + rows, + size, + morePages, + reloading, + expandable, + orderAscMenuItemCaption, + orderDescMenuItemCaption, + filterMenuItemCaption, + valueFilterCaption, + valueFromFilterCaption, + valueToFilterCaption, + okFilterBtnCaption, + clearFilterBtnCaption, + cancelFilterBtnCaption, + morePagesBtnCaption, + noDataFoundText, + headCellRender, + dataCellRender, + rowExpandRender, + valueFormatter, + onOrderChanged, + onFilterChanged, + onPagesCountChanged +}) => { + //Собственное состояние - сортировки + const [orders, setOrders] = useState([]); + + //Собственное состояние - фильтры + const [filters, setFilters] = useState(filtersInitial || []); + + //При изменении состояния сортировки + const handleOrderChanged = ({ columnName, direction }) => { + let newOrders = deepCopyObject(orders); + const curOrder = newOrders.find(o => o.name == columnName); + if (direction == null && curOrder) newOrders.splice(newOrders.indexOf(curOrder), 1); + if (direction != null && !curOrder) newOrders.push({ name: columnName, direction }); + if (direction != null && curOrder) curOrder.direction = direction; + setOrders(newOrders); + if (onOrderChanged) onOrderChanged({ orders: newOrders }); + }; + + //При изменении состояния фильтра + const handleFilterChanged = ({ columnName, from, to }) => { + let newFilters = deepCopyObject(filters); + let curFilter = newFilters.find(f => f.name == columnName); + if (from == null && to == null && curFilter) newFilters.splice(newFilters.indexOf(curFilter), 1); + if ((from != null || to != null) && !curFilter) newFilters.push({ name: columnName, from, to }); + if ((from != null || to != null) && curFilter) { + curFilter.from = from; + curFilter.to = to; + } + setFilters(newFilters); + if (onFilterChanged) onFilterChanged({ filters: newFilters }); + }; + + //При изменении количества отображаемых страниц + const handlePagesCountChanged = () => { + if (onPagesCountChanged) onPagesCountChanged(); + }; + + //Генерация содержимого + return ( + + ); +}; + +//Контроль свойств - Таблица данных +P8PDataGrid.propTypes = { + columnsDef: PropTypes.array.isRequired, + filtersInitial: PropTypes.arrayOf(P8PDATA_GRID_FILTER_SHAPE), + rows: PropTypes.array.isRequired, + size: PropTypes.string, + morePages: PropTypes.bool.isRequired, + reloading: PropTypes.bool.isRequired, + expandable: PropTypes.bool, + orderAscMenuItemCaption: PropTypes.string.isRequired, + orderDescMenuItemCaption: PropTypes.string.isRequired, + filterMenuItemCaption: PropTypes.string.isRequired, + valueFilterCaption: PropTypes.string.isRequired, + valueFromFilterCaption: PropTypes.string.isRequired, + valueToFilterCaption: PropTypes.string.isRequired, + okFilterBtnCaption: PropTypes.string.isRequired, + clearFilterBtnCaption: PropTypes.string.isRequired, + cancelFilterBtnCaption: PropTypes.string.isRequired, + morePagesBtnCaption: PropTypes.string.isRequired, + noDataFoundText: PropTypes.string, + headCellRender: PropTypes.func, + dataCellRender: PropTypes.func, + rowExpandRender: PropTypes.func, + valueFormatter: PropTypes.func, + onOrderChanged: PropTypes.func, + onFilterChanged: PropTypes.func, + onPagesCountChanged: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PDataGrid, P8PDATA_GRID_SIZE, P8PDATA_GRID_DATA_TYPE, P8PDATA_GRID_FILTER_SHAPE }; diff --git a/app/components/p8p_fullscreen_dialog.js b/app/components/p8p_fullscreen_dialog.js new file mode 100644 index 0000000..c51a4ff --- /dev/null +++ b/app/components/p8p_fullscreen_dialog.js @@ -0,0 +1,65 @@ +/* + Парус 8 - Панели мониторинга + Компонент: Полноэкранный диалог +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Dialog, AppBar, Toolbar, IconButton, Typography, Icon, DialogContent, DialogTitle } from "@mui/material"; //Интерфейсные компоненты + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + DIALOG_TITLE: { padding: 0 }, + APP_BAR: { position: "relative" }, + TITLE_TYPOGRAPHY: { ml: 2, flex: 1 } +}; + +//----------- +//Тело модуля +//----------- + +//Полноэкранный диалог +const P8PFullScreenDialog = ({ title, onClose, children }) => { + const handleClose = () => { + onClose ? onClose() : null; + }; + + return ( + + + + + + close + + + {title} + + + + + {children} + + ); +}; + +//Контроль свойств - Полноэкранный диалог +P8PFullScreenDialog.propTypes = { + title: PropTypes.string.isRequired, + onClose: PropTypes.func, + children: PropTypes.element +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PFullScreenDialog }; diff --git a/app/components/p8p_panels_menu.js b/app/components/p8p_panels_menu.js new file mode 100644 index 0000000..77f0cc2 --- /dev/null +++ b/app/components/p8p_panels_menu.js @@ -0,0 +1,231 @@ +/* + Парус 8 - Панели мониторинга + Компонент: Меню панелей +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { + Button, + Typography, + Icon, + Box, + Card, + CardActions, + CardContent, + CardMedia, + Stack, + Grid, + Divider, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText +} from "@mui/material"; //Интерфейсные компоненты + +//--------- +//Константы +//--------- + +//Типы меню +const VARIANT = { + DRAWER: "DRAWER", + GRID: "GRID" +}; + +//Стили +const STYLES = { + CONTAINER: { + display: "flex", + justifyContent: "center", + alignItems: "flex-start", + minHeight: "100vh" + }, + TITLE: { + textTransform: "uppercase", + textAlign: "center", + fontWeight: "bold" + }, + GRID: { + maxWidth: 1200, + direction: "row", + justifyContent: "left", + alignItems: "stretch" + }, + PANEL_CARD: { + maxWidth: 400, + height: "100%", + flexDirection: "column", + display: "flex" + }, + PANEL_CARD_MEDIA: { + height: 140 + }, + PANEL_CARD_CONTENT_TITLE: { + alignItems: "center" + }, + PANEL_CARD_ACTIONS: { + marginTop: "auto", + display: "flex", + justifyContent: "flex-end", + alignItems: "flex-start" + } +}; + +//Структура элемента описания панели +const PANEL_SHAPE = PropTypes.shape({ + name: PropTypes.string.isRequired, + caption: PropTypes.string.isRequired, + desc: PropTypes.string.isRequired, + group: PropTypes.string, + icon: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + preview: PropTypes.string.isRequired, + showInPanelsList: PropTypes.bool.isRequired, + url: PropTypes.string.isRequired +}); + +//-------------------------------- +//Вспомогательные классы и функции +//-------------------------------- + +//Формирование групп +const getGroups = panels => { + let res = []; + let addDefaultGroup = false; + for (const panel of panels) + if (panel.showInPanelsList == true) { + if (panel.group && !res.includes(panel.group)) res.push(panel.group); + if (!panel.group) addDefaultGroup = true; + } + if (addDefaultGroup || res.length == 0) res.push(null); + return res; +}; + +//Формирование ссылок на панели +const getPanelsLinks = ({ variant, panels, selectedPanel, defaultGroupTytle, navigateCaption, onItemNavigate }) => { + //Получим группы + let grps = getGroups(panels); + + //Построим ссылки + const panelsLinks = []; + for (const grp of grps) { + if (!(grps.length == 1 && grps[0] == null)) + panelsLinks.push( + variant === VARIANT.GRID ? ( + + + {grp ? grp : defaultGroupTytle} + + + ) : ( + + ) + ); + for (const panel of panels) { + if (panel.showInPanelsList == true && ((grp && panel.group === grp) || (!grp && !panel.group))) + panelsLinks.push( + variant === VARIANT.GRID ? ( + + + {panel.preview ? ( + + ) : null} + + + {panel.icon ? {panel.icon} : null} + {panel.caption} + + + {panel.desc} + + + + + + + + ) : ( + + (onItemNavigate ? onItemNavigate(panel) : null)} + > + + {panel.icon} + + + + + ) + ); + } + } + + //Вернём ссылки + return panelsLinks; +}; + +//----------- +//Тело модуля +//----------- + +//Меню панелей - сдвигающееся боковое меню +const P8PPanelsMenuDrawer = ({ onItemNavigate, panels = [], selectedPanel } = {}) => { + //Формируем ссылки на панели + const panelsLinks = getPanelsLinks({ variant: VARIANT.DRAWER, panels, selectedPanel, onItemNavigate }); + + //Генерация содержимого + return {panelsLinks}; +}; + +//Контроль свойств - Меню панелей - сдвигающееся боковое меню +P8PPanelsMenuDrawer.propTypes = { + onItemNavigate: PropTypes.func, + panels: PropTypes.arrayOf(PANEL_SHAPE).isRequired, + selectedPanel: PANEL_SHAPE +}; + +//Меню панелей - грид +const P8PPanelsMenuGrid = ({ title, onItemNavigate, navigateCaption, panels = [], defaultGroupTytle } = {}) => { + //Формируем ссылки на панели + const panelsLinks = getPanelsLinks({ variant: VARIANT.GRID, panels, defaultGroupTytle, navigateCaption, onItemNavigate }); + + //Генерация содержимого + return ( + + + {title ? ( + + + {title} + + + ) : null} + {panelsLinks} + + + ); +}; + +//Контроль свойств - Меню панелей - грид +P8PPanelsMenuGrid.propTypes = { + title: PropTypes.string, + onItemNavigate: PropTypes.func, + navigateCaption: PropTypes.string.isRequired, + panels: PropTypes.arrayOf(PANEL_SHAPE).isRequired, + defaultGroupTytle: PropTypes.string.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { PANEL_SHAPE, P8PPanelsMenuDrawer, P8PPanelsMenuGrid }; diff --git a/app/components/p8p_table.js b/app/components/p8p_table.js new file mode 100644 index 0000000..432df4c --- /dev/null +++ b/app/components/p8p_table.js @@ -0,0 +1,726 @@ +/* + Парус 8 - Панели мониторинга + Компонент: Таблица +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useEffect, useState, useMemo } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + IconButton, + Icon, + Menu, + MenuItem, + Divider, + Stack, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Chip, + Container +} from "@mui/material"; //Интерфейсные компоненты +import { P8PAppInlineError } from "./p8p_app_message"; //Встраиваемое сообщение об ошибке + +//--------- +//Константы +//--------- + +//Размеры отступов +const P8P_TABLE_SIZE = { + SMALL: "small", + MEDIUM: "medium" +}; + +//Типы данных +const P8P_TABLE_DATA_TYPE = { + STR: "STR", + NUMB: "NUMB", + DATE: "DATE" +}; + +//Направления сортировки +const P8P_TABLE_COLUMN_ORDER_DIRECTIONS = { + ASC: "ASC", + DESC: "DESC" +}; + +//Действия панели инструментов столбца +const P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS = { + ORDER_TOGGLE: "ORDER_TOGGLE", + FILTER_TOGGLE: "FILTER_TOGGLE" +}; + +//Действия меню столбца +const P8P_TABLE_COLUMN_MENU_ACTIONS = { + ORDER_ASC: "ORDER_ASC", + ORDER_DESC: "ORDER_DESC", + FILTER: "FILTER" +}; + +//Стили +const STYLES = { + TABLE: { + with: "100%" + }, + TABLE_ROW: { + "&:last-child td, &:last-child th": { border: 0 } + }, + TABLE_CELL_EXPAND_CONTAINER: { + paddingBottom: 0, + paddingTop: 0 + }, + TABLE_COLUMN_STACK: { + alignItems: "center" + }, + TABLE_COLUMN_MENU_ITEM_ICON: { + paddingRight: "10px" + }, + FILTER_CHIP: { + alignItems: "center" + }, + MORE_BUTTON_CONTAINER: { + with: "100%", + textAlign: "center", + padding: "5px" + } +}; + +//Структура элемента описания фильтра +const P8P_FILTER_SHAPE = PropTypes.shape({ + name: PropTypes.string.isRequired, + from: PropTypes.any, + to: PropTypes.any +}); + +//-------------------------------- +//Вспомогательные классы и функции +//-------------------------------- + +//Проверка существования значения +const hasValue = value => typeof value !== "undefined" && value !== null && value !== ""; + +//Панель инструментов столбца +const P8PTableColumnToolBar = ({ columnDef, orders, filters, onItemClick }) => { + //Кнопка сортировки + const order = orders.find(o => o.name == columnDef.name); + let orderButton = null; + if (order) + orderButton = ( + (onItemClick ? onItemClick(P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS.ORDER_TOGGLE, columnDef.name) : null)}> + {order.direction === P8P_TABLE_COLUMN_ORDER_DIRECTIONS.ASC ? "arrow_upward" : "arrow_downward"} + + ); + + //Кнопка фильтрации + const filter = filters.find(f => f.name == columnDef.name); + let filterButton = null; + if (hasValue(filter?.from) || hasValue(filter?.to)) + filterButton = ( + (onItemClick ? onItemClick(P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS.FILTER_TOGGLE, columnDef.name) : null)}> + filter_alt + + ); + //Генерация содержимого + return ( + <> + {orderButton} + {filterButton} + + ); +}; + +//Контроль свойств - Панель инструментов столбца +P8PTableColumnToolBar.propTypes = { + columnDef: PropTypes.object.isRequired, + orders: PropTypes.array.isRequired, + filters: PropTypes.array.isRequired, + onItemClick: PropTypes.func +}; + +//Меню столбца +const P8PTableColumnMenu = ({ columnDef, orderAscItemCaption, orderDescItemCaption, filterItemCaption, onItemClick }) => { + //Собственное состояние + const [anchorEl, setAnchorEl] = useState(null); + + //Флаг отображения + const open = Boolean(anchorEl); + + //По нажатию на открытие меню + const handleMenuButtonClick = event => { + setAnchorEl(event.currentTarget); + }; + + //По нажатию на пункт меню + const handleMenuItemClick = (event, index, action, columnName) => { + if (onItemClick) onItemClick(action, columnName); + setAnchorEl(null); + }; + + //При закрытии меню + const handleMenuClose = () => { + setAnchorEl(null); + }; + + //Формирование списка элементов меню в зависимости от описания колонки таблицы + const menuItems = []; + if (columnDef.order === true) { + menuItems.push( + handleMenuItemClick(event, index, P8P_TABLE_COLUMN_MENU_ACTIONS.ORDER_ASC, columnDef.name)} + > + arrow_upward + {orderAscItemCaption} + + ); + menuItems.push( + handleMenuItemClick(event, index, P8P_TABLE_COLUMN_MENU_ACTIONS.ORDER_DESC, columnDef.name)} + > + arrow_downward + {orderDescItemCaption} + + ); + } + if (columnDef.filter === true) { + if (menuItems.length > 0) menuItems.push(); + menuItems.push( + handleMenuItemClick(event, index, P8P_TABLE_COLUMN_MENU_ACTIONS.FILTER, columnDef.name)} + > + filter_alt + {filterItemCaption} + + ); + } + + //Генерация содержимого + return menuItems.length > 0 ? ( + <> + + more_vert + + + {menuItems} + + + ) : null; +}; + +//Контроль свойств - Меню столбца +P8PTableColumnMenu.propTypes = { + columnDef: PropTypes.object.isRequired, + orderAscItemCaption: PropTypes.string.isRequired, + orderDescItemCaption: PropTypes.string.isRequired, + filterItemCaption: PropTypes.string.isRequired, + onItemClick: PropTypes.func +}; + +//Диалог фильтра +const P8PTableColumnFilterDialog = ({ + columnDef, + from, + to, + valueCaption, + valueFromCaption, + valueToCaption, + okBtnCaption, + clearBtnCaption, + cancelBtnCaption, + valueFormatter, + onOk, + onClear, + onCancel +}) => { + //Собственное состояние - значения с-по + const [filterValues, setFilterValues] = useState({ from, to }); + + //Отработка воода значения в фильтр + const handleFilterTextFieldChanged = e => { + setFilterValues(prev => ({ ...prev, [e.target.name]: e.target.value })); + }; + + //Элементы ввода значений фильтра + let inputs = null; + if (Array.isArray(columnDef.values) && columnDef.values.length > 0) { + inputs = ( + + {columnDef.values.map((v, i) => ( + + {valueFormatter ? valueFormatter({ value: v, columnDef }) : v} + + ))} + + ); + } else { + switch (columnDef.dataType) { + case P8P_TABLE_DATA_TYPE.STR: { + inputs = ( + + ); + break; + } + case P8P_TABLE_DATA_TYPE.NUMB: + case P8P_TABLE_DATA_TYPE.DATE: { + inputs = ( + <> + +   + + + ); + break; + } + } + } + + return ( + (onCancel ? onCancel(columnDef.name) : null)} + > + {columnDef.caption} + {inputs} + + + + + + + ); +}; + +//Контроль свойств - Диалог фильтра +P8PTableColumnFilterDialog.propTypes = { + columnDef: PropTypes.object.isRequired, + from: PropTypes.any, + to: PropTypes.any, + valueCaption: PropTypes.string.isRequired, + valueFromCaption: PropTypes.string.isRequired, + valueToCaption: PropTypes.string.isRequired, + okBtnCaption: PropTypes.string.isRequired, + clearBtnCaption: PropTypes.string.isRequired, + cancelBtnCaption: PropTypes.string.isRequired, + valueFormatter: PropTypes.func, + onOk: PropTypes.func, + onClear: PropTypes.func, + onCancel: PropTypes.func +}; + +//Сводный фильтр +const P8PTableFiltersChips = ({ filters, columnsDef, valueFromCaption, valueToCaption, onFilterChipClick, onFilterChipDelete, valueFormatter }) => { + return ( + + {filters.map((filter, i) => { + const columnDef = columnsDef.find(columnDef => columnDef.name == filter.name); + return ( + + {columnDef.caption}:  + {hasValue(filter.from) && !columnDef.values && columnDef.dataType != P8P_TABLE_DATA_TYPE.STR + ? `${valueFromCaption.toLowerCase()} ` + : null} + {hasValue(filter.from) ? (valueFormatter ? valueFormatter({ value: filter.from, columnDef }) : filter.from) : null} + {hasValue(filter.to) && !columnDef.values && columnDef.dataType != P8P_TABLE_DATA_TYPE.STR + ? ` ${valueToCaption.toLowerCase()} ` + : null} + {hasValue(filter.to) ? (valueFormatter ? valueFormatter({ value: filter.to, columnDef }) : filter.to) : null} + + } + variant="outlined" + onClick={() => (onFilterChipClick ? onFilterChipClick(columnDef.name) : null)} + onDelete={() => (onFilterChipDelete ? onFilterChipDelete(columnDef.name) : null)} + /> + ); + })} + + ); +}; + +//Контроль свойств - Сводный фильтр +P8PTableFiltersChips.propTypes = { + filters: PropTypes.array.isRequired, + columnsDef: PropTypes.array.isRequired, + valueFromCaption: PropTypes.string.isRequired, + valueToCaption: PropTypes.string.isRequired, + onFilterChipClick: PropTypes.func, + onFilterChipDelete: PropTypes.func, + valueFormatter: PropTypes.func +}; + +//----------- +//Тело модуля +//----------- + +//Таблица +const P8PTable = ({ + columnsDef, + rows, + orders, + filters, + size, + morePages, + reloading, + expandable, + orderAscMenuItemCaption, + orderDescMenuItemCaption, + filterMenuItemCaption, + valueFilterCaption, + valueFromFilterCaption, + valueToFilterCaption, + okFilterBtnCaption, + clearFilterBtnCaption, + cancelFilterBtnCaption, + morePagesBtnCaption, + noDataFoundText, + headCellRender, + dataCellRender, + rowExpandRender, + valueFormatter, + onOrderChanged, + onFilterChanged, + onPagesCountChanged +}) => { + //Собственное состояние - фильтруемая колонка + const [filterColumn, setFilterColumn] = useState(null); + + //Собственное состояние - развёрнутые строки + const [expanded, setExpanded] = useState({}); + + //Описание фильтруемой колонки + const filterColumnDef = filterColumn ? columnsDef.find(columnDef => columnDef.name == filterColumn) || null : null; + + //Значения фильтра фильтруемой колонки + const [filterColumnFrom, filterColumnTo] = filterColumn + ? (() => { + const filter = filters.find(filter => filter.name == filterColumn); + return filter ? [filter.from == null ? "" : filter.from, filter.to == null ? "" : filter.to] : ["", ""]; + })() + : ["", ""]; + + //Определение списка видимых колонок + const visibleColumns = useMemo(() => columnsDef.filter(columnDef => columnDef.visible === true), [columnsDef]); + + //Определение количества видимых колонок + const visibleColumnsCount = useMemo(() => visibleColumns.length + (expandable === true ? 1 : 0), [visibleColumns, expandable]); + + //Выравнивание в зависимости от типа данных + const getAlignByDataType = dataType => + dataType === P8P_TABLE_DATA_TYPE.DATE ? "center" : dataType === P8P_TABLE_DATA_TYPE.NUMB ? "right" : "left"; + + //Упорядочение содержимого в зависимости от типа данных + const getJustifyContentByDataType = dataType => + dataType === P8P_TABLE_DATA_TYPE.DATE ? "center" : dataType === P8P_TABLE_DATA_TYPE.NUMB ? "flex-end" : "flex-start"; + + //Отработка нажатия на элемент пункта меню + const handleToolBarItemClick = (action, columnName) => { + switch (action) { + case P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS.ORDER_TOGGLE: { + const colOrder = orders.find(o => o.name == columnName); + const newDirection = + colOrder?.direction == P8P_TABLE_COLUMN_ORDER_DIRECTIONS.ASC + ? P8P_TABLE_COLUMN_ORDER_DIRECTIONS.DESC + : colOrder?.direction == P8P_TABLE_COLUMN_ORDER_DIRECTIONS.DESC + ? null + : P8P_TABLE_COLUMN_ORDER_DIRECTIONS.ASC; + if (onOrderChanged) onOrderChanged({ columnName, direction: newDirection }); + break; + } + case P8P_TABLE_COLUMN_TOOL_BAR_ACTIONS.FILTER_TOGGLE: + setFilterColumn(columnName); + break; + } + }; + + //Отработка нажатия на пункты меню + const handleMenuItemClick = (action, columnName) => { + switch (action) { + case P8P_TABLE_COLUMN_MENU_ACTIONS.ORDER_ASC: + onOrderChanged({ columnName, direction: P8P_TABLE_COLUMN_ORDER_DIRECTIONS.ASC }); + break; + case P8P_TABLE_COLUMN_MENU_ACTIONS.ORDER_DESC: + onOrderChanged({ columnName, direction: P8P_TABLE_COLUMN_ORDER_DIRECTIONS.DESC }); + break; + case P8P_TABLE_COLUMN_MENU_ACTIONS.FILTER: + setFilterColumn(columnName); + break; + } + }; + + //Отработка ввода значения фильтра колонки + const handleFilterOk = (columnName, from, to) => { + if (onFilterChanged) onFilterChanged({ columnName, from: from === "" ? null : from, to: to === "" ? null : to }); + setFilterColumn(null); + }; + + //Отработка очистки значения фильтра колонки + const handleFilterClear = columnName => { + if (onFilterChanged) onFilterChanged({ columnName, from: null, to: null }); + setFilterColumn(null); + }; + + //Отработка отмены ввода значения фильтра колонки + const handleFilterCancel = () => { + setFilterColumn(null); + }; + + //Отработка нажатия на элемент сводного фильтра + const handleFilterChipClick = columnName => setFilterColumn(columnName); + + //Отработка удаления элемента сводного фильтра + const handleFilterChipDelete = columnName => (onFilterChanged ? onFilterChanged({ columnName, from: null, to: null }) : null); + + //Отработка нажатия на кнопку догрузки страницы + const handleMorePagesBtnClick = () => { + if (onPagesCountChanged) onPagesCountChanged(); + }; + + //Отработка нажатия на кнопку раскрытия элемента + const handleExpandClick = rowIndex => { + if (expanded[rowIndex] === true) + setExpanded(pv => { + let res = { ...pv }; + delete res[rowIndex]; + return res; + }); + else setExpanded(pv => ({ ...pv, [rowIndex]: true })); + }; + + //При перезагрузке данных + useEffect(() => { + if (reloading) setExpanded({}); + }, [reloading]); + + //Генерация содержимого + return ( + <> + {filterColumn ? ( + + ) : null} + {Array.isArray(filters) && filters.length > 0 ? ( + + ) : null} + + + + + {expandable && rowExpandRender ? : null} + {visibleColumns.map((columnDef, j) => { + let customRender = {}; + if (headCellRender) customRender = headCellRender({ columnDef }) || {}; + return ( + + + {customRender.data ? customRender.data : columnDef.caption} + + + + + ); + })} + + + + {rows.length > 0 + ? rows.map((row, i) => ( + + + {expandable && rowExpandRender ? ( + + handleExpandClick(i)}> + {expanded[i] === true ? "keyboard_arrow_down" : "keyboard_arrow_right"} + + + ) : null} + {visibleColumns.map((columnDef, j) => { + let customRender = {}; + if (dataCellRender) customRender = dataCellRender({ row, columnDef }) || {}; + return ( + + {customRender.data + ? customRender.data + : valueFormatter + ? valueFormatter({ value: row[columnDef.name], columnDef }) + : row[columnDef.name]} + + ); + })} + + {expandable && rowExpandRender && expanded[i] === true ? ( + + + {rowExpandRender({ columnsDef, row })} + + + ) : null} + + )) + : null} + +
+ {rows.length == 0 ? ( + noDataFoundText && !reloading ? ( + + ) : null + ) : morePages ? ( + + + + ) : null} +
+ + ); +}; + +//Контроль свойств - Таблица +P8PTable.propTypes = { + columnsDef: PropTypes.arrayOf( + PropTypes.shape({ + name: PropTypes.string.isRequired, + caption: PropTypes.string.isRequired, + order: PropTypes.bool.isRequired, + filter: PropTypes.bool.isRequired, + dataType: PropTypes.string.isRequired, + values: PropTypes.array + }) + ).isRequired, + rows: PropTypes.array.isRequired, + orders: PropTypes.arrayOf( + PropTypes.shape({ + name: PropTypes.string.isRequired, + direction: PropTypes.string.isRequired + }) + ).isRequired, + filters: PropTypes.arrayOf(P8P_FILTER_SHAPE).isRequired, + size: PropTypes.string, + morePages: PropTypes.bool.isRequired, + reloading: PropTypes.bool.isRequired, + expandable: PropTypes.bool, + orderAscMenuItemCaption: PropTypes.string.isRequired, + orderDescMenuItemCaption: PropTypes.string.isRequired, + filterMenuItemCaption: PropTypes.string.isRequired, + valueFilterCaption: PropTypes.string.isRequired, + valueFromFilterCaption: PropTypes.string.isRequired, + valueToFilterCaption: PropTypes.string.isRequired, + okFilterBtnCaption: PropTypes.string.isRequired, + clearFilterBtnCaption: PropTypes.string.isRequired, + cancelFilterBtnCaption: PropTypes.string.isRequired, + morePagesBtnCaption: PropTypes.string.isRequired, + noDataFoundText: PropTypes.string, + headCellRender: PropTypes.func, + dataCellRender: PropTypes.func, + rowExpandRender: PropTypes.func, + valueFormatter: PropTypes.func, + onOrderChanged: PropTypes.func, + onFilterChanged: PropTypes.func, + onPagesCountChanged: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PTable, P8P_TABLE_DATA_TYPE, P8P_TABLE_SIZE, P8P_FILTER_SHAPE }; diff --git a/app/context/application.js b/app/context/application.js new file mode 100644 index 0000000..aceb819 --- /dev/null +++ b/app/context/application.js @@ -0,0 +1,149 @@ +/* + Парус 8 - Панели мониторинга + Контекст: Приложение +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useReducer, createContext, useEffect, useContext, useCallback } from "react"; //ReactJS +import PropTypes from "prop-types"; //Контроль свойств компонента +import { getDisplaySize } from "../core/utils"; //Вспомогательные функции +import { APP_AT, INITIAL_STATE, applicationReducer } from "./application_reducer"; //Редьюсер состояния +import { MessagingСtx } from "./messaging"; //Контекст отображения сообщений +import { BackEndСtx } from "./backend"; //Контекст взаимодействия с сервером +import { ERROR } from "../../app.text"; //Текстовые ресурсы и константы + +//--------- +//Константы +//--------- + +//Клиентский API "ПАРУС 8 Онлайн" +const P8O_API = window.parent?.parus?.clientApi; + +//-------------------------------- +//Вспомогательные классы и функции +//-------------------------------- + +//---------------- +//Интерфейс модуля +//---------------- + +//Контекст приложения +export const ApplicationСtx = createContext(); + +//Провайдер контекста приложения +export const ApplicationContext = ({ children }) => { + //Подключим редьюсер состояния + const [state, dispatch] = useReducer(applicationReducer, INITIAL_STATE); + + //Подключение к контексту взаимодействия с сервером + const { getConfig, getRespPayload } = useContext(BackEndСtx); + + //Подключение к контексту отображения сообщений + const { showMsgErr } = useContext(MessagingСtx); + + //Установка флага инициализированности приложения + const setInitialized = () => dispatch({ type: APP_AT.SET_INITIALIZED }); + + //Установка текущего размера экрана + const setDisplaySize = displaySize => dispatch({ type: APP_AT.SET_DISPLAY_SIZE, payload: displaySize }); + + //Установка списка панелей + const setPanels = panels => dispatch({ type: APP_AT.LOAD_PANELS, payload: panels }); + + //Поиск раздела по имени + const findPanelByName = name => state.panels.find(panel => panel.name == name); + + //Отображение раздела "ПАРУС 8 Онлайн" + const pOnlineShowUnit = useCallback( + ({ unitCode, showMethod = "main", inputParameters }) => { + if (P8O_API) P8O_API.fn.openDocumentModal({ unitcode: unitCode, method: showMethod, inputParameters }); + else showMsgErr(ERROR.P8O_API_UNAVAILABLE); + }, + [showMsgErr] + ); + + //Отображение документа "ПАРУС 8 Онлайн" + const pOnlineShowDocument = useCallback( + ({ unitCode, document, showMethod = "main", inRnParameter = "in_RN" }) => { + if (P8O_API) + P8O_API.fn.openDocumentModal({ unitcode: unitCode, method: showMethod, inputParameters: [{ name: inRnParameter, value: document }] }); + else showMsgErr(ERROR.P8O_API_UNAVAILABLE); + }, + [showMsgErr] + ); + + //Отображение словаря "ПАРУС 8 Онлайн" + const pOnlineShowDictionary = useCallback( + ({ unitCode, showMethod = "main", inputParameters, callBack }) => { + if (P8O_API) + P8O_API.fn.openDictionary({ unitcode: unitCode, method: showMethod, inputParameters }, res => (callBack ? callBack(res) : null)); + else showMsgErr(ERROR.P8O_API_UNAVAILABLE); + }, + [showMsgErr] + ); + + //Исполнение пользовательской процедуры "ПАРУС 8 Онлайн" + const pOnlineUserProcedure = useCallback( + ({ code, inputParameters, callBack }) => { + if (P8O_API) P8O_API.fn.performUserProcedureSync({ code, inputParameters }, res => (callBack ? callBack(res) : null)); + else showMsgErr(ERROR.P8O_API_UNAVAILABLE); + }, + [showMsgErr] + ); + + //Исполнение пользовательского отчёта "ПАРУС 8 Онлайн" + const pOnlineUserReport = useCallback( + ({ code, inputParameters }) => { + if (P8O_API) P8O_API.fn.performUserReport({ code, inputParameters }); + else showMsgErr(ERROR.P8O_API_UNAVAILABLE); + }, + [showMsgErr] + ); + + //Инициализация приложения + const initApp = useCallback(async () => { + //Читаем конфигурацию с сервера + let res = await getConfig(); + //Сохраняем список панелей + setPanels(getRespPayload(res)?.Panels?.Panel); + //Установим флаг завершения инициализации + setInitialized(); + }, [getConfig, getRespPayload]); + + //Обработка подключения контекста к странице + useEffect(() => { + if (!state.initialized) { + //Слушаем изменение размеров окна + window.addEventListener("resize", () => { + setDisplaySize(getDisplaySize()); + }); + //Инициализируем приложение + initApp(); + } + }, [state.initialized, initApp]); + + //Вернём компонент провайдера + return ( + + {children} + + ); +}; + +//Контроль свойств - Провайдер контекста приложения +ApplicationContext.propTypes = { + children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]) +}; diff --git a/app/context/application_reducer.js b/app/context/application_reducer.js new file mode 100644 index 0000000..54930fe --- /dev/null +++ b/app/context/application_reducer.js @@ -0,0 +1,68 @@ +/* + Парус 8 - Панели мониторинга + Контекст: Приложение - редьюсер состояния +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { getDisplaySize } from "../core/utils"; //Вспомогательные функции + +//--------- +//Константы +//--------- + +//Типы действий +const APP_AT = { + LOAD_PANELS: "LOAD_PANELS", //Загрузка списка панелей + SET_INITIALIZED: "SET_INITIALIZED", //Установка флага инициализированности приложения + SET_DISPLAY_SIZE: "SET_DISPLAY_SIZE" //Установка текущего типового размера экрана +}; + +//Состояние приложения по умолчанию +const INITIAL_STATE = { + displaySize: getDisplaySize(), + panels: [], + panelsLoaded: false, + initialized: false +}; + +//----------- +//Тело модуля +//----------- + +//Обработчики действий +const handlers = { + //Загрузка списка панелей + [APP_AT.LOAD_PANELS]: (state, { payload }) => { + let panels = []; + if (payload && Array.isArray(payload)) for (let p of payload) panels.push({ ...p }); + return { + ...state, + panels, + panelsLoaded: true + }; + }, + //Установка текущего типового размера экрана + [APP_AT.SET_INITIALIZED]: state => ({ ...state, initialized: true }), + //Установка текущего типового размера экрана + [APP_AT.SET_DISPLAY_SIZE]: (state, { payload }) => ({ ...state, displaySize: payload }), + //Обработчик по умолчанию + DEFAULT: state => state +}; + +//---------------- +//Интерфейс модуля +//---------------- + +//Константы +export { APP_AT, INITIAL_STATE }; + +//Редьюсер состояния +export const applicationReducer = (state, action) => { + //Подберём обработчик + const handle = handlers[action.type] || handlers.DEFAULT; + //Исполним его + return handle(state, action); +}; diff --git a/app/context/backend.js b/app/context/backend.js new file mode 100644 index 0000000..c9f67ff --- /dev/null +++ b/app/context/backend.js @@ -0,0 +1,104 @@ +/* + Парус 8 - Панели мониторинга + Контекст: Взаимодействие с серверным API +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { createContext, useContext, useCallback } from "react"; //ReactJS +import PropTypes from "prop-types"; //Контроль свойств компонента +import client from "../core/client"; //Клиент для взаимодействия с сервером +import { MessagingСtx } from "./messaging"; //Контекст сообщений + +//---------------- +//Интерфейс модуля +//---------------- + +//Контекст взаимодействия с серверным API +export const BackEndСtx = createContext(); + +//Провайдер контекста взаимодействия с серверным API +export const BackEndContext = ({ children }) => { + //Подключение к контексту сообщений + const { showLoader, hideLoader, showMsgErr } = useContext(MessagingСtx); + + //Проверка ответа на наличие ошибки + const isRespErr = useCallback(resp => client.isRespErr(resp), []); + + //Извлечение ошибки из ответа + const getRespErrMessage = useCallback(resp => client.getRespErrMessage(resp), []); + + //Извлечение полезного содержимого из ответа + const getRespPayload = useCallback(resp => client.getRespPayload(resp), []); + + //Запуск хранимой процедуры + const executeStored = useCallback( + async ({ + stored, + args, + respArg, + loader = true, + loaderMessage = "", + throwError = true, + showErrorMessage = true, + fullResponse = false, + spreadOutArguments = true + } = {}) => { + try { + if (loader !== false) showLoader(loaderMessage); + let result = await client.executeStored({ stored, args, respArg, throwError, spreadOutArguments }); + if (fullResponse === true || isRespErr(result)) return result; + else return result.XPAYLOAD; + } catch (e) { + if (showErrorMessage) showMsgErr(e.message); + throw e; + } finally { + if (loader !== false) hideLoader(); + } + }, + [showLoader, hideLoader, isRespErr, showMsgErr] + ); + + //Загрузка настроек панелей + const getConfig = useCallback( + async ({ loader = true, loaderMessage = "", throwError = true, showErrorMessage = true } = {}) => { + try { + if (loader !== false) showLoader(loaderMessage); + let result = await client.getConfig({ throwError }); + return result; + } catch (e) { + if (showErrorMessage) showMsgErr(e.message); + throw e; + } finally { + if (loader !== false) hideLoader(); + } + }, + [showLoader, hideLoader, showMsgErr] + ); + + //Вернём компонент провайдера + return ( + + {children} + + ); +}; + +//Контроль свойств - Провайдер контекста взаимодействия с серверным API +BackEndContext.propTypes = { + children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]) +}; diff --git a/app/context/messaging.js b/app/context/messaging.js new file mode 100644 index 0000000..66802d7 --- /dev/null +++ b/app/context/messaging.js @@ -0,0 +1,109 @@ +/* + Парус 8 - Панели мониторинга + Контекст: Сообщения +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useReducer, createContext, useCallback } from "react"; //ReactJS +import PropTypes from "prop-types"; //Контроль свойств компонента +import { P8PAppProgress } from "../components/p8p_app_progress"; //Индикатор процесса +import { P8PAppMessage } from "../components/p8p_app_message"; //Диалог сообщения +import { MSG_AT, MSG_DLGT, INITIAL_STATE, messagingReducer } from "./messaging_reducer"; //Редьюсер состояния +import { TITLES, TEXTS, BUTTONS } from "../../app.text"; //Текстовые ресурсы и константы + +//---------------- +//Интерфейс модуля +//---------------- + +//Контекст сообщений +export const MessagingСtx = createContext(); + +//Провайдер контекста сообщений +export const MessagingContext = ({ children }) => { + //Подключим редьюсер состояния + const [state, dispatch] = useReducer(messagingReducer, INITIAL_STATE); + + //Отображение загрузчика + const showLoader = useCallback(message => dispatch({ type: MSG_AT.SHOW_LOADER, payload: message }), []); + + //Сокрытие загрузчика + const hideLoader = useCallback(() => dispatch({ type: MSG_AT.HIDE_LOADER }), []); + + //Отображение сообщения + const showMsg = useCallback( + (type, text, msgOnOk = null, msgOnCancel = null) => dispatch({ type: MSG_AT.SHOW_MSG, payload: { type, text, msgOnOk, msgOnCancel } }), + [] + ); + + //Отображение сообщения - ошибка + const showMsgErr = useCallback((text, msgOnOk = null) => showMsg(MSG_DLGT.ERR, text, msgOnOk), [showMsg]); + + //Отображение сообщения - информация + const showMsgInfo = useCallback((text, msgOnOk = null) => showMsg(MSG_DLGT.INFO, text, msgOnOk), [showMsg]); + + //Отображение сообщения - предупреждение + const showMsgWarn = useCallback((text, msgOnOk = null, msgOnCancel = null) => showMsg(MSG_DLGT.WARN, text, msgOnOk, msgOnCancel), [showMsg]); + + //Сокрытие сообщения + const hideMsg = useCallback( + (cancel = false) => { + dispatch({ type: MSG_AT.HIDE_MSG }); + if (!cancel && state.msgOnOk) state.msgOnOk(); + if (cancel && state.msgOnCancel) state.msgOnCancel(); + }, + [state] + ); + + //Отработка нажатия на "ОК" в сообщении + const handleMessageOkClick = () => { + hideMsg(false); + }; + + //Отработка нажатия на "Отмена" в сообщении + const handleMessageCancelClick = () => { + hideMsg(true); + }; + + //Вернём компонент провайдера + return ( + + {state.loading ? : null} + {state.msg ? ( + + ) : null} + {children} + + ); +}; + +//Контроль свойств - Провайдер контекста сообщений +MessagingContext.propTypes = { + children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]) +}; diff --git a/app/context/messaging_reducer.js b/app/context/messaging_reducer.js new file mode 100644 index 0000000..a98febf --- /dev/null +++ b/app/context/messaging_reducer.js @@ -0,0 +1,84 @@ +/* + Парус 8 - Панели мониторинга + Контекст: Сообщения - редьюсер состояния +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { P8P_APP_MESSAGE_VARIANT } from "../components/p8p_app_message"; //Диалог сообщения + +//--------- +//Константы +//--------- + +//Типы действий +const MSG_AT = { + SHOW_LOADER: "SHOW_LOADER", //Отображение индикатора загрузки + HIDE_LOADER: "HIDE_LOADER", //Сокрытие индикатора загрузки + SHOW_MSG: "SHOW_MSG", //Отображение сообщения + HIDE_MSG: "HIDE_MSG" //Сокрытие сообщения +}; + +//Типы диалогов сообщений +const MSG_DLGT = { + INFO: P8P_APP_MESSAGE_VARIANT.INFO, //Тип диалога - информация + WARN: P8P_APP_MESSAGE_VARIANT.WARN, //Тип диалога - предупреждение + ERR: P8P_APP_MESSAGE_VARIANT.ERR //Тип диалога - ошибка +}; + +//Состояние сообщений по умолчанию +const INITIAL_STATE = { + loading: false, + loadingMessage: "", + msg: false, + msgType: MSG_DLGT.ERR, + msgText: null, + msgOnOk: null, + msgOnCancel: null +}; + +//----------- +//Тело модуля +//----------- + +//Обработчики действий +const handlers = { + //Отображение индикатора обработки данных + [MSG_AT.SHOW_LOADER]: (state, { payload }) => ({ + ...state, + loading: true, + loadingMessage: payload + }), + //Сокрытие индикатора обработки данных + [MSG_AT.HIDE_LOADER]: state => ({ ...state, loading: false }), + //Отображение сообщения + [MSG_AT.SHOW_MSG]: (state, { payload }) => ({ + ...state, + msg: true, + msgType: payload.type || MSG_DLGT.APP_ERR, + msgText: payload.text, + msgOnOk: payload.msgOnOk, + msgOnCancel: payload.msgOnCancel + }), + //Сокрытие сообщения + [MSG_AT.HIDE_MSG]: state => ({ ...state, msg: false, msgOnOk: null, msgOnCancel: null }), + //Обработчик по умолчанию + DEFAULT: state => state +}; + +//---------------- +//Интерфейс модуля +//---------------- + +//Константы +export { MSG_AT, MSG_DLGT, INITIAL_STATE }; + +//Редьюсер состояния +export const messagingReducer = (state, action) => { + //Подберём обработчик + const handle = handlers[action.type] || handlers.DEFAULT; + //Исполним его + return handle(state, action); +}; diff --git a/app/context/navigation.js b/app/context/navigation.js new file mode 100644 index 0000000..f0d79f4 --- /dev/null +++ b/app/context/navigation.js @@ -0,0 +1,123 @@ +/* + Парус 8 - Панели мониторинга + Контекст: Навигация +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { createContext, useContext } from "react"; //ReactJS +import PropTypes from "prop-types"; //Контроль свойств компонента +import { useLocation, useNavigate } from "react-router-dom"; //Роутер приложения +import queryString from "query-string"; //Работа со строкой запроса +import { ApplicationСtx } from "./application"; //Контекст приложения + +//--------- +//Константы +//--------- + +//Типовые пути +const PATHS = { + ROOT: "/" //Корень приложения +}; + +//---------------- +//Интерфейс модуля +//---------------- + +//Получение корневого пути +export const getRootLocation = () => PATHS.ROOT; + +//Контекст навигации +export const NavigationCtx = createContext(); + +//Провайдер контекста навигации +export const NavigationContext = ({ children }) => { + //Подключение к объекту роутера для управления навигацией + const location = useLocation(); + + //Подключение к объекту роутера для управления навигацией + const navigate = useNavigate(); + + //Подключение к контексту приложения + const { findPanelByName } = useContext(ApplicationСtx); + + //Проверка наличия параметров запроса + const isNavigationSearch = () => (location.search ? true : false); + + //Считываение параметров запроса + const getNavigationSearch = () => queryString.parse(location.search); + + //Проверка наличия параметров запроса (передаваемых через состояние) + const isNavigationState = () => (location.state ? true : false); + + //Считываение параметров запроса (передаваемых через состояние) + const getNavigationState = () => (isNavigationState() ? JSON.parse(location.state) : null); + + //Обновление текущей страницы + const refresh = () => window.location.reload(); + + //Возврат на предыдущую страницу + const navigateBack = () => navigate(-1); + + //Переход к адресу внутри приложения + const navigateTo = ({ path, search, state, replace = false }) => { + //Если указано куда переходить + if (path) { + //Переходим к адресу + if (state) navigate(path, { state: JSON.stringify(state), replace }); + else navigate({ pathname: path, search: queryString.stringify(search), replace }); + //Флаг успешного перехода + return true; + } + //Переход не состоялся + else return false; + }; + + //Переход к домашней страничке + const navigateRoot = state => navigateTo({ path: getRootLocation(), state }); + + //Переход к панели + const navigatePanel = (panel, state) => { + if (panel) { + let path = getRootLocation(); + path = !path.endsWith("/") && !panel.url.startsWith("/") ? `${path}/${panel.url}` : `${path}${panel.url}`; + navigateTo({ path, state }); + } else return false; + }; + + //Переход к панели по наименованию + const navigatePanelByName = (name, state) => navigatePanel(findPanelByName(name), state); + + //Переход к произвольному адресу + const navigateURL = url => { + window.open(url, "_self"); + }; + + //Вернём компонент провайдера + return ( + + {children} + + ); +}; + +//Контроль свойств - Провайдер контекста навигации +NavigationContext.propTypes = { + children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]) +}; diff --git a/app/core/client.js b/app/core/client.js new file mode 100644 index 0000000..75b808b --- /dev/null +++ b/app/core/client.js @@ -0,0 +1,216 @@ +/* + Парус 8 - Панели мониторинга + Ядро: Клиент для взаимодействия с сервером приложений "Парус 8 Онлайн" +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { XMLParser, XMLBuilder } from "fast-xml-parser"; //Конвертация XML в JSON и JSON в XML +import dayjs from "dayjs"; //Работа с датами +import { SYSTEM } from "../../app.config"; //Настройки приложения + +//--------- +//Константы +//--------- + +//Коды функций сервера +const SRV_FN_CODE_EXEC_STORED = "EXEC_STORED"; //Код функции сервера "Запуск хранимой процедуры" + +//Типы данных сервера +const SERV_DATA_TYPE_STR = "STR"; //Тип данных "строка" +const SERV_DATA_TYPE_NUMB = "NUMB"; //Тип данных "число" +const SERV_DATA_TYPE_DATE = "DATE"; //Тип данных "дата" +const SERV_DATA_TYPE_CLOB = "CLOB"; //Тип данных "текст" + +//Состояния ответов сервера +const RESP_STATUS_OK = "OK"; //Успех +const RESP_STATUS_ERR = "ERR"; //Ошибка + +//Типовые ошибки клиента +const ERR_APPSERVER = "Ошибка сервера приложений"; //Общая ошибка клиента +const ERR_UNEXPECTED = "Неожиданный ответ сервера"; //Неожиданный ответ сервера +const ERR_NETWORK = "Ошибка соединения с сервером"; //Ошибка сети + +//Типовые пути конвертации в массив (при переводе XML -> JSON) +const XML_ALWAYS_ARRAY_PATHS = [ + "XRESPOND.XPAYLOAD.XOUT_ARGUMENTS", + "XRESPOND.XPAYLOAD.XROWS", + "XRESPOND.XPAYLOAD.XCOLUMNS_DEF", + "XRESPOND.XPAYLOAD.XCOLUMNS_DEF.values" +]; + +//Типовой постфикс тега для массива (при переводе XML -> JSON) +const XML_ALWAYS_ARRAY_POSTFIX = "__SYSTEM__ARRAY__"; + +//----------- +//Тело модуля +//----------- + +//Определение типа данных значения аргумента +const getServerDataType = value => { + let res = SERV_DATA_TYPE_STR; + if (typeof value == "number") res = SERV_DATA_TYPE_NUMB; + if (value instanceof Date) res = SERV_DATA_TYPE_DATE; + return res; +}; + +//Формирование стандартного ответа - ошибка +const makeRespErr = ({ message }) => ({ SSTATUS: RESP_STATUS_ERR, SMESSAGE: message }); + +//Разбор XML +const parseXML = (xmlDoc, isArray, transformTagName) => { + return new Promise((resolve, reject) => { + try { + let opts = { + ignoreDeclaration: true, + ignoreAttributes: false, + parseAttributeValue: true, + attributeNamePrefix: "" + }; + if (isArray) opts.isArray = isArray; + if (transformTagName) opts.transformTagName = transformTagName; + const parser = new XMLParser(opts); + resolve(parser.parse(xmlDoc)); + } catch (e) { + reject(e); + } + }); +}; + +//Формирование XML +const buildXML = jsonObj => { + return new Promise((resolve, reject) => { + try { + const builder = new XMLBuilder({ ignoreAttributes: false, oneListGroup: true }); + resolve(builder.build(jsonObj)); + } catch (e) { + reject(e); + } + }); +}; + +//Проверка ответа на наличие ошибки +const isRespErr = resp => resp && resp?.SSTATUS && resp?.SSTATUS === RESP_STATUS_ERR; + +//Извлечение ошибки из ответа +const getRespErrMessage = resp => (isRespErr(resp) && resp.SMESSAGE ? resp.SMESSAGE : ""); + +//Извлечение полезного содержимого из ответа +const getRespPayload = resp => (resp && resp.XPAYLOAD ? resp.XPAYLOAD : null); + +//Исполнение действия на сервере +const executeAction = async ({ serverURL, action, payload = {}, isArray, transformTagName } = {}) => { + console.log(`EXECUTING ${action ? action : ""} ON ${serverURL} WITH PAYLOAD:`); + console.log(payload ? payload : "NO PAYLOAD"); + let response = null; + let responseJSON = null; + try { + //Сформируем типовой запрос + const rqBody = { + XREQUEST: { SACTION: action, XPAYLOAD: payload } + }; + //Выполняем запрос + response = await fetch(serverURL, { + method: "POST", + body: await buildXML(rqBody), + headers: { + "content-type": "application/xml" + } + }); + } catch (e) { + //Сетевая ошибка + throw new Error(`${ERR_NETWORK}: ${e.message}`); + } + //Проверим на наличие ошибок HTTP - если есть вернём их + if (!response.ok) throw new Error(`${ERR_APPSERVER}: ${response.statusText}`); + //Ошибок нет - пробуем разобрать + try { + let responseText = await response.text(); + //console.log("SERVER RESPONSE TEXT:"); + //console.log(responseText); + responseJSON = await parseXML(responseText, isArray, transformTagName); + } catch (e) { + //Что-то пошло не так при парсинге + throw new Error(ERR_UNEXPECTED); + } + //Разобрали, проверяем структуру ответа на обязательные атрибуты + if ( + !responseJSON?.XRESPOND || + !responseJSON?.XRESPOND?.SSTATUS || + ![RESP_STATUS_ERR, RESP_STATUS_OK].includes(responseJSON?.XRESPOND?.SSTATUS) || + (responseJSON?.XRESPOND?.SSTATUS === RESP_STATUS_OK && responseJSON?.XRESPOND?.XPAYLOAD == undefined) || + (responseJSON?.XRESPOND?.SSTATUS === RESP_STATUS_ERR && responseJSON?.XRESPOND?.SMESSAGE == undefined) + ) + throw new Error(ERR_UNEXPECTED); + //Всё хорошо - возвращаем (без корня, он не нужен) + console.log("SERVER RESPONSE JSON:"); + console.log(responseJSON.XRESPOND); + return responseJSON.XRESPOND; +}; + +//Запуск хранимой процедуры +const executeStored = async ({ stored, args, respArg, throwError = true, spreadOutArguments = false } = {}) => { + let res = null; + try { + let serverArgs = []; + if (args) + for (const arg in args) { + let typedArg = false; + if (Object.hasOwn(args[arg], "VALUE") && Object.hasOwn(args[arg], "SDATA_TYPE") && args[arg]?.SDATA_TYPE) typedArg = true; + const dataType = typedArg ? args[arg].SDATA_TYPE : getServerDataType(args[arg]); + let value = typedArg ? args[arg].VALUE : args[arg]; + if (dataType === SERV_DATA_TYPE_DATE) value = dayjs(value).format("YYYY-MM-DDTHH:mm:ss"); + serverArgs.push({ XARGUMENT: { SNAME: arg, VALUE: value, SDATA_TYPE: dataType } }); + } + res = await executeAction({ + serverURL: `${SYSTEM.SERVER}${!SYSTEM.SERVER.endsWith("/") ? "/" : ""}Process`, + action: SRV_FN_CODE_EXEC_STORED, + payload: { SSTORED: stored, XARGUMENTS: serverArgs, SRESP_ARG: respArg }, + isArray: (name, jPath) => XML_ALWAYS_ARRAY_PATHS.indexOf(jPath) !== -1 || jPath.endsWith(XML_ALWAYS_ARRAY_POSTFIX) + }); + if (spreadOutArguments === true && Array.isArray(res?.XPAYLOAD?.XOUT_ARGUMENTS)) { + let spreadArgs = {}; + for (let arg of res.XPAYLOAD.XOUT_ARGUMENTS) spreadArgs[arg.SNAME] = arg.VALUE; + delete res.XPAYLOAD.XOUT_ARGUMENTS; + res.XPAYLOAD = { ...res.XPAYLOAD, ...spreadArgs }; + } + } catch (e) { + if (throwError) throw e; + else return makeRespErr({ message: e.message }); + } + if (res.SSTATUS === RESP_STATUS_ERR && throwError === true) throw new Error(res.SMESSAGE); + return res; +}; + +//Чтение конфигурации плагина +const getConfig = async ({ throwError = true } = {}) => { + let res = null; + try { + res = await executeAction({ + serverURL: `${SYSTEM.SERVER}${!SYSTEM.SERVER.endsWith("/") ? "/" : ""}GetConfig` + }); + } catch (e) { + if (throwError) throw e; + else return makeRespErr({ message: e.message }); + } + if (res.SSTATUS === RESP_STATUS_ERR && throwError === true) throw new Error(res.SMESSAGE); + return res; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export default { + SERV_DATA_TYPE_STR, + SERV_DATA_TYPE_NUMB, + SERV_DATA_TYPE_DATE, + SERV_DATA_TYPE_CLOB, + isRespErr, + getRespErrMessage, + getRespPayload, + executeStored, + getConfig +}; diff --git a/app/core/utils.js b/app/core/utils.js new file mode 100644 index 0000000..3f50da5 --- /dev/null +++ b/app/core/utils.js @@ -0,0 +1,69 @@ +/* + Парус 8 - Панели мониторинга + Ядро: Вспомогательные функции +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { XMLBuilder } from "fast-xml-parser"; //Конвертация XML в JSON и JSON в XML +import dayjs from "dayjs"; //Работа с датами + +//--------- +//Константы +//--------- + +//Коды типовых размеров экранов +const DISPLAY_SIZE_CODE = { + XS: "XS", //eXtra Small - супер маленький экран + SM: "SM", //Small - маленький экран + MD: "MD", //Middle - средний экран + LG: "LG" //Large - большой экран +}; + +//Типовые размеры экранов +const DISPLAY_SIZE = { + [DISPLAY_SIZE_CODE.XS]: { WIDTH_FROM: 0, WIDTH_TO: 767 }, //eXtra Small - супер маленький экран < 768px + [DISPLAY_SIZE_CODE.SM]: { WIDTH_FROM: 768, WIDTH_TO: 991 }, //Small - маленький экран >= 768px + [DISPLAY_SIZE_CODE.MD]: { WIDTH_FROM: 992, WIDTH_TO: 1199 }, //Middle - средний экран >= 992px + [DISPLAY_SIZE_CODE.LG]: { WIDTH_FROM: 1200, WIDTH_TO: 1000000 } //Large - большой экран >= 1200px +}; + +//----------- +//Тело модуля +//----------- + +//Проверка существования значения +const hasValue = value => typeof value !== "undefined" && value !== null && value !== ""; + +//Проверка типа устройства +const getDisplaySize = () => { + let res = DISPLAY_SIZE_CODE.MD; + Object.keys(DISPLAY_SIZE).map(dspl => { + if (window.innerWidth >= DISPLAY_SIZE[dspl].WIDTH_FROM && window.innerWidth <= DISPLAY_SIZE[dspl].WIDTH_TO) res = dspl; + }); + return res; +}; + +//Глубокое копирование объекта +const deepCopyObject = obj => JSON.parse(JSON.stringify(obj)); + +//Конвертация объекта в Base64 XML +const object2Base64XML = (obj, builderOptions) => { + const builder = new XMLBuilder(builderOptions); + //onOrderChanged({ orders: btoa(ordersBuilder.build(newOrders)) }); + return btoa(unescape(encodeURIComponent(builder.build(obj)))); +}; + +//Форматирование даты в формат РФ +const formatDateRF = value => (value ? dayjs(value).format("DD.MM.YYYY") : null); + +//Форматирование числа в "Денежном" формате РФ +const formatNumberRFCurrency = value => (hasValue(value) ? new Intl.NumberFormat("ru-RU", { minimumFractionDigits: 2 }).format(value) : null); + +//---------------- +//Интерфейс модуля +//---------------- + +export { hasValue, getDisplaySize, deepCopyObject, object2Base64XML, formatDateRF, formatNumberRFCurrency }; diff --git a/app/index.js b/app/index.js new file mode 100644 index 0000000..194ccf5 --- /dev/null +++ b/app/index.js @@ -0,0 +1,19 @@ +/* + Парус 8 - Панели мониторинга + Точка входа в приложение +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //React +import { createRoot } from "react-dom/client"; //Работа с DOM в React +import Root from "./root"; //Корневой компонент приложения + +//----------- +//Точка входа +//----------- + +const root = createRoot(document.getElementById("app-content")); +root.render(); diff --git a/app/panels/dummy/dummy.js b/app/panels/dummy/dummy.js new file mode 100644 index 0000000..2d29dbc --- /dev/null +++ b/app/panels/dummy/dummy.js @@ -0,0 +1,32 @@ +/* + Парус 8 - Панели мониторинга - Загулшка + Панель-заглушка +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useContext } from "react"; //Классы React +import { NavigationCtx } from "../../context/navigation"; //Контекст навигации +import { P8PAppErrorPage } from "../../components/p8p_app_error_page"; //Страница с ошибкой +import { BUTTONS, ERROR } from "../../../app.text"; //Текстовые ресурсы и константы + +//----------- +//Тело модуля +//----------- + +//Заглушка +const Dummy = () => { + //Подключение к контексту навигации + const { navigateBack } = useContext(NavigationCtx); + + //Генерация содержимого + return navigateBack()} navigateCaption={BUTTONS.NAVIGATE_BACK} />; +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { Dummy }; diff --git a/app/panels/dummy/index.js b/app/panels/dummy/index.js new file mode 100644 index 0000000..82c10b5 --- /dev/null +++ b/app/panels/dummy/index.js @@ -0,0 +1,16 @@ +/* + Парус 8 - Панели мониторинга - Заглушка + Панель-заглушка: точка входа +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { Dummy } from "./dummy"; //Панель-заглушка + +//---------------- +//Интерфейс модуля +//---------------- + +export const RootClass = Dummy; diff --git a/app/panels/prj_fin/index.js b/app/panels/prj_fin/index.js new file mode 100644 index 0000000..d2be0ee --- /dev/null +++ b/app/panels/prj_fin/index.js @@ -0,0 +1,16 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Экономика проектов + Панель мониторинга: Точка входа +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { PrjFin } from "./prj_fin"; //Корневая панель экономики проекта + +//---------------- +//Интерфейс модуля +//---------------- + +export const RootClass = PrjFin; diff --git a/app/panels/prj_fin/prj_fin.js b/app/panels/prj_fin/prj_fin.js new file mode 100644 index 0000000..f3f03da --- /dev/null +++ b/app/panels/prj_fin/prj_fin.js @@ -0,0 +1,59 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Экономика проектов + Панель мониторинга: Корневая панель экономики проектов +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState } from "react"; //Классы React +import { Box } from "@mui/material"; //Интерфейсные компоненты +import { P8PFullScreenDialog } from "../../components/p8p_fullscreen_dialog"; //Полноэкранный диалог +import { Projects } from "./projects"; //Список проектов +import { Stages } from "./stages"; //Список этапов проекта + +//----------- +//Тело модуля +//----------- + +//Корневая панель экономики проекта +const PrjFin = () => { + //Собственное состояние + const [prjFinPanel, setPrjFinPanel] = useState({ + selectedProject: null, + stagesFilters: [] + }); + + //При открытии списка этапов проекта + const handleStagesOpen = ({ project = {}, filters = [] } = {}) => { + setPrjFinPanel(pv => ({ ...pv, selectedProject: { ...project }, stagesFilters: [...filters] })); + }; + + //При закрытии списка этапов проекта + const handleStagesClose = () => { + setPrjFinPanel(pv => ({ ...pv, selectedProject: null, stagesFilters: [] })); + }; + + //Генерация содержимого + return ( + + + {prjFinPanel.selectedProject ? ( + + + + ) : null} + + ); +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { PrjFin }; diff --git a/app/panels/prj_fin/projects.js b/app/panels/prj_fin/projects.js new file mode 100644 index 0000000..b920ad7 --- /dev/null +++ b/app/panels/prj_fin/projects.js @@ -0,0 +1,339 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Экономика проектов + Панель мониторинга: Список проктов +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Grid, Icon, Stack, Link, Button, Table, TableBody, TableRow, TableCell, Typography, Box, Paper, IconButton } from "@mui/material"; //Интерфейсные компоненты +import { hasValue, formatDateRF, formatNumberRFCurrency, object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции +import { BUTTONS, TEXTS, INPUTS } from "../../../app.text"; //Тектовые ресурсы и константы +import { P8PDataGrid, P8PDATA_GRID_SIZE } from "../../components/p8p_data_grid"; //Таблица данных +import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером +import { ApplicationСtx } from "../../context/application"; //Контекст приложения +import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений + +//----------------------- +//Вспомогательные функции +//----------------------- + +//Количество записей на странице +const PAGE_SIZE = 50; + +//Формирование значения для колонки "Состояние проекта" +const formatPrjStateValue = (value, addText = false) => { + const [text, icon] = + value == 0 + ? ["Зарегистрирован", "app_registration"] + : value == 1 + ? ["Открыт", "lock_open"] + : value == 2 + ? ["Остановлен", "do_not_disturb_on"] + : value == 3 + ? ["Закрыт", "lock_outline"] + : value == 4 + ? ["Согласован", "thumb_up_alt"] + : ["Исполнение прекращено", "block"]; + return ( + + {icon} + {addText == true ? text : null} + + ); +}; + +//Формирование значения для контрольных колонок +const formatCtrlValue = (value, addText = false) => { + if (hasValue(value)) { + const [text, icon, color] = value == 0 ? ["В норме", "done", "green"] : ["Требует внимания", "error", "red"]; + return ( + + + {icon} + + {addText == true ? text : null} + + ); + } else return value; +}; + +//Форматирование значений колонок +const valueFormatter = ({ value, columnDef }) => { + switch (columnDef.name) { + case "NSTATE": + return formatPrjStateValue(value, true); + case "DBEGPLAN": + case "DENDPLAN": + return formatDateRF(value); + case "NCTRL_FIN": + case "NCTRL_CONTR": + case "NCTRL_COEXEC": + case "NCTRL_PERIOD": + case "NCTRL_COST": + case "NCTRL_ACT": + return formatCtrlValue(value, true); + } + return value; +}; + +//Генерация представления ячейки заголовка +const headCellRender = ({ columnDef }) => { + switch (columnDef.name) { + case "NSTATE": + case "NCTRL_FIN": + case "NCTRL_CONTR": + case "NCTRL_COEXEC": + case "NCTRL_PERIOD": + case "NCTRL_COST": + case "NCTRL_ACT": + return { + stackProps: { justifyContent: "center" }, + cellProps: { align: "center" } + }; + } +}; + +//Генерация представления ячейки c данными +const dataCellRender = ({ row, columnDef }, handleStagesOpen) => { + switch (columnDef.name) { + case "SCODE": + case "SNAME_USL": + return { + data: ( + handleStagesOpen({ project: row })}> + {row[columnDef.name]} + + ) + }; + case "NSTATE": + return { + cellProps: { align: "center" }, + data: formatPrjStateValue(row[columnDef.name], false) + }; + case "NCTRL_FIN": + case "NCTRL_CONTR": + case "NCTRL_COEXEC": + case "NCTRL_PERIOD": + case "NCTRL_COST": + case "NCTRL_ACT": + return { + cellProps: { align: "center" }, + data: hasValue(row[columnDef.name]) ? ( + handleStagesOpen({ project: row, filters: [{ name: columnDef.name, from: row[columnDef.name] }] })}> + {formatCtrlValue(row[columnDef.name], false)} + + ) : null + }; + } +}; + +//Генерация представления расширения строки +const rowExpandRender = ({ columnsDef, row }, pOnlineShowDocument, showProjectPayNotes, handleStagesOpen) => { + const cardColumns = columnsDef.filter( + columnDef => + columnDef.visible == false && + columnDef.name != "NRN" && + !columnDef.name.startsWith("SLNK_UNIT_") && + !columnDef.name.startsWith("NLNK_DOCUMENT_") && + hasValue(row[columnDef.name]) + ); + const formatColumnValue = (name, value) => + name.startsWith("N") ? formatNumberRFCurrency(value) : name.startsWith("D") ? formatDateRF(value) : value; + return ( + + + + + + + + + + + + + {cardColumns.map((cardColumn, i) => ( + + + + {cardColumn.caption}: + + + + {hasValue(row[`SLNK_UNIT_${cardColumn.name}`]) && hasValue(row[`NLNK_DOCUMENT_${cardColumn.name}`]) ? ( + { + if (["NFIN_IN", "NFIN_OUT"].includes(cardColumn.name)) + showProjectPayNotes(row.NRN, row[`NLNK_DOCUMENT_${cardColumn.name}`]); + else + pOnlineShowDocument({ + unitCode: row[`SLNK_UNIT_${cardColumn.name}`], + document: row[`NLNK_DOCUMENT_${cardColumn.name}`] + }); + }} + > + + {formatColumnValue(cardColumn.name, row[cardColumn.name])} + + + ) : ( + + {formatColumnValue(cardColumn.name, row[cardColumn.name])} + + )} + + + ))} + +
+
+
+
+
+ ); +}; + +//----------- +//Тело модуля +//----------- + +//Список проектов +const Projects = ({ onStagesOpen }) => { + //Собственное состояние + const [projectsDataGrid, setProjectsDataGrid] = useState({ + dataLoaded: false, + columnsDef: [], + filters: null, + orders: null, + rows: [], + reload: true, + pageNumber: 1, + morePages: true + }); + + //Подключение к контексту взаимодействия с сервером + const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); + + //Подключение к контексту приложения + const { pOnlineShowDocument, pOnlineShowUnit } = useContext(ApplicationСtx); + + //Подключение к контексту сообщений + const { showMsgErr } = useContext(MessagingСtx); + + //Загрузка данных проектов с сервера + const loadProjects = useCallback(async () => { + if (projectsDataGrid.reload) { + const data = await executeStored({ + stored: "PKG_P8PANELS_PROJECTS.LIST", + args: { + CFILTERS: { VALUE: object2Base64XML(projectsDataGrid.filters, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, + CORDERS: { VALUE: object2Base64XML(projectsDataGrid.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, + NPAGE_NUMBER: projectsDataGrid.pageNumber, + NPAGE_SIZE: PAGE_SIZE, + NINCLUDE_DEF: projectsDataGrid.dataLoaded ? 0 : 1 + }, + respArg: "COUT" + }); + setProjectsDataGrid(pv => ({ + ...pv, + columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef, + rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])], + dataLoaded: true, + reload: false, + morePages: (data.XROWS || []).length >= PAGE_SIZE + })); + } + }, [ + projectsDataGrid.reload, + projectsDataGrid.filters, + projectsDataGrid.orders, + projectsDataGrid.dataLoaded, + projectsDataGrid.pageNumber, + executeStored, + SERV_DATA_TYPE_CLOB + ]); + + //Отображение журнала платежей по этапу проекта + const showProjectPayNotes = async (project, direction) => { + const data = await executeStored({ + stored: "PKG_P8PANELS_PROJECTS.SELECT_FIN", + args: { NRN: project, NDIRECTION: direction } + }); + if (data.NIDENT) pOnlineShowUnit({ unitCode: "PayNotes", inputParameters: [{ name: "in_SelectList_Ident", value: data.NIDENT }] }); + else showMsgErr(TEXTS.NO_DATA_FOUND); + }; + + //При изменении состояния фильтра + const handleFilterChanged = ({ filters }) => setProjectsDataGrid(pv => ({ ...pv, filters: [...filters], pageNumber: 1, reload: true })); + + //При изменении состояния сортировки + const handleOrderChanged = ({ orders }) => setProjectsDataGrid(pv => ({ ...pv, orders: [...orders], pageNumber: 1, reload: true })); + + //При изменении количества отображаемых страниц + const handlePagesCountChanged = () => setProjectsDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true })); + + //При открытии списка этапов + const handleStagesOpen = ({ project, filters }) => (onStagesOpen ? onStagesOpen({ project, filters }) : null); + + //При необходимости обновить данные + useEffect(() => { + loadProjects(); + }, [projectsDataGrid.reload, loadProjects]); + + //Генерация содержимого + return ( + <> + {projectsDataGrid.dataLoaded ? ( + dataCellRender(prms, handleStagesOpen)} + rowExpandRender={prms => rowExpandRender(prms, pOnlineShowDocument, showProjectPayNotes, handleStagesOpen)} + valueFormatter={valueFormatter} + onOrderChanged={handleOrderChanged} + onFilterChanged={handleFilterChanged} + onPagesCountChanged={handlePagesCountChanged} + /> + ) : null} + + ); +}; + +//Контроль свойств - Список проектов +Projects.propTypes = { + onStagesOpen: PropTypes.func +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { Projects }; diff --git a/app/panels/prj_fin/stage_arts.js b/app/panels/prj_fin/stage_arts.js new file mode 100644 index 0000000..e986e0d --- /dev/null +++ b/app/panels/prj_fin/stage_arts.js @@ -0,0 +1,201 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Экономика проектов + Панель мониторинга: Калькуляция этапа проекта +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Box, Icon, Stack, Link } from "@mui/material"; //Интерфейсные компоненты +import { hasValue, formatNumberRFCurrency, object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции +import { BUTTONS, TEXTS, INPUTS } from "../../../app.text"; //Тектовые ресурсы и константы +import { P8PDataGrid, P8PDATA_GRID_SIZE, P8PDATA_GRID_FILTER_SHAPE } from "../../components/p8p_data_grid"; //Таблица данных +import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером +import { ApplicationСtx } from "../../context/application"; //Контекст приложения +import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений + +//----------------------- +//Вспомогательные функции +//----------------------- + +//Формирование значения для контрольных колонок +const formatCtrlValue = (value, addText = false) => { + if (hasValue(value)) { + const [text, icon, color] = value == 0 ? ["В норме", "done", "green"] : ["Требует внимания", "error", "red"]; + return ( + + + {icon} + + {addText == true ? text : null} + + ); + } else return value; +}; + +//Форматирование значений колонок +const valueFormatter = ({ value, columnDef }) => { + switch (columnDef.name) { + case "NPLAN": + case "NCOST_FACT": + case "NCONTR": + return formatNumberRFCurrency(value); + case "NCTRL_COST": + case "NCTRL_CONTR": + return formatCtrlValue(value, true); + } + return value; +}; + +//Генерация представления ячейки c данными +const dataCellRender = ({ row, columnDef }, showStageArtCostNotes, showStageArtContracts) => { + switch (columnDef.name) { + case "NCOST_FACT": + case "NCONTR": + return { + data: row[columnDef.name] ? ( + (columnDef.name === "NCOST_FACT" ? showStageArtCostNotes(row.NRN) : showStageArtContracts(row.NRN))} + > + {formatNumberRFCurrency(row[columnDef.name])} + + ) : null + }; + case "NCTRL_COST": + case "NCTRL_CONTR": + return { + data: ( + +
+ {formatNumberRFCurrency(row[columnDef.name === "NCTRL_COST" ? "NCOST_DIFF" : "NCONTR_LEFT"])} +
+ {formatCtrlValue(row[columnDef.name], false)} +
+ ) + }; + } +}; + +//----------- +//Тело модуля +//----------- + +//Калькуляция этапа проекта +const StageArts = ({ stage, filters }) => { + //Собственное состояние + const [stageArtsDataGrid, setStageArtsDataGrid] = useState({ + dataLoaded: false, + columnsDef: [], + filters: [...filters], + rows: [], + reload: true + }); + + //Подключение к контексту взаимодействия с сервером + const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); + + //Подключение к контексту приложения + const { pOnlineShowUnit } = useContext(ApplicationСtx); + + //Подключение к контексту сообщений + const { showMsgErr } = useContext(MessagingСtx); + + //Загрузка данных калькуляции этапа с сервера + const loadStageArts = useCallback(async () => { + if (stageArtsDataGrid.reload) { + const data = await executeStored({ + stored: "PKG_P8PANELS_PROJECTS.STAGE_ARTS_LIST", + args: { + NSTAGE: stage, + CFILTERS: { VALUE: object2Base64XML(stageArtsDataGrid.filters, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, + NINCLUDE_DEF: stageArtsDataGrid.dataLoaded ? 0 : 1 + }, + respArg: "COUT" + }); + setStageArtsDataGrid(pv => ({ + ...pv, + columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef, + rows: [...(data.XROWS || [])], + dataLoaded: true, + reload: false + })); + } + }, [stage, stageArtsDataGrid.reload, stageArtsDataGrid.filters, stageArtsDataGrid.dataLoaded, executeStored, SERV_DATA_TYPE_CLOB]); + + //Отображение журнала затрат по статье калькуляции + const showStageArtCostNotes = async article => { + const data = await executeStored({ + stored: "PKG_P8PANELS_PROJECTS.STAGE_ARTS_SELECT_COST_FACT", + args: { NSTAGE: stage, NFPDARTCL: article } + }); + if (data.NIDENT) pOnlineShowUnit({ unitCode: "CostNotes", inputParameters: [{ name: "in_SelectList_Ident", value: data.NIDENT }] }); + else showMsgErr(TEXTS.NO_DATA_FOUND); + }; + + //Отображение договоров по статье калькуляции + const showStageArtContracts = async article => { + const data = await executeStored({ + stored: "PKG_P8PANELS_PROJECTS.STAGE_ARTS_SELECT_CONTR", + args: { NSTAGE: stage, NFPDARTCL: article } + }); + if (data.NIDENT) pOnlineShowUnit({ unitCode: "Contracts", inputParameters: [{ name: "in_Ident", value: data.NIDENT }] }); + else showMsgErr(TEXTS.NO_DATA_FOUND); + }; + + //При изменении состояния фильтра + const handleFilterChanged = ({ filters }) => setStageArtsDataGrid(pv => ({ ...pv, filters, reload: true })); + + //При необходимости обновить данные + useEffect(() => { + loadStageArts(); + }, [stageArtsDataGrid.reload, loadStageArts]); + + //Генерация содержимого + return ( + + {stageArtsDataGrid.dataLoaded ? ( + dataCellRender(prms, showStageArtCostNotes, showStageArtContracts)} + valueFormatter={valueFormatter} + onFilterChanged={handleFilterChanged} + /> + ) : null} + + ); +}; + +//Контроль свойств - Калькуляция этапа проекта +StageArts.propTypes = { + stage: PropTypes.number.isRequired, + filters: PropTypes.arrayOf(P8PDATA_GRID_FILTER_SHAPE) +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { StageArts }; diff --git a/app/panels/prj_fin/stage_contracts.js b/app/panels/prj_fin/stage_contracts.js new file mode 100644 index 0000000..c43e9c1 --- /dev/null +++ b/app/panels/prj_fin/stage_contracts.js @@ -0,0 +1,256 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Экономика проектов + Панель мониторинга: Договоры с соисполнителями этапа проекта +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Box, Stack, Grid, Paper, Table, TableBody, TableRow, TableCell, Typography, Button, Link } from "@mui/material"; //Интерфейсные компоненты +import { hasValue, formatDateRF, formatNumberRFCurrency, object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции +import { BUTTONS, TEXTS, INPUTS } from "../../../app.text"; //Тектовые ресурсы и константы +import { P8PDataGrid, P8PDATA_GRID_SIZE, P8PDATA_GRID_FILTER_SHAPE } from "../../components/p8p_data_grid"; //Таблица данных +import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером +import { ApplicationСtx } from "../../context/application"; //Контекст приложения + +//----------------------- +//Вспомогательные функции +//----------------------- + +//Количество записей на странице +const PAGE_SIZE = 50; + +//Форматирование значений колонок +const valueFormatter = ({ value, columnDef }) => { + switch (columnDef.name) { + case "DDOC_DATE": + case "DCSTAGE_BEGIN_DATE": + case "DCSTAGE_END_DATE": + return formatDateRF(value); + } + return value; +}; + +//Генерация представления ячейки c данными +const dataCellRender = ({ row, columnDef }, pOnlineShowDocument) => { + switch (columnDef.name) { + case "SDOC_PREF": + case "SDOC_NUMB": + return { + data: ( + + pOnlineShowDocument({ + unitCode: row[`SLNK_UNIT_${columnDef.name}`], + document: row[`NLNK_DOCUMENT_${columnDef.name}`] + }) + } + > + {row[columnDef.name]} + + ) + }; + } +}; + +//Генерация представления расширения строки +const rowExpandRender = ({ columnsDef, row }, pOnlineShowDocument) => { + const cardColumns = columnsDef.filter( + columnDef => + columnDef.visible == false && + columnDef.name != "NRN" && + !columnDef.name.startsWith("SLNK_UNIT_") && + !columnDef.name.startsWith("NLNK_DOCUMENT_") && + hasValue(row[columnDef.name]) + ); + const formatColumnValue = (name, value) => + name.startsWith("N") ? formatNumberRFCurrency(value) : name.startsWith("D") ? formatDateRF(value) : value; + return ( + + + + + + + + + + + + {cardColumns.map((cardColumn, i) => ( + + + + {cardColumn.caption}:  + + + + {hasValue(row[`SLNK_UNIT_${cardColumn.name}`]) && hasValue(row[`NLNK_DOCUMENT_${cardColumn.name}`]) ? ( + + pOnlineShowDocument({ + unitCode: row[`SLNK_UNIT_${cardColumn.name}`], + document: row[`NLNK_DOCUMENT_${cardColumn.name}`] + }) + } + > + + {formatColumnValue(cardColumn.name, row[cardColumn.name])} + + + ) : ( + + {formatColumnValue(cardColumn.name, row[cardColumn.name])} + + )} + + + ))} + +
+
+
+
+
+ ); +}; + +//----------- +//Тело модуля +//----------- + +//Договоры с соисполнителями этапа проекта +const StageContracts = ({ stage, filters }) => { + //Собственное состояние + const [stageContractsDataGrid, setStageContractsDataGrid] = useState({ + dataLoaded: false, + columnsDef: [], + filters: [...filters], + orders: null, + rows: [], + reload: true, + pageNumber: 1, + morePages: true + }); + + //Подключение к контексту взаимодействия с сервером + const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); + + //Подключение к контексту приложения + const { pOnlineShowDocument } = useContext(ApplicationСtx); + + //Загрузка данных этапов с сервера + const loadStageContracts = useCallback(async () => { + if (stageContractsDataGrid.reload) { + const data = await executeStored({ + stored: "PKG_P8PANELS_PROJECTS.STAGE_CONTRACTS_LIST", + args: { + NSTAGE: stage, + CFILTERS: { + VALUE: object2Base64XML(stageContractsDataGrid.filters, { arrayNodeName: "filters" }), + SDATA_TYPE: SERV_DATA_TYPE_CLOB + }, + CORDERS: { VALUE: object2Base64XML(stageContractsDataGrid.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, + NPAGE_NUMBER: stageContractsDataGrid.pageNumber, + NPAGE_SIZE: PAGE_SIZE, + NINCLUDE_DEF: stageContractsDataGrid.dataLoaded ? 0 : 1 + }, + respArg: "COUT" + }); + setStageContractsDataGrid(pv => ({ + ...pv, + columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef, + rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])], + dataLoaded: true, + reload: false, + morePages: (data.XROWS || []).length >= PAGE_SIZE + })); + } + }, [ + stage, + stageContractsDataGrid.reload, + stageContractsDataGrid.filters, + stageContractsDataGrid.orders, + stageContractsDataGrid.dataLoaded, + stageContractsDataGrid.pageNumber, + executeStored, + SERV_DATA_TYPE_CLOB + ]); + + //При изменении состояния фильтра + const handleFilterChanged = ({ filters }) => setStageContractsDataGrid(pv => ({ ...pv, filters, pageNumber: 1, reload: true })); + + //При изменении состояния сортировки + const handleOrderChanged = ({ orders }) => setStageContractsDataGrid(pv => ({ ...pv, orders, pageNumber: 1, reload: true })); + + //При изменении количества отображаемых страниц + const handlePagesCountChanged = () => setStageContractsDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true })); + + //При необходимости обновить данные + useEffect(() => { + loadStageContracts(); + }, [stageContractsDataGrid.reload, loadStageContracts]); + + //Генерация содержимого + return ( + + {stageContractsDataGrid.dataLoaded ? ( + dataCellRender(prms, pOnlineShowDocument)} + rowExpandRender={prms => rowExpandRender(prms, pOnlineShowDocument)} + valueFormatter={valueFormatter} + onOrderChanged={handleOrderChanged} + onFilterChanged={handleFilterChanged} + onPagesCountChanged={handlePagesCountChanged} + /> + ) : null} + + ); +}; + +//Контроль свойств - Договоры с соисполнителями этапа проекта +StageContracts.propTypes = { + stage: PropTypes.number.isRequired, + filters: PropTypes.arrayOf(P8PDATA_GRID_FILTER_SHAPE) +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { StageContracts }; diff --git a/app/panels/prj_fin/stages.js b/app/panels/prj_fin/stages.js new file mode 100644 index 0000000..2d6f642 --- /dev/null +++ b/app/panels/prj_fin/stages.js @@ -0,0 +1,408 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Экономика проектов + Панель мониторинга: Список этапов проекта +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useCallback, useEffect, useContext } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Box, Icon, Stack, Grid, Paper, Table, TableBody, TableRow, TableCell, Typography, Button, IconButton, Link } from "@mui/material"; //Интерфейсные компоненты +import { hasValue, formatDateRF, formatNumberRFCurrency, object2Base64XML } from "../../core/utils"; //Вспомогательные процедуры и функции +import { BUTTONS, TEXTS, INPUTS } from "../../../app.text"; //Тектовые ресурсы и константы +import { P8PDataGrid, P8PDATA_GRID_SIZE, P8PDATA_GRID_FILTER_SHAPE } from "../../components/p8p_data_grid"; //Таблица данных +import { P8PFullScreenDialog } from "../../components/p8p_fullscreen_dialog"; //Полноэкранный диалог +import { StageArts } from "./stage_arts"; //Калькуляция этапа проекта +import { StageContracts } from "./stage_contracts"; //Договоры с соисполнителями этапа проекта +import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером +import { ApplicationСtx } from "../../context/application"; //Контекст приложения +import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений + +//----------------------- +//Вспомогательные функции +//----------------------- + +//Количество записей на странице +const PAGE_SIZE = 50; + +//Формирование значения для колонки "Состояние" +const formatStageStatusValue = (value, addText = false) => { + const [text, icon] = + value == 0 + ? ["Зарегистрирован", "app_registration"] + : value == 1 + ? ["Открыт", "lock_open"] + : value == 2 + ? ["Закрыт", "lock_outline"] + : value == 3 + ? ["Согласован", "thumb_up_alt"] + : value == 4 + ? ["Исполнение прекращено", "block"] + : ["Остановлен", "do_not_disturb_on"]; + return ( + + {icon} + {addText == true ? text : null} + + ); +}; + +//Формирование значения для контрольных колонок +const formatCtrlValue = (value, addText = false) => { + if (hasValue(value)) { + const [text, icon, color] = value == 0 ? ["В норме", "done", "green"] : ["Требует внимания", "error", "red"]; + return ( + + + {icon} + + {addText == true ? text : null} + + ); + } else return value; +}; + +//Форматирование значений колонок +const valueFormatter = ({ value, columnDef }) => { + switch (columnDef.name) { + case "NSTATE": + return formatStageStatusValue(value, true); + case "DBEGPLAN": + case "DENDPLAN": + return formatDateRF(value); + case "NCTRL_FIN": + case "NCTRL_CONTR": + case "NCTRL_COEXEC": + case "NCTRL_PERIOD": + case "NCTRL_COST": + case "NCTRL_ACT": + return formatCtrlValue(value, true); + } + return value; +}; + +//Генерация представления ячейки заголовка +const headCellRender = ({ columnDef }) => { + switch (columnDef.name) { + case "NSTATE": + case "NCTRL_FIN": + case "NCTRL_CONTR": + case "NCTRL_COEXEC": + case "NCTRL_COST": + case "NCTRL_ACT": + return { + stackProps: { justifyContent: "center" }, + cellProps: { align: "center" } + }; + } +}; + +//Генерация представления ячейки c данными +const dataCellRender = ({ row, columnDef }, showStageArts) => { + switch (columnDef.name) { + case "NSTATE": + return { + cellProps: { align: "center" }, + data: formatStageStatusValue(row[columnDef.name], false) + }; + case "NCTRL_FIN": + case "NCTRL_COEXEC": + case "NCTRL_ACT": + return { + cellProps: { align: "center" }, + data: formatCtrlValue(row[columnDef.name], false) + }; + case "NCTRL_CONTR": + case "NCTRL_COST": + return { + cellProps: { align: "center" }, + data: hasValue(row[columnDef.name]) ? ( + + showStageArts({ stage: row.NRN, stageNumb: row.SNUMB, filters: [{ name: columnDef.name, from: row[columnDef.name] }] }) + } + > + {formatCtrlValue(row[columnDef.name], false)} + + ) : null + }; + case "NCTRL_PERIOD": + return { + cellProps: { align: "right" }, + data: hasValue(row[columnDef.name]) ? ( + +
+ {row.NDAYS_LEFT} дн. +
+ {formatCtrlValue(row[columnDef.name], false)} +
+ ) : null + }; + } +}; + +//Генерация представления расширения строки +const rowExpandRender = ({ columnsDef, row }, pOnlineShowDocument, showStageArts, showStageContracts, showStagePayNotes, showStageCostNotes) => { + const cardColumns = columnsDef.filter( + columnDef => + columnDef.visible == false && + columnDef.name != "NRN" && + !columnDef.name.startsWith("SLNK_UNIT_") && + !columnDef.name.startsWith("NLNK_DOCUMENT_") && + hasValue(row[columnDef.name]) + ); + const formatColumnValue = (name, value) => + name.startsWith("N") ? formatNumberRFCurrency(value) : name.startsWith("D") ? formatDateRF(value) : value; + return ( + + + + + + + + + + + + + + {cardColumns.map((cardColumn, i) => ( + + + + {cardColumn.caption}:  + + + + {hasValue(row[`SLNK_UNIT_${cardColumn.name}`]) && hasValue(row[`NLNK_DOCUMENT_${cardColumn.name}`]) ? ( + { + if (["NFIN_IN", "NFIN_OUT"].includes(cardColumn.name)) + showStagePayNotes(row.NRN, row[`NLNK_DOCUMENT_${cardColumn.name}`]); + else if (cardColumn.name == "NCOST_FACT") showStageCostNotes(row.NRN); + else + pOnlineShowDocument({ + unitCode: row[`SLNK_UNIT_${cardColumn.name}`], + document: row[`NLNK_DOCUMENT_${cardColumn.name}`] + }); + }} + > + + {formatColumnValue(cardColumn.name, row[cardColumn.name])} + + + ) : ( + + {["NDAYS_LEFT", "NINCOME_PRC"].includes(cardColumn.name) + ? row[cardColumn.name] + : formatColumnValue(cardColumn.name, row[cardColumn.name])} + + )} + + + ))} + +
+
+
+
+
+ ); +}; + +//----------- +//Тело модуля +//----------- + +//Список этапов проекта +const Stages = ({ project, projectName, filters }) => { + //Собственное состояние + const [stagesDataGrid, setStagesDataGrid] = useState({ + dataLoaded: false, + columnsDef: [], + filters: [...filters], + orders: null, + rows: [], + reload: true, + pageNumber: 1, + morePages: true, + selectedStageNumb: null, + showStageArts: null, + stageArtsFilters: [], + showStageContracts: null, + stageContractsFilters: [] + }); + + //Подключение к контексту взаимодействия с сервером + const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx); + + //Подключение к контексту приложения + const { pOnlineShowDocument, pOnlineShowUnit } = useContext(ApplicationСtx); + + //Подключение к контексту сообщений + const { showMsgErr } = useContext(MessagingСtx); + + //Загрузка данных этапов с сервера + const loadStages = useCallback(async () => { + if (stagesDataGrid.reload) { + const data = await executeStored({ + stored: "PKG_P8PANELS_PROJECTS.STAGES_LIST", + args: { + NPRN: project, + CFILTERS: { VALUE: object2Base64XML(stagesDataGrid.filters, { arrayNodeName: "filters" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, + CORDERS: { VALUE: object2Base64XML(stagesDataGrid.orders, { arrayNodeName: "orders" }), SDATA_TYPE: SERV_DATA_TYPE_CLOB }, + NPAGE_NUMBER: stagesDataGrid.pageNumber, + NPAGE_SIZE: PAGE_SIZE, + NINCLUDE_DEF: stagesDataGrid.dataLoaded ? 0 : 1 + }, + respArg: "COUT" + }); + setStagesDataGrid(pv => ({ + ...pv, + columnsDef: data.XCOLUMNS_DEF ? [...data.XCOLUMNS_DEF] : pv.columnsDef, + rows: pv.pageNumber == 1 ? [...(data.XROWS || [])] : [...pv.rows, ...(data.XROWS || [])], + dataLoaded: true, + reload: false, + morePages: (data.XROWS || []).length >= PAGE_SIZE + })); + } + }, [ + project, + stagesDataGrid.reload, + stagesDataGrid.filters, + stagesDataGrid.orders, + stagesDataGrid.dataLoaded, + stagesDataGrid.pageNumber, + executeStored, + SERV_DATA_TYPE_CLOB + ]); + + //Отображение журнала платежей по этапу проекта + const showStagePayNotes = async (stage, direction) => { + const data = await executeStored({ + stored: "PKG_P8PANELS_PROJECTS.STAGES_SELECT_FIN", + args: { NRN: stage, NDIRECTION: direction } + }); + if (data.NIDENT) pOnlineShowUnit({ unitCode: "PayNotes", inputParameters: [{ name: "in_SelectList_Ident", value: data.NIDENT }] }); + else showMsgErr(TEXTS.NO_DATA_FOUND); + }; + + //Отображение журнала затрат по этапу проекта + const showStageCostNotes = async stage => { + const data = await executeStored({ + stored: "PKG_P8PANELS_PROJECTS.STAGES_SELECT_COST_FACT", + args: { NRN: stage } + }); + if (data.NIDENT) pOnlineShowUnit({ unitCode: "CostNotes", inputParameters: [{ name: "in_SelectList_Ident", value: data.NIDENT }] }); + else showMsgErr(TEXTS.NO_DATA_FOUND); + }; + + //Отображение статей калькуляции по этапу проекта + const showStageArts = ({ stage, stageNumb, filters = [] } = {}) => { + setStagesDataGrid(pv => ({ ...pv, showStageArts: stage, selectedStageNumb: stageNumb, stageArtsFilters: [...filters] })); + }; + + //Отображение договоров с соисполнителями по этапу проекта + const showStageContracts = ({ stage, stageNumb, filters = [] } = {}) => { + setStagesDataGrid(pv => ({ ...pv, showStageContracts: stage, selectedStageNumb: stageNumb, stageContractsFilters: [...filters] })); + }; + + //При изменении состояния фильтра + const handleFilterChanged = ({ filters }) => setStagesDataGrid(pv => ({ ...pv, filters, pageNumber: 1, reload: true })); + + //При изменении состояния сортировки + const handleOrderChanged = ({ orders }) => setStagesDataGrid(pv => ({ ...pv, orders, pageNumber: 1, reload: true })); + + //При изменении количества отображаемых страниц + const handlePagesCountChanged = () => setStagesDataGrid(pv => ({ ...pv, pageNumber: pv.pageNumber + 1, reload: true })); + + //При закрытии списка договоров этапа + const handleStageContractsClose = () => setStagesDataGrid(pv => ({ ...pv, showStageContracts: null, stageContractsFilters: [] })); + + //При закрытии калькуляции этапа + const handleStageArtsClose = () => setStagesDataGrid(pv => ({ ...pv, showStageArts: null, stageArtsFilters: [] })); + + //При необходимости обновить данные + useEffect(() => { + loadStages(); + }, [stagesDataGrid.reload, loadStages]); + + //Генерация содержимого + return ( + + {stagesDataGrid.dataLoaded ? ( + dataCellRender(prms, showStageArts)} + rowExpandRender={prms => + rowExpandRender(prms, pOnlineShowDocument, showStageArts, showStageContracts, showStagePayNotes, showStageCostNotes) + } + valueFormatter={valueFormatter} + onOrderChanged={handleOrderChanged} + onFilterChanged={handleFilterChanged} + onPagesCountChanged={handlePagesCountChanged} + /> + ) : null} + {stagesDataGrid.showStageContracts ? ( + + + + ) : null} + {stagesDataGrid.showStageArts ? ( + + + + ) : null} + + ); +}; + +//Контроль свойств - Список этапов проекта +Stages.propTypes = { + project: PropTypes.number.isRequired, + projectName: PropTypes.string.isRequired, + filters: PropTypes.arrayOf(P8PDATA_GRID_FILTER_SHAPE) +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { Stages }; diff --git a/app/panels/prj_jobs/index.js b/app/panels/prj_jobs/index.js new file mode 100644 index 0000000..3c7099c --- /dev/null +++ b/app/panels/prj_jobs/index.js @@ -0,0 +1,16 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Работы проектов + Панель мониторинга: Точка входа +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import { PrjJobs } from "./prj_jobs"; //Корневая панель работ проектов + +//---------------- +//Интерфейс модуля +//---------------- + +export const RootClass = PrjJobs; diff --git a/app/panels/prj_jobs/prj_jobs.js b/app/panels/prj_jobs/prj_jobs.js new file mode 100644 index 0000000..0a7b66d --- /dev/null +++ b/app/panels/prj_jobs/prj_jobs.js @@ -0,0 +1,163 @@ +/* + Парус 8 - Панели мониторинга - ПУП - Работы проектов + Панель мониторинга: Корневая панель работ проектов +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useContext, useState } from "react"; //Классы React +import Button from "@mui/material/Button"; //Кнопка +import Typography from "@mui/material/Typography"; //Текст +import { NavigationCtx } from "../../context/navigation"; //Контекст навигации +import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером +import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений +import { ApplicationСtx } from "../../context/application"; //Контекст приложения + +//----------- +//Тело модуля +//----------- + +//Корневая панель работ проектов +const PrjJobs = () => { + //Собственное состояние + let [result, setResult] = useState(""); + + //Подключение к контексту навигации + const { navigateBack, navigateRoot, isNavigationState, getNavigationState, navigatePanelByName } = useContext(NavigationCtx); + + //Подключение к контексту взаимодействия с сервером + const { executeStored } = useContext(BackEndСtx); + + //Подключение к контексту сообщений + const { showMsgErr, showMsgWarn, showMsgInfo } = useContext(MessagingСtx); + + //Подключение к контексту приложения + const { pOnlineShowDocument, pOnlineShowDictionary, pOnlineUserProcedure, pOnlineUserReport } = useContext(ApplicationСtx); + + //Выполнение запроса к серверу + const makeReq = async throwError => { + try { + const data = await executeStored({ + throwError, + showErrorMessage: false, + stored: "UDO_P_P8PANELS_TEST", + args: { NRN: 123, SCODE: "123", DDATE: new Date() }, + respArg: "COUT", + spreadOutArguments: false + }); + setResult(JSON.stringify(data)); + } catch (e) { + setResult(""); + showMsgErr(e.message); + } + }; + + //Генерация содержимого + return ( +
+

Это панель работ!

+
+

Параметры: {isNavigationState() ? JSON.stringify(getNavigationState()) : "НЕ ПЕРЕДАНЫ"}

+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ RESULT: {result} +
+
+
+ + + + + +
+
+
+ ); +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { PrjJobs }; diff --git a/app/root.js b/app/root.js new file mode 100644 index 0000000..da6a908 --- /dev/null +++ b/app/root.js @@ -0,0 +1,37 @@ +/* + Парус 8 - Панели мониторинга + Корневой класс приложения (обёртка для контекста) +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React from "react"; //Классы React +import { MessagingContext } from "./context/messaging"; //Контекст сообщений +import { BackEndContext } from "./context/backend"; //Контекст взаимодействия с сервером +import { ApplicationContext } from "./context/application"; //Контекст приложения +import { App } from "./app"; //Приложение + +//----------- +//Тело модуля +//----------- + +//Обёртка для контекста +const Root = () => { + return ( + + + + + + + + ); +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export default Root; diff --git a/css/fonts-material-icons.css b/css/fonts-material-icons.css new file mode 100644 index 0000000..92d4cc4 --- /dev/null +++ b/css/fonts-material-icons.css @@ -0,0 +1,23 @@ +/* fallback */ +@font-face { + font-family: "Material Icons"; + font-style: normal; + font-weight: 400; + src: url(../fonts/material-icons.woff2) format("woff2"); +} + +.material-icons { + font-family: "Material Icons"; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: "liga"; + -webkit-font-smoothing: antialiased; +} diff --git a/css/fonts-roboto.css b/css/fonts-roboto.css new file mode 100644 index 0000000..2968e71 --- /dev/null +++ b/css/fonts-roboto.css @@ -0,0 +1,148 @@ +/* cyrillic-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url(../fonts/roboto-cyrillic-ext-300-normal.woff2) format("woff2"); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url(../fonts/roboto-cyrillic-300-normal.woff2) format("woff2"); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* latin-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url(../fonts/roboto-latin-ext-300-normal.woff2) format("woff2"); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url(../fonts/roboto-latin-300-normal.woff2) format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, + U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../fonts/roboto-cyrillic-ext-400-normal.woff2) format("woff2"); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../fonts/roboto-cyrillic-400-normal.woff2) format("woff2"); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* latin-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../fonts/roboto-latin-ext-400-normal.woff2) format("woff2"); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(../fonts/roboto-latin-400-normal.woff2) format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, + U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(../fonts/roboto-cyrillic-ext-500-normal.woff2) format("woff2"); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(../fonts/roboto-cyrillic-500-normal.woff2) format("woff2"); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* latin-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(../fonts/roboto-latin-ext-500-normal.woff2) format("woff2"); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url(../fonts/roboto-latin-500-normal.woff2) format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, + U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* cyrillic-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(../fonts/roboto-cyrillic-ext-700-normal.woff2) format("woff2"); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(../fonts/roboto-cyrillic-700-normal.woff2) format("woff2"); + unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* latin-ext */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(../fonts/roboto-latin-ext-700-normal.woff2) format("woff2"); + unicode-range: U+0100-02AF, U+0304, U+0308, U+0329, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: "Roboto"; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url(../fonts/roboto-latin-700-normal.woff2) format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, + U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} diff --git a/css/p8-panels.css b/css/p8-panels.css new file mode 100644 index 0000000..e69de29 diff --git a/dist/p8-panels.js b/dist/p8-panels.js new file mode 100644 index 0000000..1148156 --- /dev/null +++ b/dist/p8-panels.js @@ -0,0 +1,349 @@ +/* + * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). + * This devtool is neither made for production nor for readable output files. + * It uses "eval()" calls to create a separate source file in the browser devtools. + * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) + * or disable the default devtool with "devtool: false". + * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). + */ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ "./app/panels sync recursive ^\\.\\/.*$": +/*!***********************************!*\ + !*** ./app/panels/ sync ^\.\/.*$ ***! + \***********************************/ +/***/ ((module) => { + +eval("function webpackEmptyContext(req) {\n\tvar e = new Error(\"Cannot find module '\" + req + \"'\");\n\te.code = 'MODULE_NOT_FOUND';\n\tthrow e;\n}\nwebpackEmptyContext.keys = () => ([]);\nwebpackEmptyContext.resolve = webpackEmptyContext;\nwebpackEmptyContext.id = \"./app/panels sync recursive ^\\\\.\\\\/.*$\";\nmodule.exports = webpackEmptyContext;\n\n//# sourceURL=webpack://parus_8_panels_plugin/./app/panels/_sync_^\\.\\/.*$?"); + +/***/ }), + +/***/ "./node_modules/@remix-run/router/dist/router.js": +/*!*******************************************************!*\ + !*** ./node_modules/@remix-run/router/dist/router.js ***! + \*******************************************************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ AbortedDeferredError: () => (/* binding */ AbortedDeferredError),\n/* harmony export */ Action: () => (/* binding */ Action),\n/* harmony export */ ErrorResponse: () => (/* binding */ ErrorResponse),\n/* harmony export */ IDLE_BLOCKER: () => (/* binding */ IDLE_BLOCKER),\n/* harmony export */ IDLE_FETCHER: () => (/* binding */ IDLE_FETCHER),\n/* harmony export */ IDLE_NAVIGATION: () => (/* binding */ IDLE_NAVIGATION),\n/* harmony export */ UNSAFE_DEFERRED_SYMBOL: () => (/* binding */ UNSAFE_DEFERRED_SYMBOL),\n/* harmony export */ UNSAFE_DeferredData: () => (/* binding */ DeferredData),\n/* harmony export */ UNSAFE_convertRoutesToDataRoutes: () => (/* binding */ convertRoutesToDataRoutes),\n/* harmony export */ UNSAFE_getPathContributingMatches: () => (/* binding */ getPathContributingMatches),\n/* harmony export */ UNSAFE_invariant: () => (/* binding */ invariant),\n/* harmony export */ UNSAFE_warning: () => (/* binding */ warning),\n/* harmony export */ createBrowserHistory: () => (/* binding */ createBrowserHistory),\n/* harmony export */ createHashHistory: () => (/* binding */ createHashHistory),\n/* harmony export */ createMemoryHistory: () => (/* binding */ createMemoryHistory),\n/* harmony export */ createPath: () => (/* binding */ createPath),\n/* harmony export */ createRouter: () => (/* binding */ createRouter),\n/* harmony export */ createStaticHandler: () => (/* binding */ createStaticHandler),\n/* harmony export */ defer: () => (/* binding */ defer),\n/* harmony export */ generatePath: () => (/* binding */ generatePath),\n/* harmony export */ getStaticContextFromError: () => (/* binding */ getStaticContextFromError),\n/* harmony export */ getToPathname: () => (/* binding */ getToPathname),\n/* harmony export */ isDeferredData: () => (/* binding */ isDeferredData),\n/* harmony export */ isRouteErrorResponse: () => (/* binding */ isRouteErrorResponse),\n/* harmony export */ joinPaths: () => (/* binding */ joinPaths),\n/* harmony export */ json: () => (/* binding */ json),\n/* harmony export */ matchPath: () => (/* binding */ matchPath),\n/* harmony export */ matchRoutes: () => (/* binding */ matchRoutes),\n/* harmony export */ normalizePathname: () => (/* binding */ normalizePathname),\n/* harmony export */ parsePath: () => (/* binding */ parsePath),\n/* harmony export */ redirect: () => (/* binding */ redirect),\n/* harmony export */ redirectDocument: () => (/* binding */ redirectDocument),\n/* harmony export */ resolvePath: () => (/* binding */ resolvePath),\n/* harmony export */ resolveTo: () => (/* binding */ resolveTo),\n/* harmony export */ stripBasename: () => (/* binding */ stripBasename)\n/* harmony export */ });\n/**\n * @remix-run/router v1.8.0\n *\n * Copyright (c) Remix Software Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE.md file in the root directory of this source tree.\n *\n * @license MIT\n */\nfunction _extends() {\n _extends = Object.assign ? Object.assign.bind() : function (target) {\n for (var i = 1; i < arguments.length; i++) {\n var source = arguments[i];\n for (var key in source) {\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n target[key] = source[key];\n }\n }\n }\n return target;\n };\n return _extends.apply(this, arguments);\n}\n\n////////////////////////////////////////////////////////////////////////////////\n//#region Types and Constants\n////////////////////////////////////////////////////////////////////////////////\n/**\n * Actions represent the type of change to a location value.\n */\nvar Action;\n(function (Action) {\n /**\n * A POP indicates a change to an arbitrary index in the history stack, such\n * as a back or forward navigation. It does not describe the direction of the\n * navigation, only that the current index changed.\n *\n * Note: This is the default action for newly created history objects.\n */\n Action[\"Pop\"] = \"POP\";\n /**\n * A PUSH indicates a new entry being added to the history stack, such as when\n * a link is clicked and a new page loads. When this happens, all subsequent\n * entries in the stack are lost.\n */\n Action[\"Push\"] = \"PUSH\";\n /**\n * A REPLACE indicates the entry at the current index in the history stack\n * being replaced by a new one.\n */\n Action[\"Replace\"] = \"REPLACE\";\n})(Action || (Action = {}));\nconst PopStateEventType = \"popstate\";\n/**\n * Memory history stores the current location in memory. It is designed for use\n * in stateful non-browser environments like tests and React Native.\n */\nfunction createMemoryHistory(options) {\n if (options === void 0) {\n options = {};\n }\n let {\n initialEntries = [\"/\"],\n initialIndex,\n v5Compat = false\n } = options;\n let entries; // Declare so we can access from createMemoryLocation\n entries = initialEntries.map((entry, index) => createMemoryLocation(entry, typeof entry === \"string\" ? null : entry.state, index === 0 ? \"default\" : undefined));\n let index = clampIndex(initialIndex == null ? entries.length - 1 : initialIndex);\n let action = Action.Pop;\n let listener = null;\n function clampIndex(n) {\n return Math.min(Math.max(n, 0), entries.length - 1);\n }\n function getCurrentLocation() {\n return entries[index];\n }\n function createMemoryLocation(to, state, key) {\n if (state === void 0) {\n state = null;\n }\n let location = createLocation(entries ? getCurrentLocation().pathname : \"/\", to, state, key);\n warning(location.pathname.charAt(0) === \"/\", \"relative pathnames are not supported in memory history: \" + JSON.stringify(to));\n return location;\n }\n function createHref(to) {\n return typeof to === \"string\" ? to : createPath(to);\n }\n let history = {\n get index() {\n return index;\n },\n get action() {\n return action;\n },\n get location() {\n return getCurrentLocation();\n },\n createHref,\n createURL(to) {\n return new URL(createHref(to), \"http://localhost\");\n },\n encodeLocation(to) {\n let path = typeof to === \"string\" ? parsePath(to) : to;\n return {\n pathname: path.pathname || \"\",\n search: path.search || \"\",\n hash: path.hash || \"\"\n };\n },\n push(to, state) {\n action = Action.Push;\n let nextLocation = createMemoryLocation(to, state);\n index += 1;\n entries.splice(index, entries.length, nextLocation);\n if (v5Compat && listener) {\n listener({\n action,\n location: nextLocation,\n delta: 1\n });\n }\n },\n replace(to, state) {\n action = Action.Replace;\n let nextLocation = createMemoryLocation(to, state);\n entries[index] = nextLocation;\n if (v5Compat && listener) {\n listener({\n action,\n location: nextLocation,\n delta: 0\n });\n }\n },\n go(delta) {\n action = Action.Pop;\n let nextIndex = clampIndex(index + delta);\n let nextLocation = entries[nextIndex];\n index = nextIndex;\n if (listener) {\n listener({\n action,\n location: nextLocation,\n delta\n });\n }\n },\n listen(fn) {\n listener = fn;\n return () => {\n listener = null;\n };\n }\n };\n return history;\n}\n/**\n * Browser history stores the location in regular URLs. This is the standard for\n * most web apps, but it requires some configuration on the server to ensure you\n * serve the same app at multiple URLs.\n *\n * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createbrowserhistory\n */\nfunction createBrowserHistory(options) {\n if (options === void 0) {\n options = {};\n }\n function createBrowserLocation(window, globalHistory) {\n let {\n pathname,\n search,\n hash\n } = window.location;\n return createLocation(\"\", {\n pathname,\n search,\n hash\n },\n // state defaults to `null` because `window.history.state` does\n globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || \"default\");\n }\n function createBrowserHref(window, to) {\n return typeof to === \"string\" ? to : createPath(to);\n }\n return getUrlBasedHistory(createBrowserLocation, createBrowserHref, null, options);\n}\n/**\n * Hash history stores the location in window.location.hash. This makes it ideal\n * for situations where you don't want to send the location to the server for\n * some reason, either because you do cannot configure it or the URL space is\n * reserved for something else.\n *\n * @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#createhashhistory\n */\nfunction createHashHistory(options) {\n if (options === void 0) {\n options = {};\n }\n function createHashLocation(window, globalHistory) {\n let {\n pathname = \"/\",\n search = \"\",\n hash = \"\"\n } = parsePath(window.location.hash.substr(1));\n // Hash URL should always have a leading / just like window.location.pathname\n // does, so if an app ends up at a route like /#something then we add a\n // leading slash so all of our path-matching behaves the same as if it would\n // in a browser router. This is particularly important when there exists a\n // root splat route () since that matches internally against\n // \"/*\" and we'd expect /#something to 404 in a hash router app.\n if (!pathname.startsWith(\"/\") && !pathname.startsWith(\".\")) {\n pathname = \"/\" + pathname;\n }\n return createLocation(\"\", {\n pathname,\n search,\n hash\n },\n // state defaults to `null` because `window.history.state` does\n globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || \"default\");\n }\n function createHashHref(window, to) {\n let base = window.document.querySelector(\"base\");\n let href = \"\";\n if (base && base.getAttribute(\"href\")) {\n let url = window.location.href;\n let hashIndex = url.indexOf(\"#\");\n href = hashIndex === -1 ? url : url.slice(0, hashIndex);\n }\n return href + \"#\" + (typeof to === \"string\" ? to : createPath(to));\n }\n function validateHashLocation(location, to) {\n warning(location.pathname.charAt(0) === \"/\", \"relative pathnames are not supported in hash history.push(\" + JSON.stringify(to) + \")\");\n }\n return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);\n}\nfunction invariant(value, message) {\n if (value === false || value === null || typeof value === \"undefined\") {\n throw new Error(message);\n }\n}\nfunction warning(cond, message) {\n if (!cond) {\n // eslint-disable-next-line no-console\n if (typeof console !== \"undefined\") console.warn(message);\n try {\n // Welcome to debugging history!\n //\n // This error is thrown as a convenience so you can more easily\n // find the source for a warning that appears in the console by\n // enabling \"pause on exceptions\" in your JavaScript debugger.\n throw new Error(message);\n // eslint-disable-next-line no-empty\n } catch (e) {}\n }\n}\nfunction createKey() {\n return Math.random().toString(36).substr(2, 8);\n}\n/**\n * For browser-based histories, we combine the state and key into an object\n */\nfunction getHistoryState(location, index) {\n return {\n usr: location.state,\n key: location.key,\n idx: index\n };\n}\n/**\n * Creates a Location object with a unique key from the given Path\n */\nfunction createLocation(current, to, state, key) {\n if (state === void 0) {\n state = null;\n }\n let location = _extends({\n pathname: typeof current === \"string\" ? current : current.pathname,\n search: \"\",\n hash: \"\"\n }, typeof to === \"string\" ? parsePath(to) : to, {\n state,\n // TODO: This could be cleaned up. push/replace should probably just take\n // full Locations now and avoid the need to run through this flow at all\n // But that's a pretty big refactor to the current test suite so going to\n // keep as is for the time being and just let any incoming keys take precedence\n key: to && to.key || key || createKey()\n });\n return location;\n}\n/**\n * Creates a string URL path from the given pathname, search, and hash components.\n */\nfunction createPath(_ref) {\n let {\n pathname = \"/\",\n search = \"\",\n hash = \"\"\n } = _ref;\n if (search && search !== \"?\") pathname += search.charAt(0) === \"?\" ? search : \"?\" + search;\n if (hash && hash !== \"#\") pathname += hash.charAt(0) === \"#\" ? hash : \"#\" + hash;\n return pathname;\n}\n/**\n * Parses a string URL path into its separate pathname, search, and hash components.\n */\nfunction parsePath(path) {\n let parsedPath = {};\n if (path) {\n let hashIndex = path.indexOf(\"#\");\n if (hashIndex >= 0) {\n parsedPath.hash = path.substr(hashIndex);\n path = path.substr(0, hashIndex);\n }\n let searchIndex = path.indexOf(\"?\");\n if (searchIndex >= 0) {\n parsedPath.search = path.substr(searchIndex);\n path = path.substr(0, searchIndex);\n }\n if (path) {\n parsedPath.pathname = path;\n }\n }\n return parsedPath;\n}\nfunction getUrlBasedHistory(getLocation, createHref, validateLocation, options) {\n if (options === void 0) {\n options = {};\n }\n let {\n window = document.defaultView,\n v5Compat = false\n } = options;\n let globalHistory = window.history;\n let action = Action.Pop;\n let listener = null;\n let index = getIndex();\n // Index should only be null when we initialize. If not, it's because the\n // user called history.pushState or history.replaceState directly, in which\n // case we should log a warning as it will result in bugs.\n if (index == null) {\n index = 0;\n globalHistory.replaceState(_extends({}, globalHistory.state, {\n idx: index\n }), \"\");\n }\n function getIndex() {\n let state = globalHistory.state || {\n idx: null\n };\n return state.idx;\n }\n function handlePop() {\n action = Action.Pop;\n let nextIndex = getIndex();\n let delta = nextIndex == null ? null : nextIndex - index;\n index = nextIndex;\n if (listener) {\n listener({\n action,\n location: history.location,\n delta\n });\n }\n }\n function push(to, state) {\n action = Action.Push;\n let location = createLocation(history.location, to, state);\n if (validateLocation) validateLocation(location, to);\n index = getIndex() + 1;\n let historyState = getHistoryState(location, index);\n let url = history.createHref(location);\n // try...catch because iOS limits us to 100 pushState calls :/\n try {\n globalHistory.pushState(historyState, \"\", url);\n } catch (error) {\n // If the exception is because `state` can't be serialized, let that throw\n // outwards just like a replace call would so the dev knows the cause\n // https://html.spec.whatwg.org/multipage/nav-history-apis.html#shared-history-push/replace-state-steps\n // https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal\n if (error instanceof DOMException && error.name === \"DataCloneError\") {\n throw error;\n }\n // They are going to lose state here, but there is no real\n // way to warn them about it since the page will refresh...\n window.location.assign(url);\n }\n if (v5Compat && listener) {\n listener({\n action,\n location: history.location,\n delta: 1\n });\n }\n }\n function replace(to, state) {\n action = Action.Replace;\n let location = createLocation(history.location, to, state);\n if (validateLocation) validateLocation(location, to);\n index = getIndex();\n let historyState = getHistoryState(location, index);\n let url = history.createHref(location);\n globalHistory.replaceState(historyState, \"\", url);\n if (v5Compat && listener) {\n listener({\n action,\n location: history.location,\n delta: 0\n });\n }\n }\n function createURL(to) {\n // window.location.origin is \"null\" (the literal string value) in Firefox\n // under certain conditions, notably when serving from a local HTML file\n // See https://bugzilla.mozilla.org/show_bug.cgi?id=878297\n let base = window.location.origin !== \"null\" ? window.location.origin : window.location.href;\n let href = typeof to === \"string\" ? to : createPath(to);\n invariant(base, \"No window.location.(origin|href) available to create URL for href: \" + href);\n return new URL(href, base);\n }\n let history = {\n get action() {\n return action;\n },\n get location() {\n return getLocation(window, globalHistory);\n },\n listen(fn) {\n if (listener) {\n throw new Error(\"A history only accepts one active listener\");\n }\n window.addEventListener(PopStateEventType, handlePop);\n listener = fn;\n return () => {\n window.removeEventListener(PopStateEventType, handlePop);\n listener = null;\n };\n },\n createHref(to) {\n return createHref(window, to);\n },\n createURL,\n encodeLocation(to) {\n // Encode a Location the same way window.location would\n let url = createURL(to);\n return {\n pathname: url.pathname,\n search: url.search,\n hash: url.hash\n };\n },\n push,\n replace,\n go(n) {\n return globalHistory.go(n);\n }\n };\n return history;\n}\n//#endregion\n\nvar ResultType;\n(function (ResultType) {\n ResultType[\"data\"] = \"data\";\n ResultType[\"deferred\"] = \"deferred\";\n ResultType[\"redirect\"] = \"redirect\";\n ResultType[\"error\"] = \"error\";\n})(ResultType || (ResultType = {}));\nconst immutableRouteKeys = new Set([\"lazy\", \"caseSensitive\", \"path\", \"id\", \"index\", \"children\"]);\nfunction isIndexRoute(route) {\n return route.index === true;\n}\n// Walk the route tree generating unique IDs where necessary so we are working\n// solely with AgnosticDataRouteObject's within the Router\nfunction convertRoutesToDataRoutes(routes, mapRouteProperties, parentPath, manifest) {\n if (parentPath === void 0) {\n parentPath = [];\n }\n if (manifest === void 0) {\n manifest = {};\n }\n return routes.map((route, index) => {\n let treePath = [...parentPath, index];\n let id = typeof route.id === \"string\" ? route.id : treePath.join(\"-\");\n invariant(route.index !== true || !route.children, \"Cannot specify children on an index route\");\n invariant(!manifest[id], \"Found a route id collision on id \\\"\" + id + \"\\\". Route \" + \"id's must be globally unique within Data Router usages\");\n if (isIndexRoute(route)) {\n let indexRoute = _extends({}, route, mapRouteProperties(route), {\n id\n });\n manifest[id] = indexRoute;\n return indexRoute;\n } else {\n let pathOrLayoutRoute = _extends({}, route, mapRouteProperties(route), {\n id,\n children: undefined\n });\n manifest[id] = pathOrLayoutRoute;\n if (route.children) {\n pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, mapRouteProperties, treePath, manifest);\n }\n return pathOrLayoutRoute;\n }\n });\n}\n/**\n * Matches the given routes to a location and returns the match data.\n *\n * @see https://reactrouter.com/utils/match-routes\n */\nfunction matchRoutes(routes, locationArg, basename) {\n if (basename === void 0) {\n basename = \"/\";\n }\n let location = typeof locationArg === \"string\" ? parsePath(locationArg) : locationArg;\n let pathname = stripBasename(location.pathname || \"/\", basename);\n if (pathname == null) {\n return null;\n }\n let branches = flattenRoutes(routes);\n rankRouteBranches(branches);\n let matches = null;\n for (let i = 0; matches == null && i < branches.length; ++i) {\n matches = matchRouteBranch(branches[i],\n // Incoming pathnames are generally encoded from either window.location\n // or from router.navigate, but we want to match against the unencoded\n // paths in the route definitions. Memory router locations won't be\n // encoded here but there also shouldn't be anything to decode so this\n // should be a safe operation. This avoids needing matchRoutes to be\n // history-aware.\n safelyDecodeURI(pathname));\n }\n return matches;\n}\nfunction flattenRoutes(routes, branches, parentsMeta, parentPath) {\n if (branches === void 0) {\n branches = [];\n }\n if (parentsMeta === void 0) {\n parentsMeta = [];\n }\n if (parentPath === void 0) {\n parentPath = \"\";\n }\n let flattenRoute = (route, index, relativePath) => {\n let meta = {\n relativePath: relativePath === undefined ? route.path || \"\" : relativePath,\n caseSensitive: route.caseSensitive === true,\n childrenIndex: index,\n route\n };\n if (meta.relativePath.startsWith(\"/\")) {\n invariant(meta.relativePath.startsWith(parentPath), \"Absolute route path \\\"\" + meta.relativePath + \"\\\" nested under path \" + (\"\\\"\" + parentPath + \"\\\" is not valid. An absolute child route path \") + \"must start with the combined path of all its parent routes.\");\n meta.relativePath = meta.relativePath.slice(parentPath.length);\n }\n let path = joinPaths([parentPath, meta.relativePath]);\n let routesMeta = parentsMeta.concat(meta);\n // Add the children before adding this route to the array so we traverse the\n // route tree depth-first and child routes appear before their parents in\n // the \"flattened\" version.\n if (route.children && route.children.length > 0) {\n invariant(\n // Our types know better, but runtime JS may not!\n // @ts-expect-error\n route.index !== true, \"Index routes must not have child routes. Please remove \" + (\"all child routes from route path \\\"\" + path + \"\\\".\"));\n flattenRoutes(route.children, branches, routesMeta, path);\n }\n // Routes without a path shouldn't ever match by themselves unless they are\n // index routes, so don't add them to the list of possible branches.\n if (route.path == null && !route.index) {\n return;\n }\n branches.push({\n path,\n score: computeScore(path, route.index),\n routesMeta\n });\n };\n routes.forEach((route, index) => {\n var _route$path;\n // coarse-grain check for optional params\n if (route.path === \"\" || !((_route$path = route.path) != null && _route$path.includes(\"?\"))) {\n flattenRoute(route, index);\n } else {\n for (let exploded of explodeOptionalSegments(route.path)) {\n flattenRoute(route, index, exploded);\n }\n }\n });\n return branches;\n}\n/**\n * Computes all combinations of optional path segments for a given path,\n * excluding combinations that are ambiguous and of lower priority.\n *\n * For example, `/one/:two?/three/:four?/:five?` explodes to:\n * - `/one/three`\n * - `/one/:two/three`\n * - `/one/three/:four`\n * - `/one/three/:five`\n * - `/one/:two/three/:four`\n * - `/one/:two/three/:five`\n * - `/one/three/:four/:five`\n * - `/one/:two/three/:four/:five`\n */\nfunction explodeOptionalSegments(path) {\n let segments = path.split(\"/\");\n if (segments.length === 0) return [];\n let [first, ...rest] = segments;\n // Optional path segments are denoted by a trailing `?`\n let isOptional = first.endsWith(\"?\");\n // Compute the corresponding required segment: `foo?` -> `foo`\n let required = first.replace(/\\?$/, \"\");\n if (rest.length === 0) {\n // Intepret empty string as omitting an optional segment\n // `[\"one\", \"\", \"three\"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`\n return isOptional ? [required, \"\"] : [required];\n }\n let restExploded = explodeOptionalSegments(rest.join(\"/\"));\n let result = [];\n // All child paths with the prefix. Do this for all children before the\n // optional version for all children so we get consistent ordering where the\n // parent optional aspect is preferred as required. Otherwise, we can get\n // child sections interspersed where deeper optional segments are higher than\n // parent optional segments, where for example, /:two would explodes _earlier_\n // then /:one. By always including the parent as required _for all children_\n // first, we avoid this issue\n result.push(...restExploded.map(subpath => subpath === \"\" ? required : [required, subpath].join(\"/\")));\n // Then if this is an optional value, add all child versions without\n if (isOptional) {\n result.push(...restExploded);\n }\n // for absolute paths, ensure `/` instead of empty segment\n return result.map(exploded => path.startsWith(\"/\") && exploded === \"\" ? \"/\" : exploded);\n}\nfunction rankRouteBranches(branches) {\n branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first\n : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));\n}\nconst paramRe = /^:\\w+$/;\nconst dynamicSegmentValue = 3;\nconst indexRouteValue = 2;\nconst emptySegmentValue = 1;\nconst staticSegmentValue = 10;\nconst splatPenalty = -2;\nconst isSplat = s => s === \"*\";\nfunction computeScore(path, index) {\n let segments = path.split(\"/\");\n let initialScore = segments.length;\n if (segments.some(isSplat)) {\n initialScore += splatPenalty;\n }\n if (index) {\n initialScore += indexRouteValue;\n }\n return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === \"\" ? emptySegmentValue : staticSegmentValue), initialScore);\n}\nfunction compareIndexes(a, b) {\n let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);\n return siblings ?\n // If two routes are siblings, we should try to match the earlier sibling\n // first. This allows people to have fine-grained control over the matching\n // behavior by simply putting routes with identical paths in the order they\n // want them tried.\n a[a.length - 1] - b[b.length - 1] :\n // Otherwise, it doesn't really make sense to rank non-siblings by index,\n // so they sort equally.\n 0;\n}\nfunction matchRouteBranch(branch, pathname) {\n let {\n routesMeta\n } = branch;\n let matchedParams = {};\n let matchedPathname = \"/\";\n let matches = [];\n for (let i = 0; i < routesMeta.length; ++i) {\n let meta = routesMeta[i];\n let end = i === routesMeta.length - 1;\n let remainingPathname = matchedPathname === \"/\" ? pathname : pathname.slice(matchedPathname.length) || \"/\";\n let match = matchPath({\n path: meta.relativePath,\n caseSensitive: meta.caseSensitive,\n end\n }, remainingPathname);\n if (!match) return null;\n Object.assign(matchedParams, match.params);\n let route = meta.route;\n matches.push({\n // TODO: Can this as be avoided?\n params: matchedParams,\n pathname: joinPaths([matchedPathname, match.pathname]),\n pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),\n route\n });\n if (match.pathnameBase !== \"/\") {\n matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);\n }\n }\n return matches;\n}\n/**\n * Returns a path with params interpolated.\n *\n * @see https://reactrouter.com/utils/generate-path\n */\nfunction generatePath(originalPath, params) {\n if (params === void 0) {\n params = {};\n }\n let path = originalPath;\n if (path.endsWith(\"*\") && path !== \"*\" && !path.endsWith(\"/*\")) {\n warning(false, \"Route path \\\"\" + path + \"\\\" will be treated as if it were \" + (\"\\\"\" + path.replace(/\\*$/, \"/*\") + \"\\\" because the `*` character must \") + \"always follow a `/` in the pattern. To get rid of this warning, \" + (\"please change the route path to \\\"\" + path.replace(/\\*$/, \"/*\") + \"\\\".\"));\n path = path.replace(/\\*$/, \"/*\");\n }\n // ensure `/` is added at the beginning if the path is absolute\n const prefix = path.startsWith(\"/\") ? \"/\" : \"\";\n const stringify = p => p == null ? \"\" : typeof p === \"string\" ? p : String(p);\n const segments = path.split(/\\/+/).map((segment, index, array) => {\n const isLastSegment = index === array.length - 1;\n // only apply the splat if it's the last segment\n if (isLastSegment && segment === \"*\") {\n const star = \"*\";\n // Apply the splat\n return stringify(params[star]);\n }\n const keyMatch = segment.match(/^:(\\w+)(\\??)$/);\n if (keyMatch) {\n const [, key, optional] = keyMatch;\n let param = params[key];\n invariant(optional === \"?\" || param != null, \"Missing \\\":\" + key + \"\\\" param\");\n return stringify(param);\n }\n // Remove any optional markers from optional static segments\n return segment.replace(/\\?$/g, \"\");\n })\n // Remove empty segments\n .filter(segment => !!segment);\n return prefix + segments.join(\"/\");\n}\n/**\n * Performs pattern matching on a URL pathname and returns information about\n * the match.\n *\n * @see https://reactrouter.com/utils/match-path\n */\nfunction matchPath(pattern, pathname) {\n if (typeof pattern === \"string\") {\n pattern = {\n path: pattern,\n caseSensitive: false,\n end: true\n };\n }\n let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);\n let match = pathname.match(matcher);\n if (!match) return null;\n let matchedPathname = match[0];\n let pathnameBase = matchedPathname.replace(/(.)\\/+$/, \"$1\");\n let captureGroups = match.slice(1);\n let params = paramNames.reduce((memo, paramName, index) => {\n // We need to compute the pathnameBase here using the raw splat value\n // instead of using params[\"*\"] later because it will be decoded then\n if (paramName === \"*\") {\n let splatValue = captureGroups[index] || \"\";\n pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\\/+$/, \"$1\");\n }\n memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || \"\", paramName);\n return memo;\n }, {});\n return {\n params,\n pathname: matchedPathname,\n pathnameBase,\n pattern\n };\n}\nfunction compilePath(path, caseSensitive, end) {\n if (caseSensitive === void 0) {\n caseSensitive = false;\n }\n if (end === void 0) {\n end = true;\n }\n warning(path === \"*\" || !path.endsWith(\"*\") || path.endsWith(\"/*\"), \"Route path \\\"\" + path + \"\\\" will be treated as if it were \" + (\"\\\"\" + path.replace(/\\*$/, \"/*\") + \"\\\" because the `*` character must \") + \"always follow a `/` in the pattern. To get rid of this warning, \" + (\"please change the route path to \\\"\" + path.replace(/\\*$/, \"/*\") + \"\\\".\"));\n let paramNames = [];\n let regexpSource = \"^\" + path.replace(/\\/*\\*?$/, \"\") // Ignore trailing / and /*, we'll handle it below\n .replace(/^\\/*/, \"/\") // Make sure it has a leading /\n .replace(/[\\\\.*+^$?{}|()[\\]]/g, \"\\\\$&\") // Escape special regex chars\n .replace(/\\/:(\\w+)/g, (_, paramName) => {\n paramNames.push(paramName);\n return \"/([^\\\\/]+)\";\n });\n if (path.endsWith(\"*\")) {\n paramNames.push(\"*\");\n regexpSource += path === \"*\" || path === \"/*\" ? \"(.*)$\" // Already matched the initial /, just match the rest\n : \"(?:\\\\/(.+)|\\\\/*)$\"; // Don't include the / in params[\"*\"]\n } else if (end) {\n // When matching to the end, ignore trailing slashes\n regexpSource += \"\\\\/*$\";\n } else if (path !== \"\" && path !== \"/\") {\n // If our path is non-empty and contains anything beyond an initial slash,\n // then we have _some_ form of path in our regex so we should expect to\n // match only if we find the end of this path segment. Look for an optional\n // non-captured trailing slash (to match a portion of the URL) or the end\n // of the path (if we've matched to the end). We used to do this with a\n // word boundary but that gives false positives on routes like\n // /user-preferences since `-` counts as a word boundary.\n regexpSource += \"(?:(?=\\\\/|$))\";\n } else ;\n let matcher = new RegExp(regexpSource, caseSensitive ? undefined : \"i\");\n return [matcher, paramNames];\n}\nfunction safelyDecodeURI(value) {\n try {\n return decodeURI(value);\n } catch (error) {\n warning(false, \"The URL path \\\"\" + value + \"\\\" could not be decoded because it is is a \" + \"malformed URL segment. This is probably due to a bad percent \" + (\"encoding (\" + error + \").\"));\n return value;\n }\n}\nfunction safelyDecodeURIComponent(value, paramName) {\n try {\n return decodeURIComponent(value);\n } catch (error) {\n warning(false, \"The value for the URL param \\\"\" + paramName + \"\\\" will not be decoded because\" + (\" the string \\\"\" + value + \"\\\" is a malformed URL segment. This is probably\") + (\" due to a bad percent encoding (\" + error + \").\"));\n return value;\n }\n}\n/**\n * @private\n */\nfunction stripBasename(pathname, basename) {\n if (basename === \"/\") return pathname;\n if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {\n return null;\n }\n // We want to leave trailing slash behavior in the user's control, so if they\n // specify a basename with a trailing slash, we should support it\n let startIndex = basename.endsWith(\"/\") ? basename.length - 1 : basename.length;\n let nextChar = pathname.charAt(startIndex);\n if (nextChar && nextChar !== \"/\") {\n // pathname does not start with basename/\n return null;\n }\n return pathname.slice(startIndex) || \"/\";\n}\n/**\n * Returns a resolved path object relative to the given pathname.\n *\n * @see https://reactrouter.com/utils/resolve-path\n */\nfunction resolvePath(to, fromPathname) {\n if (fromPathname === void 0) {\n fromPathname = \"/\";\n }\n let {\n pathname: toPathname,\n search = \"\",\n hash = \"\"\n } = typeof to === \"string\" ? parsePath(to) : to;\n let pathname = toPathname ? toPathname.startsWith(\"/\") ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname;\n return {\n pathname,\n search: normalizeSearch(search),\n hash: normalizeHash(hash)\n };\n}\nfunction resolvePathname(relativePath, fromPathname) {\n let segments = fromPathname.replace(/\\/+$/, \"\").split(\"/\");\n let relativeSegments = relativePath.split(\"/\");\n relativeSegments.forEach(segment => {\n if (segment === \"..\") {\n // Keep the root \"\" segment so the pathname starts at /\n if (segments.length > 1) segments.pop();\n } else if (segment !== \".\") {\n segments.push(segment);\n }\n });\n return segments.length > 1 ? segments.join(\"/\") : \"/\";\n}\nfunction getInvalidPathError(char, field, dest, path) {\n return \"Cannot include a '\" + char + \"' character in a manually specified \" + (\"`to.\" + field + \"` field [\" + JSON.stringify(path) + \"]. Please separate it out to the \") + (\"`to.\" + dest + \"` field. Alternatively you may provide the full path as \") + \"a string in and the router will parse it for you.\";\n}\n/**\n * @private\n *\n * When processing relative navigation we want to ignore ancestor routes that\n * do not contribute to the path, such that index/pathless layout routes don't\n * interfere.\n *\n * For example, when moving a route element into an index route and/or a\n * pathless layout route, relative link behavior contained within should stay\n * the same. Both of the following examples should link back to the root:\n *\n * \n * \n * \n *\n * \n * \n * }> // <-- Does not contribute\n * // <-- Does not contribute\n * \n * \n */\nfunction getPathContributingMatches(matches) {\n return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);\n}\n/**\n * @private\n */\nfunction resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {\n if (isPathRelative === void 0) {\n isPathRelative = false;\n }\n let to;\n if (typeof toArg === \"string\") {\n to = parsePath(toArg);\n } else {\n to = _extends({}, toArg);\n invariant(!to.pathname || !to.pathname.includes(\"?\"), getInvalidPathError(\"?\", \"pathname\", \"search\", to));\n invariant(!to.pathname || !to.pathname.includes(\"#\"), getInvalidPathError(\"#\", \"pathname\", \"hash\", to));\n invariant(!to.search || !to.search.includes(\"#\"), getInvalidPathError(\"#\", \"search\", \"hash\", to));\n }\n let isEmptyPath = toArg === \"\" || to.pathname === \"\";\n let toPathname = isEmptyPath ? \"/\" : to.pathname;\n let from;\n // Routing is relative to the current pathname if explicitly requested.\n //\n // If a pathname is explicitly provided in `to`, it should be relative to the\n // route context. This is explained in `Note on `` values` in our\n // migration guide from v5 as a means of disambiguation between `to` values\n // that begin with `/` and those that do not. However, this is problematic for\n // `to` values that do not provide a pathname. `to` can simply be a search or\n // hash string, in which case we should assume that the navigation is relative\n // to the current location's pathname and *not* the route pathname.\n if (isPathRelative || toPathname == null) {\n from = locationPathname;\n } else {\n let routePathnameIndex = routePathnames.length - 1;\n if (toPathname.startsWith(\"..\")) {\n let toSegments = toPathname.split(\"/\");\n // Each leading .. segment means \"go up one route\" instead of \"go up one\n // URL segment\". This is a key difference from how works and a\n // major reason we call this a \"to\" value instead of a \"href\".\n while (toSegments[0] === \"..\") {\n toSegments.shift();\n routePathnameIndex -= 1;\n }\n to.pathname = toSegments.join(\"/\");\n }\n // If there are more \"..\" segments than parent routes, resolve relative to\n // the root / URL.\n from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : \"/\";\n }\n let path = resolvePath(to, from);\n // Ensure the pathname has a trailing slash if the original \"to\" had one\n let hasExplicitTrailingSlash = toPathname && toPathname !== \"/\" && toPathname.endsWith(\"/\");\n // Or if this was a link to the current path which has a trailing slash\n let hasCurrentTrailingSlash = (isEmptyPath || toPathname === \".\") && locationPathname.endsWith(\"/\");\n if (!path.pathname.endsWith(\"/\") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) {\n path.pathname += \"/\";\n }\n return path;\n}\n/**\n * @private\n */\nfunction getToPathname(to) {\n // Empty strings should be treated the same as / paths\n return to === \"\" || to.pathname === \"\" ? \"/\" : typeof to === \"string\" ? parsePath(to).pathname : to.pathname;\n}\n/**\n * @private\n */\nconst joinPaths = paths => paths.join(\"/\").replace(/\\/\\/+/g, \"/\");\n/**\n * @private\n */\nconst normalizePathname = pathname => pathname.replace(/\\/+$/, \"\").replace(/^\\/*/, \"/\");\n/**\n * @private\n */\nconst normalizeSearch = search => !search || search === \"?\" ? \"\" : search.startsWith(\"?\") ? search : \"?\" + search;\n/**\n * @private\n */\nconst normalizeHash = hash => !hash || hash === \"#\" ? \"\" : hash.startsWith(\"#\") ? hash : \"#\" + hash;\n/**\n * This is a shortcut for creating `application/json` responses. Converts `data`\n * to JSON and sets the `Content-Type` header.\n */\nconst json = function json(data, init) {\n if (init === void 0) {\n init = {};\n }\n let responseInit = typeof init === \"number\" ? {\n status: init\n } : init;\n let headers = new Headers(responseInit.headers);\n if (!headers.has(\"Content-Type\")) {\n headers.set(\"Content-Type\", \"application/json; charset=utf-8\");\n }\n return new Response(JSON.stringify(data), _extends({}, responseInit, {\n headers\n }));\n};\nclass AbortedDeferredError extends Error {}\nclass DeferredData {\n constructor(data, responseInit) {\n this.pendingKeysSet = new Set();\n this.subscribers = new Set();\n this.deferredKeys = [];\n invariant(data && typeof data === \"object\" && !Array.isArray(data), \"defer() only accepts plain objects\");\n // Set up an AbortController + Promise we can race against to exit early\n // cancellation\n let reject;\n this.abortPromise = new Promise((_, r) => reject = r);\n this.controller = new AbortController();\n let onAbort = () => reject(new AbortedDeferredError(\"Deferred data aborted\"));\n this.unlistenAbortSignal = () => this.controller.signal.removeEventListener(\"abort\", onAbort);\n this.controller.signal.addEventListener(\"abort\", onAbort);\n this.data = Object.entries(data).reduce((acc, _ref) => {\n let [key, value] = _ref;\n return Object.assign(acc, {\n [key]: this.trackPromise(key, value)\n });\n }, {});\n if (this.done) {\n // All incoming values were resolved\n this.unlistenAbortSignal();\n }\n this.init = responseInit;\n }\n trackPromise(key, value) {\n if (!(value instanceof Promise)) {\n return value;\n }\n this.deferredKeys.push(key);\n this.pendingKeysSet.add(key);\n // We store a little wrapper promise that will be extended with\n // _data/_error props upon resolve/reject\n let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, undefined, data), error => this.onSettle(promise, key, error));\n // Register rejection listeners to avoid uncaught promise rejections on\n // errors or aborted deferred values\n promise.catch(() => {});\n Object.defineProperty(promise, \"_tracked\", {\n get: () => true\n });\n return promise;\n }\n onSettle(promise, key, error, data) {\n if (this.controller.signal.aborted && error instanceof AbortedDeferredError) {\n this.unlistenAbortSignal();\n Object.defineProperty(promise, \"_error\", {\n get: () => error\n });\n return Promise.reject(error);\n }\n this.pendingKeysSet.delete(key);\n if (this.done) {\n // Nothing left to abort!\n this.unlistenAbortSignal();\n }\n // If the promise was resolved/rejected with undefined, we'll throw an error as you\n // should always resolve with a value or null\n if (error === undefined && data === undefined) {\n let undefinedError = new Error(\"Deferred data for key \\\"\" + key + \"\\\" resolved/rejected with `undefined`, \" + \"you must resolve/reject with a value or `null`.\");\n Object.defineProperty(promise, \"_error\", {\n get: () => undefinedError\n });\n this.emit(false, key);\n return Promise.reject(undefinedError);\n }\n if (data === undefined) {\n Object.defineProperty(promise, \"_error\", {\n get: () => error\n });\n this.emit(false, key);\n return Promise.reject(error);\n }\n Object.defineProperty(promise, \"_data\", {\n get: () => data\n });\n this.emit(false, key);\n return data;\n }\n emit(aborted, settledKey) {\n this.subscribers.forEach(subscriber => subscriber(aborted, settledKey));\n }\n subscribe(fn) {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n cancel() {\n this.controller.abort();\n this.pendingKeysSet.forEach((v, k) => this.pendingKeysSet.delete(k));\n this.emit(true);\n }\n async resolveData(signal) {\n let aborted = false;\n if (!this.done) {\n let onAbort = () => this.cancel();\n signal.addEventListener(\"abort\", onAbort);\n aborted = await new Promise(resolve => {\n this.subscribe(aborted => {\n signal.removeEventListener(\"abort\", onAbort);\n if (aborted || this.done) {\n resolve(aborted);\n }\n });\n });\n }\n return aborted;\n }\n get done() {\n return this.pendingKeysSet.size === 0;\n }\n get unwrappedData() {\n invariant(this.data !== null && this.done, \"Can only unwrap data on initialized and settled deferreds\");\n return Object.entries(this.data).reduce((acc, _ref2) => {\n let [key, value] = _ref2;\n return Object.assign(acc, {\n [key]: unwrapTrackedPromise(value)\n });\n }, {});\n }\n get pendingKeys() {\n return Array.from(this.pendingKeysSet);\n }\n}\nfunction isTrackedPromise(value) {\n return value instanceof Promise && value._tracked === true;\n}\nfunction unwrapTrackedPromise(value) {\n if (!isTrackedPromise(value)) {\n return value;\n }\n if (value._error) {\n throw value._error;\n }\n return value._data;\n}\nconst defer = function defer(data, init) {\n if (init === void 0) {\n init = {};\n }\n let responseInit = typeof init === \"number\" ? {\n status: init\n } : init;\n return new DeferredData(data, responseInit);\n};\n/**\n * A redirect response. Sets the status code and the `Location` header.\n * Defaults to \"302 Found\".\n */\nconst redirect = function redirect(url, init) {\n if (init === void 0) {\n init = 302;\n }\n let responseInit = init;\n if (typeof responseInit === \"number\") {\n responseInit = {\n status: responseInit\n };\n } else if (typeof responseInit.status === \"undefined\") {\n responseInit.status = 302;\n }\n let headers = new Headers(responseInit.headers);\n headers.set(\"Location\", url);\n return new Response(null, _extends({}, responseInit, {\n headers\n }));\n};\n/**\n * A redirect response that will force a document reload to the new location.\n * Sets the status code and the `Location` header.\n * Defaults to \"302 Found\".\n */\nconst redirectDocument = (url, init) => {\n let response = redirect(url, init);\n response.headers.set(\"X-Remix-Reload-Document\", \"true\");\n return response;\n};\n/**\n * @private\n * Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies\n */\nclass ErrorResponse {\n constructor(status, statusText, data, internal) {\n if (internal === void 0) {\n internal = false;\n }\n this.status = status;\n this.statusText = statusText || \"\";\n this.internal = internal;\n if (data instanceof Error) {\n this.data = data.toString();\n this.error = data;\n } else {\n this.data = data;\n }\n }\n}\n/**\n * Check if the given error is an ErrorResponse generated from a 4xx/5xx\n * Response thrown from an action/loader\n */\nfunction isRouteErrorResponse(error) {\n return error != null && typeof error.status === \"number\" && typeof error.statusText === \"string\" && typeof error.internal === \"boolean\" && \"data\" in error;\n}\n\nconst validMutationMethodsArr = [\"post\", \"put\", \"patch\", \"delete\"];\nconst validMutationMethods = new Set(validMutationMethodsArr);\nconst validRequestMethodsArr = [\"get\", ...validMutationMethodsArr];\nconst validRequestMethods = new Set(validRequestMethodsArr);\nconst redirectStatusCodes = new Set([301, 302, 303, 307, 308]);\nconst redirectPreserveMethodStatusCodes = new Set([307, 308]);\nconst IDLE_NAVIGATION = {\n state: \"idle\",\n location: undefined,\n formMethod: undefined,\n formAction: undefined,\n formEncType: undefined,\n formData: undefined,\n json: undefined,\n text: undefined\n};\nconst IDLE_FETCHER = {\n state: \"idle\",\n data: undefined,\n formMethod: undefined,\n formAction: undefined,\n formEncType: undefined,\n formData: undefined,\n json: undefined,\n text: undefined\n};\nconst IDLE_BLOCKER = {\n state: \"unblocked\",\n proceed: undefined,\n reset: undefined,\n location: undefined\n};\nconst ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\\/\\/)/i;\nconst defaultMapRouteProperties = route => ({\n hasErrorBoundary: Boolean(route.hasErrorBoundary)\n});\n//#endregion\n////////////////////////////////////////////////////////////////////////////////\n//#region createRouter\n////////////////////////////////////////////////////////////////////////////////\n/**\n * Create a router and listen to history POP navigations\n */\nfunction createRouter(init) {\n const routerWindow = init.window ? init.window : typeof window !== \"undefined\" ? window : undefined;\n const isBrowser = typeof routerWindow !== \"undefined\" && typeof routerWindow.document !== \"undefined\" && typeof routerWindow.document.createElement !== \"undefined\";\n const isServer = !isBrowser;\n invariant(init.routes.length > 0, \"You must provide a non-empty routes array to createRouter\");\n let mapRouteProperties;\n if (init.mapRouteProperties) {\n mapRouteProperties = init.mapRouteProperties;\n } else if (init.detectErrorBoundary) {\n // If they are still using the deprecated version, wrap it with the new API\n let detectErrorBoundary = init.detectErrorBoundary;\n mapRouteProperties = route => ({\n hasErrorBoundary: detectErrorBoundary(route)\n });\n } else {\n mapRouteProperties = defaultMapRouteProperties;\n }\n // Routes keyed by ID\n let manifest = {};\n // Routes in tree format for matching\n let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);\n let inFlightDataRoutes;\n let basename = init.basename || \"/\";\n // Config driven behavior flags\n let future = _extends({\n v7_normalizeFormMethod: false,\n v7_prependBasename: false\n }, init.future);\n // Cleanup function for history\n let unlistenHistory = null;\n // Externally-provided functions to call on all state changes\n let subscribers = new Set();\n // Externally-provided object to hold scroll restoration locations during routing\n let savedScrollPositions = null;\n // Externally-provided function to get scroll restoration keys\n let getScrollRestorationKey = null;\n // Externally-provided function to get current scroll position\n let getScrollPosition = null;\n // One-time flag to control the initial hydration scroll restoration. Because\n // we don't get the saved positions from until _after_\n // the initial render, we need to manually trigger a separate updateState to\n // send along the restoreScrollPosition\n // Set to true if we have `hydrationData` since we assume we were SSR'd and that\n // SSR did the initial scroll restoration.\n let initialScrollRestored = init.hydrationData != null;\n let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);\n let initialErrors = null;\n if (initialMatches == null) {\n // If we do not match a user-provided-route, fall back to the root\n // to allow the error boundary to take over\n let error = getInternalRouterError(404, {\n pathname: init.history.location.pathname\n });\n let {\n matches,\n route\n } = getShortCircuitMatches(dataRoutes);\n initialMatches = matches;\n initialErrors = {\n [route.id]: error\n };\n }\n let initialized =\n // All initialMatches need to be loaded before we're ready. If we have lazy\n // functions around still then we'll need to run them in initialize()\n !initialMatches.some(m => m.route.lazy) && (\n // And we have to either have no loaders or have been provided hydrationData\n !initialMatches.some(m => m.route.loader) || init.hydrationData != null);\n let router;\n let state = {\n historyAction: init.history.action,\n location: init.history.location,\n matches: initialMatches,\n initialized,\n navigation: IDLE_NAVIGATION,\n // Don't restore on initial updateState() if we were SSR'd\n restoreScrollPosition: init.hydrationData != null ? false : null,\n preventScrollReset: false,\n revalidation: \"idle\",\n loaderData: init.hydrationData && init.hydrationData.loaderData || {},\n actionData: init.hydrationData && init.hydrationData.actionData || null,\n errors: init.hydrationData && init.hydrationData.errors || initialErrors,\n fetchers: new Map(),\n blockers: new Map()\n };\n // -- Stateful internal variables to manage navigations --\n // Current navigation in progress (to be committed in completeNavigation)\n let pendingAction = Action.Pop;\n // Should the current navigation prevent the scroll reset if scroll cannot\n // be restored?\n let pendingPreventScrollReset = false;\n // AbortController for the active navigation\n let pendingNavigationController;\n // We use this to avoid touching history in completeNavigation if a\n // revalidation is entirely uninterrupted\n let isUninterruptedRevalidation = false;\n // Use this internal flag to force revalidation of all loaders:\n // - submissions (completed or interrupted)\n // - useRevalidator()\n // - X-Remix-Revalidate (from redirect)\n let isRevalidationRequired = false;\n // Use this internal array to capture routes that require revalidation due\n // to a cancelled deferred on action submission\n let cancelledDeferredRoutes = [];\n // Use this internal array to capture fetcher loads that were cancelled by an\n // action navigation and require revalidation\n let cancelledFetcherLoads = [];\n // AbortControllers for any in-flight fetchers\n let fetchControllers = new Map();\n // Track loads based on the order in which they started\n let incrementingLoadId = 0;\n // Track the outstanding pending navigation data load to be compared against\n // the globally incrementing load when a fetcher load lands after a completed\n // navigation\n let pendingNavigationLoadId = -1;\n // Fetchers that triggered data reloads as a result of their actions\n let fetchReloadIds = new Map();\n // Fetchers that triggered redirect navigations\n let fetchRedirectIds = new Set();\n // Most recent href/match for fetcher.load calls for fetchers\n let fetchLoadMatches = new Map();\n // Store DeferredData instances for active route matches. When a\n // route loader returns defer() we stick one in here. Then, when a nested\n // promise resolves we update loaderData. If a new navigation starts we\n // cancel active deferreds for eliminated routes.\n let activeDeferreds = new Map();\n // Store blocker functions in a separate Map outside of router state since\n // we don't need to update UI state if they change\n let blockerFunctions = new Map();\n // Flag to ignore the next history update, so we can revert the URL change on\n // a POP navigation that was blocked by the user without touching router state\n let ignoreNextHistoryUpdate = false;\n // Initialize the router, all side effects should be kicked off from here.\n // Implemented as a Fluent API for ease of:\n // let router = createRouter(init).initialize();\n function initialize() {\n // If history informs us of a POP navigation, start the navigation but do not update\n // state. We'll update our own state once the navigation completes\n unlistenHistory = init.history.listen(_ref => {\n let {\n action: historyAction,\n location,\n delta\n } = _ref;\n // Ignore this event if it was just us resetting the URL from a\n // blocked POP navigation\n if (ignoreNextHistoryUpdate) {\n ignoreNextHistoryUpdate = false;\n return;\n }\n warning(blockerFunctions.size === 0 || delta != null, \"You are trying to use a blocker on a POP navigation to a location \" + \"that was not created by @remix-run/router. This will fail silently in \" + \"production. This can happen if you are navigating outside the router \" + \"via `window.history.pushState`/`window.location.hash` instead of using \" + \"router navigation APIs. This can also happen if you are using \" + \"createHashRouter and the user manually changes the URL.\");\n let blockerKey = shouldBlockNavigation({\n currentLocation: state.location,\n nextLocation: location,\n historyAction\n });\n if (blockerKey && delta != null) {\n // Restore the URL to match the current UI, but don't update router state\n ignoreNextHistoryUpdate = true;\n init.history.go(delta * -1);\n // Put the blocker into a blocked state\n updateBlocker(blockerKey, {\n state: \"blocked\",\n location,\n proceed() {\n updateBlocker(blockerKey, {\n state: \"proceeding\",\n proceed: undefined,\n reset: undefined,\n location\n });\n // Re-do the same POP navigation we just blocked\n init.history.go(delta);\n },\n reset() {\n let blockers = new Map(state.blockers);\n blockers.set(blockerKey, IDLE_BLOCKER);\n updateState({\n blockers\n });\n }\n });\n return;\n }\n return startNavigation(historyAction, location);\n });\n // Kick off initial data load if needed. Use Pop to avoid modifying history\n // Note we don't do any handling of lazy here. For SPA's it'll get handled\n // in the normal navigation flow. For SSR it's expected that lazy modules are\n // resolved prior to router creation since we can't go into a fallbackElement\n // UI for SSR'd apps\n if (!state.initialized) {\n startNavigation(Action.Pop, state.location);\n }\n return router;\n }\n // Clean up a router and it's side effects\n function dispose() {\n if (unlistenHistory) {\n unlistenHistory();\n }\n subscribers.clear();\n pendingNavigationController && pendingNavigationController.abort();\n state.fetchers.forEach((_, key) => deleteFetcher(key));\n state.blockers.forEach((_, key) => deleteBlocker(key));\n }\n // Subscribe to state updates for the router\n function subscribe(fn) {\n subscribers.add(fn);\n return () => subscribers.delete(fn);\n }\n // Update our state and notify the calling context of the change\n function updateState(newState) {\n state = _extends({}, state, newState);\n subscribers.forEach(subscriber => subscriber(state));\n }\n // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION\n // and setting state.[historyAction/location/matches] to the new route.\n // - Location is a required param\n // - Navigation will always be set to IDLE_NAVIGATION\n // - Can pass any other state in newState\n function completeNavigation(location, newState) {\n var _location$state, _location$state2;\n // Deduce if we're in a loading/actionReload state:\n // - We have committed actionData in the store\n // - The current navigation was a mutation submission\n // - We're past the submitting state and into the loading state\n // - The location being loaded is not the result of a redirect\n let isActionReload = state.actionData != null && state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && state.navigation.state === \"loading\" && ((_location$state = location.state) == null ? void 0 : _location$state._isRedirect) !== true;\n let actionData;\n if (newState.actionData) {\n if (Object.keys(newState.actionData).length > 0) {\n actionData = newState.actionData;\n } else {\n // Empty actionData -> clear prior actionData due to an action error\n actionData = null;\n }\n } else if (isActionReload) {\n // Keep the current data if we're wrapping up the action reload\n actionData = state.actionData;\n } else {\n // Clear actionData on any other completed navigations\n actionData = null;\n }\n // Always preserve any existing loaderData from re-used routes\n let loaderData = newState.loaderData ? mergeLoaderData(state.loaderData, newState.loaderData, newState.matches || [], newState.errors) : state.loaderData;\n // On a successful navigation we can assume we got through all blockers\n // so we can start fresh\n let blockers = state.blockers;\n if (blockers.size > 0) {\n blockers = new Map(blockers);\n blockers.forEach((_, k) => blockers.set(k, IDLE_BLOCKER));\n }\n // Always respect the user flag. Otherwise don't reset on mutation\n // submission navigations unless they redirect\n let preventScrollReset = pendingPreventScrollReset === true || state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && ((_location$state2 = location.state) == null ? void 0 : _location$state2._isRedirect) !== true;\n if (inFlightDataRoutes) {\n dataRoutes = inFlightDataRoutes;\n inFlightDataRoutes = undefined;\n }\n if (isUninterruptedRevalidation) ; else if (pendingAction === Action.Pop) ; else if (pendingAction === Action.Push) {\n init.history.push(location, location.state);\n } else if (pendingAction === Action.Replace) {\n init.history.replace(location, location.state);\n }\n updateState(_extends({}, newState, {\n actionData,\n loaderData,\n historyAction: pendingAction,\n location,\n initialized: true,\n navigation: IDLE_NAVIGATION,\n revalidation: \"idle\",\n restoreScrollPosition: getSavedScrollPosition(location, newState.matches || state.matches),\n preventScrollReset,\n blockers\n }));\n // Reset stateful navigation vars\n pendingAction = Action.Pop;\n pendingPreventScrollReset = false;\n isUninterruptedRevalidation = false;\n isRevalidationRequired = false;\n cancelledDeferredRoutes = [];\n cancelledFetcherLoads = [];\n }\n // Trigger a navigation event, which can either be a numerical POP or a PUSH\n // replace with an optional submission\n async function navigate(to, opts) {\n if (typeof to === \"number\") {\n init.history.go(to);\n return;\n }\n let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, to, opts == null ? void 0 : opts.fromRouteId, opts == null ? void 0 : opts.relative);\n let {\n path,\n submission,\n error\n } = normalizeNavigateOptions(future.v7_normalizeFormMethod, false, normalizedPath, opts);\n let currentLocation = state.location;\n let nextLocation = createLocation(state.location, path, opts && opts.state);\n // When using navigate as a PUSH/REPLACE we aren't reading an already-encoded\n // URL from window.location, so we need to encode it here so the behavior\n // remains the same as POP and non-data-router usages. new URL() does all\n // the same encoding we'd get from a history.pushState/window.location read\n // without having to touch history\n nextLocation = _extends({}, nextLocation, init.history.encodeLocation(nextLocation));\n let userReplace = opts && opts.replace != null ? opts.replace : undefined;\n let historyAction = Action.Push;\n if (userReplace === true) {\n historyAction = Action.Replace;\n } else if (userReplace === false) ; else if (submission != null && isMutationMethod(submission.formMethod) && submission.formAction === state.location.pathname + state.location.search) {\n // By default on submissions to the current location we REPLACE so that\n // users don't have to double-click the back button to get to the prior\n // location. If the user redirects to a different location from the\n // action/loader this will be ignored and the redirect will be a PUSH\n historyAction = Action.Replace;\n }\n let preventScrollReset = opts && \"preventScrollReset\" in opts ? opts.preventScrollReset === true : undefined;\n let blockerKey = shouldBlockNavigation({\n currentLocation,\n nextLocation,\n historyAction\n });\n if (blockerKey) {\n // Put the blocker into a blocked state\n updateBlocker(blockerKey, {\n state: \"blocked\",\n location: nextLocation,\n proceed() {\n updateBlocker(blockerKey, {\n state: \"proceeding\",\n proceed: undefined,\n reset: undefined,\n location: nextLocation\n });\n // Send the same navigation through\n navigate(to, opts);\n },\n reset() {\n let blockers = new Map(state.blockers);\n blockers.set(blockerKey, IDLE_BLOCKER);\n updateState({\n blockers\n });\n }\n });\n return;\n }\n return await startNavigation(historyAction, nextLocation, {\n submission,\n // Send through the formData serialization error if we have one so we can\n // render at the right error boundary after we match routes\n pendingError: error,\n preventScrollReset,\n replace: opts && opts.replace\n });\n }\n // Revalidate all current loaders. If a navigation is in progress or if this\n // is interrupted by a navigation, allow this to \"succeed\" by calling all\n // loaders during the next loader round\n function revalidate() {\n interruptActiveLoads();\n updateState({\n revalidation: \"loading\"\n });\n // If we're currently submitting an action, we don't need to start a new\n // navigation, we'll just let the follow up loader execution call all loaders\n if (state.navigation.state === \"submitting\") {\n return;\n }\n // If we're currently in an idle state, start a new navigation for the current\n // action/location and mark it as uninterrupted, which will skip the history\n // update in completeNavigation\n if (state.navigation.state === \"idle\") {\n startNavigation(state.historyAction, state.location, {\n startUninterruptedRevalidation: true\n });\n return;\n }\n // Otherwise, if we're currently in a loading state, just start a new\n // navigation to the navigation.location but do not trigger an uninterrupted\n // revalidation so that history correctly updates once the navigation completes\n startNavigation(pendingAction || state.historyAction, state.navigation.location, {\n overrideNavigation: state.navigation\n });\n }\n // Start a navigation to the given action/location. Can optionally provide a\n // overrideNavigation which will override the normalLoad in the case of a redirect\n // navigation\n async function startNavigation(historyAction, location, opts) {\n // Abort any in-progress navigations and start a new one. Unset any ongoing\n // uninterrupted revalidations unless told otherwise, since we want this\n // new navigation to update history normally\n pendingNavigationController && pendingNavigationController.abort();\n pendingNavigationController = null;\n pendingAction = historyAction;\n isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true;\n // Save the current scroll position every time we start a new navigation,\n // and track whether we should reset scroll on completion\n saveScrollPosition(state.location, state.matches);\n pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;\n let routesToUse = inFlightDataRoutes || dataRoutes;\n let loadingNavigation = opts && opts.overrideNavigation;\n let matches = matchRoutes(routesToUse, location, basename);\n // Short circuit with a 404 on the root error boundary if we match nothing\n if (!matches) {\n let error = getInternalRouterError(404, {\n pathname: location.pathname\n });\n let {\n matches: notFoundMatches,\n route\n } = getShortCircuitMatches(routesToUse);\n // Cancel all pending deferred on 404s since we don't keep any routes\n cancelActiveDeferreds();\n completeNavigation(location, {\n matches: notFoundMatches,\n loaderData: {},\n errors: {\n [route.id]: error\n }\n });\n return;\n }\n // Short circuit if it's only a hash change and not a revalidation or\n // mutation submission.\n //\n // Ignore on initial page loads because since the initial load will always\n // be \"same hash\". For example, on /page#hash and submit a
\n // which will default to a navigation to /page\n if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {\n completeNavigation(location, {\n matches\n });\n return;\n }\n // Create a controller/Request for this navigation\n pendingNavigationController = new AbortController();\n let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);\n let pendingActionData;\n let pendingError;\n if (opts && opts.pendingError) {\n // If we have a pendingError, it means the user attempted a GET submission\n // with binary FormData so assign here and skip to handleLoaders. That\n // way we handle calling loaders above the boundary etc. It's not really\n // different from an actionError in that sense.\n pendingError = {\n [findNearestBoundary(matches).route.id]: opts.pendingError\n };\n } else if (opts && opts.submission && isMutationMethod(opts.submission.formMethod)) {\n // Call action if we received an action submission\n let actionOutput = await handleAction(request, location, opts.submission, matches, {\n replace: opts.replace\n });\n if (actionOutput.shortCircuited) {\n return;\n }\n pendingActionData = actionOutput.pendingActionData;\n pendingError = actionOutput.pendingActionError;\n loadingNavigation = getLoadingNavigation(location, opts.submission);\n // Create a GET request for the loaders\n request = new Request(request.url, {\n signal: request.signal\n });\n }\n // Call loaders\n let {\n shortCircuited,\n loaderData,\n errors\n } = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, pendingActionData, pendingError);\n if (shortCircuited) {\n return;\n }\n // Clean up now that the action/loaders have completed. Don't clean up if\n // we short circuited because pendingNavigationController will have already\n // been assigned to a new controller for the next navigation\n pendingNavigationController = null;\n completeNavigation(location, _extends({\n matches\n }, pendingActionData ? {\n actionData: pendingActionData\n } : {}, {\n loaderData,\n errors\n }));\n }\n // Call the action matched by the leaf route for this navigation and handle\n // redirects/errors\n async function handleAction(request, location, submission, matches, opts) {\n if (opts === void 0) {\n opts = {};\n }\n interruptActiveLoads();\n // Put us in a submitting state\n let navigation = getSubmittingNavigation(location, submission);\n updateState({\n navigation\n });\n // Call our action and get the result\n let result;\n let actionMatch = getTargetMatch(matches, location);\n if (!actionMatch.route.action && !actionMatch.route.lazy) {\n result = {\n type: ResultType.error,\n error: getInternalRouterError(405, {\n method: request.method,\n pathname: location.pathname,\n routeId: actionMatch.route.id\n })\n };\n } else {\n result = await callLoaderOrAction(\"action\", request, actionMatch, matches, manifest, mapRouteProperties, basename);\n if (request.signal.aborted) {\n return {\n shortCircuited: true\n };\n }\n }\n if (isRedirectResult(result)) {\n let replace;\n if (opts && opts.replace != null) {\n replace = opts.replace;\n } else {\n // If the user didn't explicity indicate replace behavior, replace if\n // we redirected to the exact same location we're currently at to avoid\n // double back-buttons\n replace = result.location === state.location.pathname + state.location.search;\n }\n await startRedirectNavigation(state, result, {\n submission,\n replace\n });\n return {\n shortCircuited: true\n };\n }\n if (isErrorResult(result)) {\n // Store off the pending error - we use it to determine which loaders\n // to call and will commit it when we complete the navigation\n let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);\n // By default, all submissions are REPLACE navigations, but if the\n // action threw an error that'll be rendered in an errorElement, we fall\n // back to PUSH so that the user can use the back button to get back to\n // the pre-submission form location to try again\n if ((opts && opts.replace) !== true) {\n pendingAction = Action.Push;\n }\n return {\n // Send back an empty object we can use to clear out any prior actionData\n pendingActionData: {},\n pendingActionError: {\n [boundaryMatch.route.id]: result.error\n }\n };\n }\n if (isDeferredResult(result)) {\n throw getInternalRouterError(400, {\n type: \"defer-action\"\n });\n }\n return {\n pendingActionData: {\n [actionMatch.route.id]: result.data\n }\n };\n }\n // Call all applicable loaders for the given matches, handling redirects,\n // errors, etc.\n async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, pendingActionData, pendingError) {\n // Figure out the right navigation we want to use for data loading\n let loadingNavigation = overrideNavigation || getLoadingNavigation(location, submission);\n // If this was a redirect from an action we don't have a \"submission\" but\n // we have it on the loading navigation so use that if available\n let activeSubmission = submission || fetcherSubmission || getSubmissionFromNavigation(loadingNavigation);\n let routesToUse = inFlightDataRoutes || dataRoutes;\n let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionData, pendingError);\n // Cancel pending deferreds for no-longer-matched routes or routes we're\n // about to reload. Note that if this is an action reload we would have\n // already cancelled all pending deferreds so this would be a no-op\n cancelActiveDeferreds(routeId => !(matches && matches.some(m => m.route.id === routeId)) || matchesToLoad && matchesToLoad.some(m => m.route.id === routeId));\n pendingNavigationLoadId = ++incrementingLoadId;\n // Short circuit if we have no loaders to run\n if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {\n let updatedFetchers = markFetchRedirectsDone();\n completeNavigation(location, _extends({\n matches,\n loaderData: {},\n // Commit pending error if we're short circuiting\n errors: pendingError || null\n }, pendingActionData ? {\n actionData: pendingActionData\n } : {}, updatedFetchers ? {\n fetchers: new Map(state.fetchers)\n } : {}));\n return {\n shortCircuited: true\n };\n }\n // If this is an uninterrupted revalidation, we remain in our current idle\n // state. If not, we need to switch to our loading state and load data,\n // preserving any new action data or existing action data (in the case of\n // a revalidation interrupting an actionReload)\n if (!isUninterruptedRevalidation) {\n revalidatingFetchers.forEach(rf => {\n let fetcher = state.fetchers.get(rf.key);\n let revalidatingFetcher = getLoadingFetcher(undefined, fetcher ? fetcher.data : undefined);\n state.fetchers.set(rf.key, revalidatingFetcher);\n });\n let actionData = pendingActionData || state.actionData;\n updateState(_extends({\n navigation: loadingNavigation\n }, actionData ? Object.keys(actionData).length === 0 ? {\n actionData: null\n } : {\n actionData\n } : {}, revalidatingFetchers.length > 0 ? {\n fetchers: new Map(state.fetchers)\n } : {}));\n }\n revalidatingFetchers.forEach(rf => {\n if (fetchControllers.has(rf.key)) {\n abortFetcher(rf.key);\n }\n if (rf.controller) {\n // Fetchers use an independent AbortController so that aborting a fetcher\n // (via deleteFetcher) does not abort the triggering navigation that\n // triggered the revalidation\n fetchControllers.set(rf.key, rf.controller);\n }\n });\n // Proxy navigation abort through to revalidation fetchers\n let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(f => abortFetcher(f.key));\n if (pendingNavigationController) {\n pendingNavigationController.signal.addEventListener(\"abort\", abortPendingFetchRevalidations);\n }\n let {\n results,\n loaderResults,\n fetcherResults\n } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);\n if (request.signal.aborted) {\n return {\n shortCircuited: true\n };\n }\n // Clean up _after_ loaders have completed. Don't clean up if we short\n // circuited because fetchControllers would have been aborted and\n // reassigned to new controllers for the next navigation\n if (pendingNavigationController) {\n pendingNavigationController.signal.removeEventListener(\"abort\", abortPendingFetchRevalidations);\n }\n revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key));\n // If any loaders returned a redirect Response, start a new REPLACE navigation\n let redirect = findRedirect(results);\n if (redirect) {\n if (redirect.idx >= matchesToLoad.length) {\n // If this redirect came from a fetcher make sure we mark it in\n // fetchRedirectIds so it doesn't get revalidated on the next set of\n // loader executions\n let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;\n fetchRedirectIds.add(fetcherKey);\n }\n await startRedirectNavigation(state, redirect.result, {\n replace\n });\n return {\n shortCircuited: true\n };\n }\n // Process and commit output from loaders\n let {\n loaderData,\n errors\n } = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds);\n // Wire up subscribers to update loaderData as promises settle\n activeDeferreds.forEach((deferredData, routeId) => {\n deferredData.subscribe(aborted => {\n // Note: No need to updateState here since the TrackedPromise on\n // loaderData is stable across resolve/reject\n // Remove this instance if we were aborted or if promises have settled\n if (aborted || deferredData.done) {\n activeDeferreds.delete(routeId);\n }\n });\n });\n let updatedFetchers = markFetchRedirectsDone();\n let didAbortFetchLoads = abortStaleFetchLoads(pendingNavigationLoadId);\n let shouldUpdateFetchers = updatedFetchers || didAbortFetchLoads || revalidatingFetchers.length > 0;\n return _extends({\n loaderData,\n errors\n }, shouldUpdateFetchers ? {\n fetchers: new Map(state.fetchers)\n } : {});\n }\n function getFetcher(key) {\n return state.fetchers.get(key) || IDLE_FETCHER;\n }\n // Trigger a fetcher load/submit for the given fetcher key\n function fetch(key, routeId, href, opts) {\n if (isServer) {\n throw new Error(\"router.fetch() was called during the server render, but it shouldn't be. \" + \"You are likely calling a useFetcher() method in the body of your component. \" + \"Try moving it to a useEffect or a callback.\");\n }\n if (fetchControllers.has(key)) abortFetcher(key);\n let routesToUse = inFlightDataRoutes || dataRoutes;\n let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, routeId, opts == null ? void 0 : opts.relative);\n let matches = matchRoutes(routesToUse, normalizedPath, basename);\n if (!matches) {\n setFetcherError(key, routeId, getInternalRouterError(404, {\n pathname: normalizedPath\n }));\n return;\n }\n let {\n path,\n submission,\n error\n } = normalizeNavigateOptions(future.v7_normalizeFormMethod, true, normalizedPath, opts);\n if (error) {\n setFetcherError(key, routeId, error);\n return;\n }\n let match = getTargetMatch(matches, path);\n pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;\n if (submission && isMutationMethod(submission.formMethod)) {\n handleFetcherAction(key, routeId, path, match, matches, submission);\n return;\n }\n // Store off the match so we can call it's shouldRevalidate on subsequent\n // revalidations\n fetchLoadMatches.set(key, {\n routeId,\n path\n });\n handleFetcherLoader(key, routeId, path, match, matches, submission);\n }\n // Call the action for the matched fetcher.submit(), and then handle redirects,\n // errors, and revalidation\n async function handleFetcherAction(key, routeId, path, match, requestMatches, submission) {\n interruptActiveLoads();\n fetchLoadMatches.delete(key);\n if (!match.route.action && !match.route.lazy) {\n let error = getInternalRouterError(405, {\n method: submission.formMethod,\n pathname: path,\n routeId: routeId\n });\n setFetcherError(key, routeId, error);\n return;\n }\n // Put this fetcher into it's submitting state\n let existingFetcher = state.fetchers.get(key);\n let fetcher = getSubmittingFetcher(submission, existingFetcher);\n state.fetchers.set(key, fetcher);\n updateState({\n fetchers: new Map(state.fetchers)\n });\n // Call the action for the fetcher\n let abortController = new AbortController();\n let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);\n fetchControllers.set(key, abortController);\n let originatingLoadId = incrementingLoadId;\n let actionResult = await callLoaderOrAction(\"action\", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);\n if (fetchRequest.signal.aborted) {\n // We can delete this so long as we weren't aborted by ou our own fetcher\n // re-submit which would have put _new_ controller is in fetchControllers\n if (fetchControllers.get(key) === abortController) {\n fetchControllers.delete(key);\n }\n return;\n }\n if (isRedirectResult(actionResult)) {\n fetchControllers.delete(key);\n if (pendingNavigationLoadId > originatingLoadId) {\n // A new navigation was kicked off after our action started, so that\n // should take precedence over this redirect navigation. We already\n // set isRevalidationRequired so all loaders for the new route should\n // fire unless opted out via shouldRevalidate\n let doneFetcher = getDoneFetcher(undefined);\n state.fetchers.set(key, doneFetcher);\n updateState({\n fetchers: new Map(state.fetchers)\n });\n return;\n } else {\n fetchRedirectIds.add(key);\n let loadingFetcher = getLoadingFetcher(submission);\n state.fetchers.set(key, loadingFetcher);\n updateState({\n fetchers: new Map(state.fetchers)\n });\n return startRedirectNavigation(state, actionResult, {\n submission,\n isFetchActionRedirect: true\n });\n }\n }\n // Process any non-redirect errors thrown\n if (isErrorResult(actionResult)) {\n setFetcherError(key, routeId, actionResult.error);\n return;\n }\n if (isDeferredResult(actionResult)) {\n throw getInternalRouterError(400, {\n type: \"defer-action\"\n });\n }\n // Start the data load for current matches, or the next location if we're\n // in the middle of a navigation\n let nextLocation = state.navigation.location || state.location;\n let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal);\n let routesToUse = inFlightDataRoutes || dataRoutes;\n let matches = state.navigation.state !== \"idle\" ? matchRoutes(routesToUse, state.navigation.location, basename) : state.matches;\n invariant(matches, \"Didn't find any matches after fetcher action\");\n let loadId = ++incrementingLoadId;\n fetchReloadIds.set(key, loadId);\n let loadFetcher = getLoadingFetcher(submission, actionResult.data);\n state.fetchers.set(key, loadFetcher);\n let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, {\n [match.route.id]: actionResult.data\n }, undefined // No need to send through errors since we short circuit above\n );\n // Put all revalidating fetchers into the loading state, except for the\n // current fetcher which we want to keep in it's current loading state which\n // contains it's action submission info + action data\n revalidatingFetchers.filter(rf => rf.key !== key).forEach(rf => {\n let staleKey = rf.key;\n let existingFetcher = state.fetchers.get(staleKey);\n let revalidatingFetcher = getLoadingFetcher(undefined, existingFetcher ? existingFetcher.data : undefined);\n state.fetchers.set(staleKey, revalidatingFetcher);\n if (fetchControllers.has(staleKey)) {\n abortFetcher(staleKey);\n }\n if (rf.controller) {\n fetchControllers.set(staleKey, rf.controller);\n }\n });\n updateState({\n fetchers: new Map(state.fetchers)\n });\n let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));\n abortController.signal.addEventListener(\"abort\", abortPendingFetchRevalidations);\n let {\n results,\n loaderResults,\n fetcherResults\n } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);\n if (abortController.signal.aborted) {\n return;\n }\n abortController.signal.removeEventListener(\"abort\", abortPendingFetchRevalidations);\n fetchReloadIds.delete(key);\n fetchControllers.delete(key);\n revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));\n let redirect = findRedirect(results);\n if (redirect) {\n if (redirect.idx >= matchesToLoad.length) {\n // If this redirect came from a fetcher make sure we mark it in\n // fetchRedirectIds so it doesn't get revalidated on the next set of\n // loader executions\n let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;\n fetchRedirectIds.add(fetcherKey);\n }\n return startRedirectNavigation(state, redirect.result);\n }\n // Process and commit output from loaders\n let {\n loaderData,\n errors\n } = processLoaderData(state, state.matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);\n // Since we let revalidations complete even if the submitting fetcher was\n // deleted, only put it back to idle if it hasn't been deleted\n if (state.fetchers.has(key)) {\n let doneFetcher = getDoneFetcher(actionResult.data);\n state.fetchers.set(key, doneFetcher);\n }\n let didAbortFetchLoads = abortStaleFetchLoads(loadId);\n // If we are currently in a navigation loading state and this fetcher is\n // more recent than the navigation, we want the newer data so abort the\n // navigation and complete it with the fetcher data\n if (state.navigation.state === \"loading\" && loadId > pendingNavigationLoadId) {\n invariant(pendingAction, \"Expected pending action\");\n pendingNavigationController && pendingNavigationController.abort();\n completeNavigation(state.navigation.location, {\n matches,\n loaderData,\n errors,\n fetchers: new Map(state.fetchers)\n });\n } else {\n // otherwise just update with the fetcher data, preserving any existing\n // loaderData for loaders that did not need to reload. We have to\n // manually merge here since we aren't going through completeNavigation\n updateState(_extends({\n errors,\n loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors)\n }, didAbortFetchLoads || revalidatingFetchers.length > 0 ? {\n fetchers: new Map(state.fetchers)\n } : {}));\n isRevalidationRequired = false;\n }\n }\n // Call the matched loader for fetcher.load(), handling redirects, errors, etc.\n async function handleFetcherLoader(key, routeId, path, match, matches, submission) {\n let existingFetcher = state.fetchers.get(key);\n // Put this fetcher into it's loading state\n let loadingFetcher = getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined);\n state.fetchers.set(key, loadingFetcher);\n updateState({\n fetchers: new Map(state.fetchers)\n });\n // Call the loader for this fetcher route match\n let abortController = new AbortController();\n let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);\n fetchControllers.set(key, abortController);\n let originatingLoadId = incrementingLoadId;\n let result = await callLoaderOrAction(\"loader\", fetchRequest, match, matches, manifest, mapRouteProperties, basename);\n // Deferred isn't supported for fetcher loads, await everything and treat it\n // as a normal load. resolveDeferredData will return undefined if this\n // fetcher gets aborted, so we just leave result untouched and short circuit\n // below if that happens\n if (isDeferredResult(result)) {\n result = (await resolveDeferredData(result, fetchRequest.signal, true)) || result;\n }\n // We can delete this so long as we weren't aborted by our our own fetcher\n // re-load which would have put _new_ controller is in fetchControllers\n if (fetchControllers.get(key) === abortController) {\n fetchControllers.delete(key);\n }\n if (fetchRequest.signal.aborted) {\n return;\n }\n // If the loader threw a redirect Response, start a new REPLACE navigation\n if (isRedirectResult(result)) {\n if (pendingNavigationLoadId > originatingLoadId) {\n // A new navigation was kicked off after our loader started, so that\n // should take precedence over this redirect navigation\n let doneFetcher = getDoneFetcher(undefined);\n state.fetchers.set(key, doneFetcher);\n updateState({\n fetchers: new Map(state.fetchers)\n });\n return;\n } else {\n fetchRedirectIds.add(key);\n await startRedirectNavigation(state, result);\n return;\n }\n }\n // Process any non-redirect errors thrown\n if (isErrorResult(result)) {\n let boundaryMatch = findNearestBoundary(state.matches, routeId);\n state.fetchers.delete(key);\n // TODO: In remix, this would reset to IDLE_NAVIGATION if it was a catch -\n // do we need to behave any differently with our non-redirect errors?\n // What if it was a non-redirect Response?\n updateState({\n fetchers: new Map(state.fetchers),\n errors: {\n [boundaryMatch.route.id]: result.error\n }\n });\n return;\n }\n invariant(!isDeferredResult(result), \"Unhandled fetcher deferred data\");\n // Put the fetcher back into an idle state\n let doneFetcher = getDoneFetcher(result.data);\n state.fetchers.set(key, doneFetcher);\n updateState({\n fetchers: new Map(state.fetchers)\n });\n }\n /**\n * Utility function to handle redirects returned from an action or loader.\n * Normally, a redirect \"replaces\" the navigation that triggered it. So, for\n * example:\n *\n * - user is on /a\n * - user clicks a link to /b\n * - loader for /b redirects to /c\n *\n * In a non-JS app the browser would track the in-flight navigation to /b and\n * then replace it with /c when it encountered the redirect response. In\n * the end it would only ever update the URL bar with /c.\n *\n * In client-side routing using pushState/replaceState, we aim to emulate\n * this behavior and we also do not update history until the end of the\n * navigation (including processed redirects). This means that we never\n * actually touch history until we've processed redirects, so we just use\n * the history action from the original navigation (PUSH or REPLACE).\n */\n async function startRedirectNavigation(state, redirect, _temp) {\n let {\n submission,\n replace,\n isFetchActionRedirect\n } = _temp === void 0 ? {} : _temp;\n if (redirect.revalidate) {\n isRevalidationRequired = true;\n }\n let redirectLocation = createLocation(state.location, redirect.location, // TODO: This can be removed once we get rid of useTransition in Remix v2\n _extends({\n _isRedirect: true\n }, isFetchActionRedirect ? {\n _isFetchActionRedirect: true\n } : {}));\n invariant(redirectLocation, \"Expected a location on the redirect navigation\");\n if (isBrowser) {\n let isDocumentReload = false;\n if (redirect.reloadDocument) {\n // Hard reload if the response contained X-Remix-Reload-Document\n isDocumentReload = true;\n } else if (ABSOLUTE_URL_REGEX.test(redirect.location)) {\n const url = init.history.createURL(redirect.location);\n isDocumentReload =\n // Hard reload if it's an absolute URL to a new origin\n url.origin !== routerWindow.location.origin ||\n // Hard reload if it's an absolute URL that does not match our basename\n stripBasename(url.pathname, basename) == null;\n }\n if (isDocumentReload) {\n if (replace) {\n routerWindow.location.replace(redirect.location);\n } else {\n routerWindow.location.assign(redirect.location);\n }\n return;\n }\n }\n // There's no need to abort on redirects, since we don't detect the\n // redirect until the action/loaders have settled\n pendingNavigationController = null;\n let redirectHistoryAction = replace === true ? Action.Replace : Action.Push;\n // Use the incoming submission if provided, fallback on the active one in\n // state.navigation\n let activeSubmission = submission || getSubmissionFromNavigation(state.navigation);\n // If this was a 307/308 submission we want to preserve the HTTP method and\n // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the\n // redirected location\n if (redirectPreserveMethodStatusCodes.has(redirect.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {\n await startNavigation(redirectHistoryAction, redirectLocation, {\n submission: _extends({}, activeSubmission, {\n formAction: redirect.location\n }),\n // Preserve this flag across redirects\n preventScrollReset: pendingPreventScrollReset\n });\n } else if (isFetchActionRedirect) {\n // For a fetch action redirect, we kick off a new loading navigation\n // without the fetcher submission, but we send it along for shouldRevalidate\n await startNavigation(redirectHistoryAction, redirectLocation, {\n overrideNavigation: getLoadingNavigation(redirectLocation),\n fetcherSubmission: activeSubmission,\n // Preserve this flag across redirects\n preventScrollReset: pendingPreventScrollReset\n });\n } else {\n // If we have a submission, we will preserve it through the redirect navigation\n let overrideNavigation = getLoadingNavigation(redirectLocation, activeSubmission);\n await startNavigation(redirectHistoryAction, redirectLocation, {\n overrideNavigation,\n // Preserve this flag across redirects\n preventScrollReset: pendingPreventScrollReset\n });\n }\n }\n async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {\n // Call all navigation loaders and revalidating fetcher loaders in parallel,\n // then slice off the results into separate arrays so we can handle them\n // accordingly\n let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction(\"loader\", request, match, matches, manifest, mapRouteProperties, basename)), ...fetchersToLoad.map(f => {\n if (f.matches && f.match && f.controller) {\n return callLoaderOrAction(\"loader\", createClientSideRequest(init.history, f.path, f.controller.signal), f.match, f.matches, manifest, mapRouteProperties, basename);\n } else {\n let error = {\n type: ResultType.error,\n error: getInternalRouterError(404, {\n pathname: f.path\n })\n };\n return error;\n }\n })]);\n let loaderResults = results.slice(0, matchesToLoad.length);\n let fetcherResults = results.slice(matchesToLoad.length);\n await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, loaderResults.map(() => request.signal), false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(f => f.match), fetcherResults, fetchersToLoad.map(f => f.controller ? f.controller.signal : null), true)]);\n return {\n results,\n loaderResults,\n fetcherResults\n };\n }\n function interruptActiveLoads() {\n // Every interruption triggers a revalidation\n isRevalidationRequired = true;\n // Cancel pending route-level deferreds and mark cancelled routes for\n // revalidation\n cancelledDeferredRoutes.push(...cancelActiveDeferreds());\n // Abort in-flight fetcher loads\n fetchLoadMatches.forEach((_, key) => {\n if (fetchControllers.has(key)) {\n cancelledFetcherLoads.push(key);\n abortFetcher(key);\n }\n });\n }\n function setFetcherError(key, routeId, error) {\n let boundaryMatch = findNearestBoundary(state.matches, routeId);\n deleteFetcher(key);\n updateState({\n errors: {\n [boundaryMatch.route.id]: error\n },\n fetchers: new Map(state.fetchers)\n });\n }\n function deleteFetcher(key) {\n let fetcher = state.fetchers.get(key);\n // Don't abort the controller if this is a deletion of a fetcher.submit()\n // in it's loading phase since - we don't want to abort the corresponding\n // revalidation and want them to complete and land\n if (fetchControllers.has(key) && !(fetcher && fetcher.state === \"loading\" && fetchReloadIds.has(key))) {\n abortFetcher(key);\n }\n fetchLoadMatches.delete(key);\n fetchReloadIds.delete(key);\n fetchRedirectIds.delete(key);\n state.fetchers.delete(key);\n }\n function abortFetcher(key) {\n let controller = fetchControllers.get(key);\n invariant(controller, \"Expected fetch controller: \" + key);\n controller.abort();\n fetchControllers.delete(key);\n }\n function markFetchersDone(keys) {\n for (let key of keys) {\n let fetcher = getFetcher(key);\n let doneFetcher = getDoneFetcher(fetcher.data);\n state.fetchers.set(key, doneFetcher);\n }\n }\n function markFetchRedirectsDone() {\n let doneKeys = [];\n let updatedFetchers = false;\n for (let key of fetchRedirectIds) {\n let fetcher = state.fetchers.get(key);\n invariant(fetcher, \"Expected fetcher: \" + key);\n if (fetcher.state === \"loading\") {\n fetchRedirectIds.delete(key);\n doneKeys.push(key);\n updatedFetchers = true;\n }\n }\n markFetchersDone(doneKeys);\n return updatedFetchers;\n }\n function abortStaleFetchLoads(landedId) {\n let yeetedKeys = [];\n for (let [key, id] of fetchReloadIds) {\n if (id < landedId) {\n let fetcher = state.fetchers.get(key);\n invariant(fetcher, \"Expected fetcher: \" + key);\n if (fetcher.state === \"loading\") {\n abortFetcher(key);\n fetchReloadIds.delete(key);\n yeetedKeys.push(key);\n }\n }\n }\n markFetchersDone(yeetedKeys);\n return yeetedKeys.length > 0;\n }\n function getBlocker(key, fn) {\n let blocker = state.blockers.get(key) || IDLE_BLOCKER;\n if (blockerFunctions.get(key) !== fn) {\n blockerFunctions.set(key, fn);\n }\n return blocker;\n }\n function deleteBlocker(key) {\n state.blockers.delete(key);\n blockerFunctions.delete(key);\n }\n // Utility function to update blockers, ensuring valid state transitions\n function updateBlocker(key, newBlocker) {\n let blocker = state.blockers.get(key) || IDLE_BLOCKER;\n // Poor mans state machine :)\n // https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM\n invariant(blocker.state === \"unblocked\" && newBlocker.state === \"blocked\" || blocker.state === \"blocked\" && newBlocker.state === \"blocked\" || blocker.state === \"blocked\" && newBlocker.state === \"proceeding\" || blocker.state === \"blocked\" && newBlocker.state === \"unblocked\" || blocker.state === \"proceeding\" && newBlocker.state === \"unblocked\", \"Invalid blocker state transition: \" + blocker.state + \" -> \" + newBlocker.state);\n let blockers = new Map(state.blockers);\n blockers.set(key, newBlocker);\n updateState({\n blockers\n });\n }\n function shouldBlockNavigation(_ref2) {\n let {\n currentLocation,\n nextLocation,\n historyAction\n } = _ref2;\n if (blockerFunctions.size === 0) {\n return;\n }\n // We ony support a single active blocker at the moment since we don't have\n // any compelling use cases for multi-blocker yet\n if (blockerFunctions.size > 1) {\n warning(false, \"A router only supports one blocker at a time\");\n }\n let entries = Array.from(blockerFunctions.entries());\n let [blockerKey, blockerFunction] = entries[entries.length - 1];\n let blocker = state.blockers.get(blockerKey);\n if (blocker && blocker.state === \"proceeding\") {\n // If the blocker is currently proceeding, we don't need to re-check\n // it and can let this navigation continue\n return;\n }\n // At this point, we know we're unblocked/blocked so we need to check the\n // user-provided blocker function\n if (blockerFunction({\n currentLocation,\n nextLocation,\n historyAction\n })) {\n return blockerKey;\n }\n }\n function cancelActiveDeferreds(predicate) {\n let cancelledRouteIds = [];\n activeDeferreds.forEach((dfd, routeId) => {\n if (!predicate || predicate(routeId)) {\n // Cancel the deferred - but do not remove from activeDeferreds here -\n // we rely on the subscribers to do that so our tests can assert proper\n // cleanup via _internalActiveDeferreds\n dfd.cancel();\n cancelledRouteIds.push(routeId);\n activeDeferreds.delete(routeId);\n }\n });\n return cancelledRouteIds;\n }\n // Opt in to capturing and reporting scroll positions during navigations,\n // used by the component\n function enableScrollRestoration(positions, getPosition, getKey) {\n savedScrollPositions = positions;\n getScrollPosition = getPosition;\n getScrollRestorationKey = getKey || null;\n // Perform initial hydration scroll restoration, since we miss the boat on\n // the initial updateState() because we've not yet rendered \n // and therefore have no savedScrollPositions available\n if (!initialScrollRestored && state.navigation === IDLE_NAVIGATION) {\n initialScrollRestored = true;\n let y = getSavedScrollPosition(state.location, state.matches);\n if (y != null) {\n updateState({\n restoreScrollPosition: y\n });\n }\n }\n return () => {\n savedScrollPositions = null;\n getScrollPosition = null;\n getScrollRestorationKey = null;\n };\n }\n function getScrollKey(location, matches) {\n if (getScrollRestorationKey) {\n let key = getScrollRestorationKey(location, matches.map(m => createUseMatchesMatch(m, state.loaderData)));\n return key || location.key;\n }\n return location.key;\n }\n function saveScrollPosition(location, matches) {\n if (savedScrollPositions && getScrollPosition) {\n let key = getScrollKey(location, matches);\n savedScrollPositions[key] = getScrollPosition();\n }\n }\n function getSavedScrollPosition(location, matches) {\n if (savedScrollPositions) {\n let key = getScrollKey(location, matches);\n let y = savedScrollPositions[key];\n if (typeof y === \"number\") {\n return y;\n }\n }\n return null;\n }\n function _internalSetRoutes(newRoutes) {\n manifest = {};\n inFlightDataRoutes = convertRoutesToDataRoutes(newRoutes, mapRouteProperties, undefined, manifest);\n }\n router = {\n get basename() {\n return basename;\n },\n get state() {\n return state;\n },\n get routes() {\n return dataRoutes;\n },\n initialize,\n subscribe,\n enableScrollRestoration,\n navigate,\n fetch,\n revalidate,\n // Passthrough to history-aware createHref used by useHref so we get proper\n // hash-aware URLs in DOM paths\n createHref: to => init.history.createHref(to),\n encodeLocation: to => init.history.encodeLocation(to),\n getFetcher,\n deleteFetcher,\n dispose,\n getBlocker,\n deleteBlocker,\n _internalFetchControllers: fetchControllers,\n _internalActiveDeferreds: activeDeferreds,\n // TODO: Remove setRoutes, it's temporary to avoid dealing with\n // updating the tree while validating the update algorithm.\n _internalSetRoutes\n };\n return router;\n}\n//#endregion\n////////////////////////////////////////////////////////////////////////////////\n//#region createStaticHandler\n////////////////////////////////////////////////////////////////////////////////\nconst UNSAFE_DEFERRED_SYMBOL = Symbol(\"deferred\");\nfunction createStaticHandler(routes, opts) {\n invariant(routes.length > 0, \"You must provide a non-empty routes array to createStaticHandler\");\n let manifest = {};\n let basename = (opts ? opts.basename : null) || \"/\";\n let mapRouteProperties;\n if (opts != null && opts.mapRouteProperties) {\n mapRouteProperties = opts.mapRouteProperties;\n } else if (opts != null && opts.detectErrorBoundary) {\n // If they are still using the deprecated version, wrap it with the new API\n let detectErrorBoundary = opts.detectErrorBoundary;\n mapRouteProperties = route => ({\n hasErrorBoundary: detectErrorBoundary(route)\n });\n } else {\n mapRouteProperties = defaultMapRouteProperties;\n }\n let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);\n /**\n * The query() method is intended for document requests, in which we want to\n * call an optional action and potentially multiple loaders for all nested\n * routes. It returns a StaticHandlerContext object, which is very similar\n * to the router state (location, loaderData, actionData, errors, etc.) and\n * also adds SSR-specific information such as the statusCode and headers\n * from action/loaders Responses.\n *\n * It _should_ never throw and should report all errors through the\n * returned context.errors object, properly associating errors to their error\n * boundary. Additionally, it tracks _deepestRenderedBoundaryId which can be\n * used to emulate React error boundaries during SSr by performing a second\n * pass only down to the boundaryId.\n *\n * The one exception where we do not return a StaticHandlerContext is when a\n * redirect response is returned or thrown from any action/loader. We\n * propagate that out and return the raw Response so the HTTP server can\n * return it directly.\n */\n async function query(request, _temp2) {\n let {\n requestContext\n } = _temp2 === void 0 ? {} : _temp2;\n let url = new URL(request.url);\n let method = request.method;\n let location = createLocation(\"\", createPath(url), null, \"default\");\n let matches = matchRoutes(dataRoutes, location, basename);\n // SSR supports HEAD requests while SPA doesn't\n if (!isValidMethod(method) && method !== \"HEAD\") {\n let error = getInternalRouterError(405, {\n method\n });\n let {\n matches: methodNotAllowedMatches,\n route\n } = getShortCircuitMatches(dataRoutes);\n return {\n basename,\n location,\n matches: methodNotAllowedMatches,\n loaderData: {},\n actionData: null,\n errors: {\n [route.id]: error\n },\n statusCode: error.status,\n loaderHeaders: {},\n actionHeaders: {},\n activeDeferreds: null\n };\n } else if (!matches) {\n let error = getInternalRouterError(404, {\n pathname: location.pathname\n });\n let {\n matches: notFoundMatches,\n route\n } = getShortCircuitMatches(dataRoutes);\n return {\n basename,\n location,\n matches: notFoundMatches,\n loaderData: {},\n actionData: null,\n errors: {\n [route.id]: error\n },\n statusCode: error.status,\n loaderHeaders: {},\n actionHeaders: {},\n activeDeferreds: null\n };\n }\n let result = await queryImpl(request, location, matches, requestContext);\n if (isResponse(result)) {\n return result;\n }\n // When returning StaticHandlerContext, we patch back in the location here\n // since we need it for React Context. But this helps keep our submit and\n // loadRouteData operating on a Request instead of a Location\n return _extends({\n location,\n basename\n }, result);\n }\n /**\n * The queryRoute() method is intended for targeted route requests, either\n * for fetch ?_data requests or resource route requests. In this case, we\n * are only ever calling a single action or loader, and we are returning the\n * returned value directly. In most cases, this will be a Response returned\n * from the action/loader, but it may be a primitive or other value as well -\n * and in such cases the calling context should handle that accordingly.\n *\n * We do respect the throw/return differentiation, so if an action/loader\n * throws, then this method will throw the value. This is important so we\n * can do proper boundary identification in Remix where a thrown Response\n * must go to the Catch Boundary but a returned Response is happy-path.\n *\n * One thing to note is that any Router-initiated Errors that make sense\n * to associate with a status code will be thrown as an ErrorResponse\n * instance which include the raw Error, such that the calling context can\n * serialize the error as they see fit while including the proper response\n * code. Examples here are 404 and 405 errors that occur prior to reaching\n * any user-defined loaders.\n */\n async function queryRoute(request, _temp3) {\n let {\n routeId,\n requestContext\n } = _temp3 === void 0 ? {} : _temp3;\n let url = new URL(request.url);\n let method = request.method;\n let location = createLocation(\"\", createPath(url), null, \"default\");\n let matches = matchRoutes(dataRoutes, location, basename);\n // SSR supports HEAD requests while SPA doesn't\n if (!isValidMethod(method) && method !== \"HEAD\" && method !== \"OPTIONS\") {\n throw getInternalRouterError(405, {\n method\n });\n } else if (!matches) {\n throw getInternalRouterError(404, {\n pathname: location.pathname\n });\n }\n let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);\n if (routeId && !match) {\n throw getInternalRouterError(403, {\n pathname: location.pathname,\n routeId\n });\n } else if (!match) {\n // This should never hit I don't think?\n throw getInternalRouterError(404, {\n pathname: location.pathname\n });\n }\n let result = await queryImpl(request, location, matches, requestContext, match);\n if (isResponse(result)) {\n return result;\n }\n let error = result.errors ? Object.values(result.errors)[0] : undefined;\n if (error !== undefined) {\n // If we got back result.errors, that means the loader/action threw\n // _something_ that wasn't a Response, but it's not guaranteed/required\n // to be an `instanceof Error` either, so we have to use throw here to\n // preserve the \"error\" state outside of queryImpl.\n throw error;\n }\n // Pick off the right state value to return\n if (result.actionData) {\n return Object.values(result.actionData)[0];\n }\n if (result.loaderData) {\n var _result$activeDeferre;\n let data = Object.values(result.loaderData)[0];\n if ((_result$activeDeferre = result.activeDeferreds) != null && _result$activeDeferre[match.route.id]) {\n data[UNSAFE_DEFERRED_SYMBOL] = result.activeDeferreds[match.route.id];\n }\n return data;\n }\n return undefined;\n }\n async function queryImpl(request, location, matches, requestContext, routeMatch) {\n invariant(request.signal, \"query()/queryRoute() requests must contain an AbortController signal\");\n try {\n if (isMutationMethod(request.method.toLowerCase())) {\n let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);\n return result;\n }\n let result = await loadRouteData(request, matches, requestContext, routeMatch);\n return isResponse(result) ? result : _extends({}, result, {\n actionData: null,\n actionHeaders: {}\n });\n } catch (e) {\n // If the user threw/returned a Response in callLoaderOrAction, we throw\n // it to bail out and then return or throw here based on whether the user\n // returned or threw\n if (isQueryRouteResponse(e)) {\n if (e.type === ResultType.error) {\n throw e.response;\n }\n return e.response;\n }\n // Redirects are always returned since they don't propagate to catch\n // boundaries\n if (isRedirectResponse(e)) {\n return e;\n }\n throw e;\n }\n }\n async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {\n let result;\n if (!actionMatch.route.action && !actionMatch.route.lazy) {\n let error = getInternalRouterError(405, {\n method: request.method,\n pathname: new URL(request.url).pathname,\n routeId: actionMatch.route.id\n });\n if (isRouteRequest) {\n throw error;\n }\n result = {\n type: ResultType.error,\n error\n };\n } else {\n result = await callLoaderOrAction(\"action\", request, actionMatch, matches, manifest, mapRouteProperties, basename, {\n isStaticRequest: true,\n isRouteRequest,\n requestContext\n });\n if (request.signal.aborted) {\n let method = isRouteRequest ? \"queryRoute\" : \"query\";\n throw new Error(method + \"() call aborted\");\n }\n }\n if (isRedirectResult(result)) {\n // Uhhhh - this should never happen, we should always throw these from\n // callLoaderOrAction, but the type narrowing here keeps TS happy and we\n // can get back on the \"throw all redirect responses\" train here should\n // this ever happen :/\n throw new Response(null, {\n status: result.status,\n headers: {\n Location: result.location\n }\n });\n }\n if (isDeferredResult(result)) {\n let error = getInternalRouterError(400, {\n type: \"defer-action\"\n });\n if (isRouteRequest) {\n throw error;\n }\n result = {\n type: ResultType.error,\n error\n };\n }\n if (isRouteRequest) {\n // Note: This should only be non-Response values if we get here, since\n // isRouteRequest should throw any Response received in callLoaderOrAction\n if (isErrorResult(result)) {\n throw result.error;\n }\n return {\n matches: [actionMatch],\n loaderData: {},\n actionData: {\n [actionMatch.route.id]: result.data\n },\n errors: null,\n // Note: statusCode + headers are unused here since queryRoute will\n // return the raw Response or value\n statusCode: 200,\n loaderHeaders: {},\n actionHeaders: {},\n activeDeferreds: null\n };\n }\n if (isErrorResult(result)) {\n // Store off the pending error - we use it to determine which loaders\n // to call and will commit it when we complete the navigation\n let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);\n let context = await loadRouteData(request, matches, requestContext, undefined, {\n [boundaryMatch.route.id]: result.error\n });\n // action status codes take precedence over loader status codes\n return _extends({}, context, {\n statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,\n actionData: null,\n actionHeaders: _extends({}, result.headers ? {\n [actionMatch.route.id]: result.headers\n } : {})\n });\n }\n // Create a GET request for the loaders\n let loaderRequest = new Request(request.url, {\n headers: request.headers,\n redirect: request.redirect,\n signal: request.signal\n });\n let context = await loadRouteData(loaderRequest, matches, requestContext);\n return _extends({}, context, result.statusCode ? {\n statusCode: result.statusCode\n } : {}, {\n actionData: {\n [actionMatch.route.id]: result.data\n },\n actionHeaders: _extends({}, result.headers ? {\n [actionMatch.route.id]: result.headers\n } : {})\n });\n }\n async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {\n let isRouteRequest = routeMatch != null;\n // Short circuit if we have no loaders to run (queryRoute())\n if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {\n throw getInternalRouterError(400, {\n method: request.method,\n pathname: new URL(request.url).pathname,\n routeId: routeMatch == null ? void 0 : routeMatch.route.id\n });\n }\n let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);\n let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);\n // Short circuit if we have no loaders to run (query())\n if (matchesToLoad.length === 0) {\n return {\n matches,\n // Add a null for all matched routes for proper revalidation on the client\n loaderData: matches.reduce((acc, m) => Object.assign(acc, {\n [m.route.id]: null\n }), {}),\n errors: pendingActionError || null,\n statusCode: 200,\n loaderHeaders: {},\n activeDeferreds: null\n };\n }\n let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction(\"loader\", request, match, matches, manifest, mapRouteProperties, basename, {\n isStaticRequest: true,\n isRouteRequest,\n requestContext\n }))]);\n if (request.signal.aborted) {\n let method = isRouteRequest ? \"queryRoute\" : \"query\";\n throw new Error(method + \"() call aborted\");\n }\n // Process and commit output from loaders\n let activeDeferreds = new Map();\n let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionError, activeDeferreds);\n // Add a null for any non-loader matches for proper revalidation on the client\n let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));\n matches.forEach(match => {\n if (!executedLoaders.has(match.route.id)) {\n context.loaderData[match.route.id] = null;\n }\n });\n return _extends({}, context, {\n matches,\n activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null\n });\n }\n return {\n dataRoutes,\n query,\n queryRoute\n };\n}\n//#endregion\n////////////////////////////////////////////////////////////////////////////////\n//#region Helpers\n////////////////////////////////////////////////////////////////////////////////\n/**\n * Given an existing StaticHandlerContext and an error thrown at render time,\n * provide an updated StaticHandlerContext suitable for a second SSR render\n */\nfunction getStaticContextFromError(routes, context, error) {\n let newContext = _extends({}, context, {\n statusCode: 500,\n errors: {\n [context._deepestRenderedBoundaryId || routes[0].id]: error\n }\n });\n return newContext;\n}\nfunction isSubmissionNavigation(opts) {\n return opts != null && (\"formData\" in opts && opts.formData != null || \"body\" in opts && opts.body !== undefined);\n}\nfunction normalizeTo(location, matches, basename, prependBasename, to, fromRouteId, relative) {\n let contextualMatches;\n let activeRouteMatch;\n if (fromRouteId != null && relative !== \"path\") {\n // Grab matches up to the calling route so our route-relative logic is\n // relative to the correct source route. When using relative:path,\n // fromRouteId is ignored since that is always relative to the current\n // location path\n contextualMatches = [];\n for (let match of matches) {\n contextualMatches.push(match);\n if (match.route.id === fromRouteId) {\n activeRouteMatch = match;\n break;\n }\n }\n } else {\n contextualMatches = matches;\n activeRouteMatch = matches[matches.length - 1];\n }\n // Resolve the relative path\n let path = resolveTo(to ? to : \".\", getPathContributingMatches(contextualMatches).map(m => m.pathnameBase), stripBasename(location.pathname, basename) || location.pathname, relative === \"path\");\n // When `to` is not specified we inherit search/hash from the current\n // location, unlike when to=\".\" and we just inherit the path.\n // See https://github.com/remix-run/remix/issues/927\n if (to == null) {\n path.search = location.search;\n path.hash = location.hash;\n }\n // Add an ?index param for matched index routes if we don't already have one\n if ((to == null || to === \"\" || to === \".\") && activeRouteMatch && activeRouteMatch.route.index && !hasNakedIndexQuery(path.search)) {\n path.search = path.search ? path.search.replace(/^\\?/, \"?index&\") : \"?index\";\n }\n // If we're operating within a basename, prepend it to the pathname. If\n // this is a root navigation, then just use the raw basename which allows\n // the basename to have full control over the presence of a trailing slash\n // on root actions\n if (prependBasename && basename !== \"/\") {\n path.pathname = path.pathname === \"/\" ? basename : joinPaths([basename, path.pathname]);\n }\n return createPath(path);\n}\n// Normalize navigation options by converting formMethod=GET formData objects to\n// URLSearchParams so they behave identically to links with query params\nfunction normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {\n // Return location verbatim on non-submission navigations\n if (!opts || !isSubmissionNavigation(opts)) {\n return {\n path\n };\n }\n if (opts.formMethod && !isValidMethod(opts.formMethod)) {\n return {\n path,\n error: getInternalRouterError(405, {\n method: opts.formMethod\n })\n };\n }\n let getInvalidBodyError = () => ({\n path,\n error: getInternalRouterError(400, {\n type: \"invalid-body\"\n })\n });\n // Create a Submission on non-GET navigations\n let rawFormMethod = opts.formMethod || \"get\";\n let formMethod = normalizeFormMethod ? rawFormMethod.toUpperCase() : rawFormMethod.toLowerCase();\n let formAction = stripHashFromPath(path);\n if (opts.body !== undefined) {\n if (opts.formEncType === \"text/plain\") {\n // text only support POST/PUT/PATCH/DELETE submissions\n if (!isMutationMethod(formMethod)) {\n return getInvalidBodyError();\n }\n let text = typeof opts.body === \"string\" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?\n // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data\n Array.from(opts.body.entries()).reduce((acc, _ref3) => {\n let [name, value] = _ref3;\n return \"\" + acc + name + \"=\" + value + \"\\n\";\n }, \"\") : String(opts.body);\n return {\n path,\n submission: {\n formMethod,\n formAction,\n formEncType: opts.formEncType,\n formData: undefined,\n json: undefined,\n text\n }\n };\n } else if (opts.formEncType === \"application/json\") {\n // json only supports POST/PUT/PATCH/DELETE submissions\n if (!isMutationMethod(formMethod)) {\n return getInvalidBodyError();\n }\n try {\n let json = typeof opts.body === \"string\" ? JSON.parse(opts.body) : opts.body;\n return {\n path,\n submission: {\n formMethod,\n formAction,\n formEncType: opts.formEncType,\n formData: undefined,\n json,\n text: undefined\n }\n };\n } catch (e) {\n return getInvalidBodyError();\n }\n }\n }\n invariant(typeof FormData === \"function\", \"FormData is not available in this environment\");\n let searchParams;\n let formData;\n if (opts.formData) {\n searchParams = convertFormDataToSearchParams(opts.formData);\n formData = opts.formData;\n } else if (opts.body instanceof FormData) {\n searchParams = convertFormDataToSearchParams(opts.body);\n formData = opts.body;\n } else if (opts.body instanceof URLSearchParams) {\n searchParams = opts.body;\n formData = convertSearchParamsToFormData(searchParams);\n } else if (opts.body == null) {\n searchParams = new URLSearchParams();\n formData = new FormData();\n } else {\n try {\n searchParams = new URLSearchParams(opts.body);\n formData = convertSearchParamsToFormData(searchParams);\n } catch (e) {\n return getInvalidBodyError();\n }\n }\n let submission = {\n formMethod,\n formAction,\n formEncType: opts && opts.formEncType || \"application/x-www-form-urlencoded\",\n formData,\n json: undefined,\n text: undefined\n };\n if (isMutationMethod(submission.formMethod)) {\n return {\n path,\n submission\n };\n }\n // Flatten submission onto URLSearchParams for GET submissions\n let parsedPath = parsePath(path);\n // On GET navigation submissions we can drop the ?index param from the\n // resulting location since all loaders will run. But fetcher GET submissions\n // only run a single loader so we need to preserve any incoming ?index params\n if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {\n searchParams.append(\"index\", \"\");\n }\n parsedPath.search = \"?\" + searchParams;\n return {\n path: createPath(parsedPath),\n submission\n };\n}\n// Filter out all routes below any caught error as they aren't going to\n// render so we don't need to load them\nfunction getLoaderMatchesUntilBoundary(matches, boundaryId) {\n let boundaryMatches = matches;\n if (boundaryId) {\n let index = matches.findIndex(m => m.route.id === boundaryId);\n if (index >= 0) {\n boundaryMatches = matches.slice(0, index);\n }\n }\n return boundaryMatches;\n}\nfunction getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, pendingActionData, pendingError) {\n let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;\n let currentUrl = history.createURL(state.location);\n let nextUrl = history.createURL(location);\n // Pick navigation matches that are net-new or qualify for revalidation\n let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;\n let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);\n let navigationMatches = boundaryMatches.filter((match, index) => {\n if (match.route.lazy) {\n // We haven't loaded this route yet so we don't know if it's got a loader!\n return true;\n }\n if (match.route.loader == null) {\n return false;\n }\n // Always call the loader on new route instances and pending defer cancellations\n if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {\n return true;\n }\n // This is the default implementation for when we revalidate. If the route\n // provides it's own implementation, then we give them full control but\n // provide this value so they can leverage it if needed after they check\n // their own specific use cases\n let currentRouteMatch = state.matches[index];\n let nextRouteMatch = match;\n return shouldRevalidateLoader(match, _extends({\n currentUrl,\n currentParams: currentRouteMatch.params,\n nextUrl,\n nextParams: nextRouteMatch.params\n }, submission, {\n actionResult,\n defaultShouldRevalidate:\n // Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate\n isRevalidationRequired ||\n // Clicked the same link, resubmitted a GET form\n currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||\n // Search params affect all loaders\n currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)\n }));\n });\n // Pick fetcher.loads that need to be revalidated\n let revalidatingFetchers = [];\n fetchLoadMatches.forEach((f, key) => {\n // Don't revalidate if fetcher won't be present in the subsequent render\n if (!matches.some(m => m.route.id === f.routeId)) {\n return;\n }\n let fetcherMatches = matchRoutes(routesToUse, f.path, basename);\n // If the fetcher path no longer matches, push it in with null matches so\n // we can trigger a 404 in callLoadersAndMaybeResolveData. Note this is\n // currently only a use-case for Remix HMR where the route tree can change\n // at runtime and remove a route previously loaded via a fetcher\n if (!fetcherMatches) {\n revalidatingFetchers.push({\n key,\n routeId: f.routeId,\n path: f.path,\n matches: null,\n match: null,\n controller: null\n });\n return;\n }\n // Revalidating fetchers are decoupled from the route matches since they\n // load from a static href. They revalidate based on explicit revalidation\n // (submission, useRevalidator, or X-Remix-Revalidate)\n let fetcher = state.fetchers.get(key);\n let fetcherMatch = getTargetMatch(fetcherMatches, f.path);\n let shouldRevalidate = false;\n if (fetchRedirectIds.has(key)) {\n // Never trigger a revalidation of an actively redirecting fetcher\n shouldRevalidate = false;\n } else if (cancelledFetcherLoads.includes(key)) {\n // Always revalidate if the fetcher was cancelled\n shouldRevalidate = true;\n } else if (fetcher && fetcher.state !== \"idle\" && fetcher.data === undefined) {\n // If the fetcher hasn't ever completed loading yet, then this isn't a\n // revalidation, it would just be a brand new load if an explicit\n // revalidation is required\n shouldRevalidate = isRevalidationRequired;\n } else {\n // Otherwise fall back on any user-defined shouldRevalidate, defaulting\n // to explicit revalidations only\n shouldRevalidate = shouldRevalidateLoader(fetcherMatch, _extends({\n currentUrl,\n currentParams: state.matches[state.matches.length - 1].params,\n nextUrl,\n nextParams: matches[matches.length - 1].params\n }, submission, {\n actionResult,\n defaultShouldRevalidate: isRevalidationRequired\n }));\n }\n if (shouldRevalidate) {\n revalidatingFetchers.push({\n key,\n routeId: f.routeId,\n path: f.path,\n matches: fetcherMatches,\n match: fetcherMatch,\n controller: new AbortController()\n });\n }\n });\n return [navigationMatches, revalidatingFetchers];\n}\nfunction isNewLoader(currentLoaderData, currentMatch, match) {\n let isNew =\n // [a] -> [a, b]\n !currentMatch ||\n // [a, b] -> [a, c]\n match.route.id !== currentMatch.route.id;\n // Handle the case that we don't have data for a re-used route, potentially\n // from a prior error or from a cancelled pending deferred\n let isMissingData = currentLoaderData[match.route.id] === undefined;\n // Always load if this is a net-new route or we don't yet have data\n return isNew || isMissingData;\n}\nfunction isNewRouteInstance(currentMatch, match) {\n let currentPath = currentMatch.route.path;\n return (\n // param change for this match, /users/123 -> /users/456\n currentMatch.pathname !== match.pathname ||\n // splat param changed, which is not present in match.path\n // e.g. /files/images/avatar.jpg -> files/finances.xls\n currentPath != null && currentPath.endsWith(\"*\") && currentMatch.params[\"*\"] !== match.params[\"*\"]\n );\n}\nfunction shouldRevalidateLoader(loaderMatch, arg) {\n if (loaderMatch.route.shouldRevalidate) {\n let routeChoice = loaderMatch.route.shouldRevalidate(arg);\n if (typeof routeChoice === \"boolean\") {\n return routeChoice;\n }\n }\n return arg.defaultShouldRevalidate;\n}\n/**\n * Execute route.lazy() methods to lazily load route modules (loader, action,\n * shouldRevalidate) and update the routeManifest in place which shares objects\n * with dataRoutes so those get updated as well.\n */\nasync function loadLazyRouteModule(route, mapRouteProperties, manifest) {\n if (!route.lazy) {\n return;\n }\n let lazyRoute = await route.lazy();\n // If the lazy route function was executed and removed by another parallel\n // call then we can return - first lazy() to finish wins because the return\n // value of lazy is expected to be static\n if (!route.lazy) {\n return;\n }\n let routeToUpdate = manifest[route.id];\n invariant(routeToUpdate, \"No route found in manifest\");\n // Update the route in place. This should be safe because there's no way\n // we could yet be sitting on this route as we can't get there without\n // resolving lazy() first.\n //\n // This is different than the HMR \"update\" use-case where we may actively be\n // on the route being updated. The main concern boils down to \"does this\n // mutation affect any ongoing navigations or any current state.matches\n // values?\". If not, it should be safe to update in place.\n let routeUpdates = {};\n for (let lazyRouteProperty in lazyRoute) {\n let staticRouteValue = routeToUpdate[lazyRouteProperty];\n let isPropertyStaticallyDefined = staticRouteValue !== undefined &&\n // This property isn't static since it should always be updated based\n // on the route updates\n lazyRouteProperty !== \"hasErrorBoundary\";\n warning(!isPropertyStaticallyDefined, \"Route \\\"\" + routeToUpdate.id + \"\\\" has a static property \\\"\" + lazyRouteProperty + \"\\\" \" + \"defined but its lazy function is also returning a value for this property. \" + (\"The lazy route property \\\"\" + lazyRouteProperty + \"\\\" will be ignored.\"));\n if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {\n routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];\n }\n }\n // Mutate the route with the provided updates. Do this first so we pass\n // the updated version to mapRouteProperties\n Object.assign(routeToUpdate, routeUpdates);\n // Mutate the `hasErrorBoundary` property on the route based on the route\n // updates and remove the `lazy` function so we don't resolve the lazy\n // route again.\n Object.assign(routeToUpdate, _extends({}, mapRouteProperties(routeToUpdate), {\n lazy: undefined\n }));\n}\nasync function callLoaderOrAction(type, request, match, matches, manifest, mapRouteProperties, basename, opts) {\n if (opts === void 0) {\n opts = {};\n }\n let resultType;\n let result;\n let onReject;\n let runHandler = handler => {\n // Setup a promise we can race against so that abort signals short circuit\n let reject;\n let abortPromise = new Promise((_, r) => reject = r);\n onReject = () => reject();\n request.signal.addEventListener(\"abort\", onReject);\n return Promise.race([handler({\n request,\n params: match.params,\n context: opts.requestContext\n }), abortPromise]);\n };\n try {\n let handler = match.route[type];\n if (match.route.lazy) {\n if (handler) {\n // Run statically defined handler in parallel with lazy()\n let values = await Promise.all([runHandler(handler), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);\n result = values[0];\n } else {\n // Load lazy route module, then run any returned handler\n await loadLazyRouteModule(match.route, mapRouteProperties, manifest);\n handler = match.route[type];\n if (handler) {\n // Handler still run even if we got interrupted to maintain consistency\n // with un-abortable behavior of handler execution on non-lazy or\n // previously-lazy-loaded routes\n result = await runHandler(handler);\n } else if (type === \"action\") {\n let url = new URL(request.url);\n let pathname = url.pathname + url.search;\n throw getInternalRouterError(405, {\n method: request.method,\n pathname,\n routeId: match.route.id\n });\n } else {\n // lazy() route has no loader to run. Short circuit here so we don't\n // hit the invariant below that errors on returning undefined.\n return {\n type: ResultType.data,\n data: undefined\n };\n }\n }\n } else if (!handler) {\n let url = new URL(request.url);\n let pathname = url.pathname + url.search;\n throw getInternalRouterError(404, {\n pathname\n });\n } else {\n result = await runHandler(handler);\n }\n invariant(result !== undefined, \"You defined \" + (type === \"action\" ? \"an action\" : \"a loader\") + \" for route \" + (\"\\\"\" + match.route.id + \"\\\" but didn't return anything from your `\" + type + \"` \") + \"function. Please return a value or `null`.\");\n } catch (e) {\n resultType = ResultType.error;\n result = e;\n } finally {\n if (onReject) {\n request.signal.removeEventListener(\"abort\", onReject);\n }\n }\n if (isResponse(result)) {\n let status = result.status;\n // Process redirects\n if (redirectStatusCodes.has(status)) {\n let location = result.headers.get(\"Location\");\n invariant(location, \"Redirects returned/thrown from loaders/actions must have a Location header\");\n // Support relative routing in internal redirects\n if (!ABSOLUTE_URL_REGEX.test(location)) {\n location = normalizeTo(new URL(request.url), matches.slice(0, matches.indexOf(match) + 1), basename, true, location);\n } else if (!opts.isStaticRequest) {\n // Strip off the protocol+origin for same-origin + same-basename absolute\n // redirects. If this is a static request, we can let it go back to the\n // browser as-is\n let currentUrl = new URL(request.url);\n let url = location.startsWith(\"//\") ? new URL(currentUrl.protocol + location) : new URL(location);\n let isSameBasename = stripBasename(url.pathname, basename) != null;\n if (url.origin === currentUrl.origin && isSameBasename) {\n location = url.pathname + url.search + url.hash;\n }\n }\n // Don't process redirects in the router during static requests requests.\n // Instead, throw the Response and let the server handle it with an HTTP\n // redirect. We also update the Location header in place in this flow so\n // basename and relative routing is taken into account\n if (opts.isStaticRequest) {\n result.headers.set(\"Location\", location);\n throw result;\n }\n return {\n type: ResultType.redirect,\n status,\n location,\n revalidate: result.headers.get(\"X-Remix-Revalidate\") !== null,\n reloadDocument: result.headers.get(\"X-Remix-Reload-Document\") !== null\n };\n }\n // For SSR single-route requests, we want to hand Responses back directly\n // without unwrapping. We do this with the QueryRouteResponse wrapper\n // interface so we can know whether it was returned or thrown\n if (opts.isRouteRequest) {\n let queryRouteResponse = {\n type: resultType === ResultType.error ? ResultType.error : ResultType.data,\n response: result\n };\n throw queryRouteResponse;\n }\n let data;\n let contentType = result.headers.get(\"Content-Type\");\n // Check between word boundaries instead of startsWith() due to the last\n // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type\n if (contentType && /\\bapplication\\/json\\b/.test(contentType)) {\n data = await result.json();\n } else {\n data = await result.text();\n }\n if (resultType === ResultType.error) {\n return {\n type: resultType,\n error: new ErrorResponse(status, result.statusText, data),\n headers: result.headers\n };\n }\n return {\n type: ResultType.data,\n data,\n statusCode: result.status,\n headers: result.headers\n };\n }\n if (resultType === ResultType.error) {\n return {\n type: resultType,\n error: result\n };\n }\n if (isDeferredData(result)) {\n var _result$init, _result$init2;\n return {\n type: ResultType.deferred,\n deferredData: result,\n statusCode: (_result$init = result.init) == null ? void 0 : _result$init.status,\n headers: ((_result$init2 = result.init) == null ? void 0 : _result$init2.headers) && new Headers(result.init.headers)\n };\n }\n return {\n type: ResultType.data,\n data: result\n };\n}\n// Utility method for creating the Request instances for loaders/actions during\n// client-side navigations and fetches. During SSR we will always have a\n// Request instance from the static handler (query/queryRoute)\nfunction createClientSideRequest(history, location, signal, submission) {\n let url = history.createURL(stripHashFromPath(location)).toString();\n let init = {\n signal\n };\n if (submission && isMutationMethod(submission.formMethod)) {\n let {\n formMethod,\n formEncType\n } = submission;\n // Didn't think we needed this but it turns out unlike other methods, patch\n // won't be properly normalized to uppercase and results in a 405 error.\n // See: https://fetch.spec.whatwg.org/#concept-method\n init.method = formMethod.toUpperCase();\n if (formEncType === \"application/json\") {\n init.headers = new Headers({\n \"Content-Type\": formEncType\n });\n init.body = JSON.stringify(submission.json);\n } else if (formEncType === \"text/plain\") {\n // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)\n init.body = submission.text;\n } else if (formEncType === \"application/x-www-form-urlencoded\" && submission.formData) {\n // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)\n init.body = convertFormDataToSearchParams(submission.formData);\n } else {\n // Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)\n init.body = submission.formData;\n }\n }\n return new Request(url, init);\n}\nfunction convertFormDataToSearchParams(formData) {\n let searchParams = new URLSearchParams();\n for (let [key, value] of formData.entries()) {\n // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs\n searchParams.append(key, typeof value === \"string\" ? value : value.name);\n }\n return searchParams;\n}\nfunction convertSearchParamsToFormData(searchParams) {\n let formData = new FormData();\n for (let [key, value] of searchParams.entries()) {\n formData.append(key, value);\n }\n return formData;\n}\nfunction processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds) {\n // Fill in loaderData/errors from our loaders\n let loaderData = {};\n let errors = null;\n let statusCode;\n let foundError = false;\n let loaderHeaders = {};\n // Process loader results into state.loaderData/state.errors\n results.forEach((result, index) => {\n let id = matchesToLoad[index].route.id;\n invariant(!isRedirectResult(result), \"Cannot handle redirect results in processLoaderData\");\n if (isErrorResult(result)) {\n // Look upwards from the matched route for the closest ancestor\n // error boundary, defaulting to the root match\n let boundaryMatch = findNearestBoundary(matches, id);\n let error = result.error;\n // If we have a pending action error, we report it at the highest-route\n // that throws a loader error, and then clear it out to indicate that\n // it was consumed\n if (pendingError) {\n error = Object.values(pendingError)[0];\n pendingError = undefined;\n }\n errors = errors || {};\n // Prefer higher error values if lower errors bubble to the same boundary\n if (errors[boundaryMatch.route.id] == null) {\n errors[boundaryMatch.route.id] = error;\n }\n // Clear our any prior loaderData for the throwing route\n loaderData[id] = undefined;\n // Once we find our first (highest) error, we set the status code and\n // prevent deeper status codes from overriding\n if (!foundError) {\n foundError = true;\n statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;\n }\n if (result.headers) {\n loaderHeaders[id] = result.headers;\n }\n } else {\n if (isDeferredResult(result)) {\n activeDeferreds.set(id, result.deferredData);\n loaderData[id] = result.deferredData.data;\n } else {\n loaderData[id] = result.data;\n }\n // Error status codes always override success status codes, but if all\n // loaders are successful we take the deepest status code.\n if (result.statusCode != null && result.statusCode !== 200 && !foundError) {\n statusCode = result.statusCode;\n }\n if (result.headers) {\n loaderHeaders[id] = result.headers;\n }\n }\n });\n // If we didn't consume the pending action error (i.e., all loaders\n // resolved), then consume it here. Also clear out any loaderData for the\n // throwing route\n if (pendingError) {\n errors = pendingError;\n loaderData[Object.keys(pendingError)[0]] = undefined;\n }\n return {\n loaderData,\n errors,\n statusCode: statusCode || 200,\n loaderHeaders\n };\n}\nfunction processLoaderData(state, matches, matchesToLoad, results, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds) {\n let {\n loaderData,\n errors\n } = processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds);\n // Process results from our revalidating fetchers\n for (let index = 0; index < revalidatingFetchers.length; index++) {\n let {\n key,\n match,\n controller\n } = revalidatingFetchers[index];\n invariant(fetcherResults !== undefined && fetcherResults[index] !== undefined, \"Did not find corresponding fetcher result\");\n let result = fetcherResults[index];\n // Process fetcher non-redirect errors\n if (controller && controller.signal.aborted) {\n // Nothing to do for aborted fetchers\n continue;\n } else if (isErrorResult(result)) {\n let boundaryMatch = findNearestBoundary(state.matches, match == null ? void 0 : match.route.id);\n if (!(errors && errors[boundaryMatch.route.id])) {\n errors = _extends({}, errors, {\n [boundaryMatch.route.id]: result.error\n });\n }\n state.fetchers.delete(key);\n } else if (isRedirectResult(result)) {\n // Should never get here, redirects should get processed above, but we\n // keep this to type narrow to a success result in the else\n invariant(false, \"Unhandled fetcher revalidation redirect\");\n } else if (isDeferredResult(result)) {\n // Should never get here, deferred data should be awaited for fetchers\n // in resolveDeferredResults\n invariant(false, \"Unhandled fetcher deferred data\");\n } else {\n let doneFetcher = getDoneFetcher(result.data);\n state.fetchers.set(key, doneFetcher);\n }\n }\n return {\n loaderData,\n errors\n };\n}\nfunction mergeLoaderData(loaderData, newLoaderData, matches, errors) {\n let mergedLoaderData = _extends({}, newLoaderData);\n for (let match of matches) {\n let id = match.route.id;\n if (newLoaderData.hasOwnProperty(id)) {\n if (newLoaderData[id] !== undefined) {\n mergedLoaderData[id] = newLoaderData[id];\n }\n } else if (loaderData[id] !== undefined && match.route.loader) {\n // Preserve existing keys not included in newLoaderData and where a loader\n // wasn't removed by HMR\n mergedLoaderData[id] = loaderData[id];\n }\n if (errors && errors.hasOwnProperty(id)) {\n // Don't keep any loader data below the boundary\n break;\n }\n }\n return mergedLoaderData;\n}\n// Find the nearest error boundary, looking upwards from the leaf route (or the\n// route specified by routeId) for the closest ancestor error boundary,\n// defaulting to the root match\nfunction findNearestBoundary(matches, routeId) {\n let eligibleMatches = routeId ? matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1) : [...matches];\n return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];\n}\nfunction getShortCircuitMatches(routes) {\n // Prefer a root layout route if present, otherwise shim in a route object\n let route = routes.find(r => r.index || !r.path || r.path === \"/\") || {\n id: \"__shim-error-route__\"\n };\n return {\n matches: [{\n params: {},\n pathname: \"\",\n pathnameBase: \"\",\n route\n }],\n route\n };\n}\nfunction getInternalRouterError(status, _temp4) {\n let {\n pathname,\n routeId,\n method,\n type\n } = _temp4 === void 0 ? {} : _temp4;\n let statusText = \"Unknown Server Error\";\n let errorMessage = \"Unknown @remix-run/router error\";\n if (status === 400) {\n statusText = \"Bad Request\";\n if (method && pathname && routeId) {\n errorMessage = \"You made a \" + method + \" request to \\\"\" + pathname + \"\\\" but \" + (\"did not provide a `loader` for route \\\"\" + routeId + \"\\\", \") + \"so there is no way to handle the request.\";\n } else if (type === \"defer-action\") {\n errorMessage = \"defer() is not supported in actions\";\n } else if (type === \"invalid-body\") {\n errorMessage = \"Unable to encode submission body\";\n }\n } else if (status === 403) {\n statusText = \"Forbidden\";\n errorMessage = \"Route \\\"\" + routeId + \"\\\" does not match URL \\\"\" + pathname + \"\\\"\";\n } else if (status === 404) {\n statusText = \"Not Found\";\n errorMessage = \"No route matches URL \\\"\" + pathname + \"\\\"\";\n } else if (status === 405) {\n statusText = \"Method Not Allowed\";\n if (method && pathname && routeId) {\n errorMessage = \"You made a \" + method.toUpperCase() + \" request to \\\"\" + pathname + \"\\\" but \" + (\"did not provide an `action` for route \\\"\" + routeId + \"\\\", \") + \"so there is no way to handle the request.\";\n } else if (method) {\n errorMessage = \"Invalid request method \\\"\" + method.toUpperCase() + \"\\\"\";\n }\n }\n return new ErrorResponse(status || 500, statusText, new Error(errorMessage), true);\n}\n// Find any returned redirect errors, starting from the lowest match\nfunction findRedirect(results) {\n for (let i = results.length - 1; i >= 0; i--) {\n let result = results[i];\n if (isRedirectResult(result)) {\n return {\n result,\n idx: i\n };\n }\n }\n}\nfunction stripHashFromPath(path) {\n let parsedPath = typeof path === \"string\" ? parsePath(path) : path;\n return createPath(_extends({}, parsedPath, {\n hash: \"\"\n }));\n}\nfunction isHashChangeOnly(a, b) {\n if (a.pathname !== b.pathname || a.search !== b.search) {\n return false;\n }\n if (a.hash === \"\") {\n // /page -> /page#hash\n return b.hash !== \"\";\n } else if (a.hash === b.hash) {\n // /page#hash -> /page#hash\n return true;\n } else if (b.hash !== \"\") {\n // /page#hash -> /page#other\n return true;\n }\n // If the hash is removed the browser will re-perform a request to the server\n // /page#hash -> /page\n return false;\n}\nfunction isDeferredResult(result) {\n return result.type === ResultType.deferred;\n}\nfunction isErrorResult(result) {\n return result.type === ResultType.error;\n}\nfunction isRedirectResult(result) {\n return (result && result.type) === ResultType.redirect;\n}\nfunction isDeferredData(value) {\n let deferred = value;\n return deferred && typeof deferred === \"object\" && typeof deferred.data === \"object\" && typeof deferred.subscribe === \"function\" && typeof deferred.cancel === \"function\" && typeof deferred.resolveData === \"function\";\n}\nfunction isResponse(value) {\n return value != null && typeof value.status === \"number\" && typeof value.statusText === \"string\" && typeof value.headers === \"object\" && typeof value.body !== \"undefined\";\n}\nfunction isRedirectResponse(result) {\n if (!isResponse(result)) {\n return false;\n }\n let status = result.status;\n let location = result.headers.get(\"Location\");\n return status >= 300 && status <= 399 && location != null;\n}\nfunction isQueryRouteResponse(obj) {\n return obj && isResponse(obj.response) && (obj.type === ResultType.data || obj.type === ResultType.error);\n}\nfunction isValidMethod(method) {\n return validRequestMethods.has(method.toLowerCase());\n}\nfunction isMutationMethod(method) {\n return validMutationMethods.has(method.toLowerCase());\n}\nasync function resolveDeferredResults(currentMatches, matchesToLoad, results, signals, isFetcher, currentLoaderData) {\n for (let index = 0; index < results.length; index++) {\n let result = results[index];\n let match = matchesToLoad[index];\n // If we don't have a match, then we can have a deferred result to do\n // anything with. This is for revalidating fetchers where the route was\n // removed during HMR\n if (!match) {\n continue;\n }\n let currentMatch = currentMatches.find(m => m.route.id === match.route.id);\n let isRevalidatingLoader = currentMatch != null && !isNewRouteInstance(currentMatch, match) && (currentLoaderData && currentLoaderData[match.route.id]) !== undefined;\n if (isDeferredResult(result) && (isFetcher || isRevalidatingLoader)) {\n // Note: we do not have to touch activeDeferreds here since we race them\n // against the signal in resolveDeferredData and they'll get aborted\n // there if needed\n let signal = signals[index];\n invariant(signal, \"Expected an AbortSignal for revalidating fetcher deferred result\");\n await resolveDeferredData(result, signal, isFetcher).then(result => {\n if (result) {\n results[index] = result || results[index];\n }\n });\n }\n }\n}\nasync function resolveDeferredData(result, signal, unwrap) {\n if (unwrap === void 0) {\n unwrap = false;\n }\n let aborted = await result.deferredData.resolveData(signal);\n if (aborted) {\n return;\n }\n if (unwrap) {\n try {\n return {\n type: ResultType.data,\n data: result.deferredData.unwrappedData\n };\n } catch (e) {\n // Handle any TrackedPromise._error values encountered while unwrapping\n return {\n type: ResultType.error,\n error: e\n };\n }\n }\n return {\n type: ResultType.data,\n data: result.deferredData.data\n };\n}\nfunction hasNakedIndexQuery(search) {\n return new URLSearchParams(search).getAll(\"index\").some(v => v === \"\");\n}\n// Note: This should match the format exported by useMatches, so if you change\n// this please also change that :) Eventually we'll DRY this up\nfunction createUseMatchesMatch(match, loaderData) {\n let {\n route,\n pathname,\n params\n } = match;\n return {\n id: route.id,\n pathname,\n params,\n data: loaderData[route.id],\n handle: route.handle\n };\n}\nfunction getTargetMatch(matches, location) {\n let search = typeof location === \"string\" ? parsePath(location).search : location.search;\n if (matches[matches.length - 1].route.index && hasNakedIndexQuery(search || \"\")) {\n // Return the leaf index route when index is present\n return matches[matches.length - 1];\n }\n // Otherwise grab the deepest \"path contributing\" match (ignoring index and\n // pathless layout routes)\n let pathMatches = getPathContributingMatches(matches);\n return pathMatches[pathMatches.length - 1];\n}\nfunction getSubmissionFromNavigation(navigation) {\n let {\n formMethod,\n formAction,\n formEncType,\n text,\n formData,\n json\n } = navigation;\n if (!formMethod || !formAction || !formEncType) {\n return;\n }\n if (text != null) {\n return {\n formMethod,\n formAction,\n formEncType,\n formData: undefined,\n json: undefined,\n text\n };\n } else if (formData != null) {\n return {\n formMethod,\n formAction,\n formEncType,\n formData,\n json: undefined,\n text: undefined\n };\n } else if (json !== undefined) {\n return {\n formMethod,\n formAction,\n formEncType,\n formData: undefined,\n json,\n text: undefined\n };\n }\n}\nfunction getLoadingNavigation(location, submission) {\n if (submission) {\n let navigation = {\n state: \"loading\",\n location,\n formMethod: submission.formMethod,\n formAction: submission.formAction,\n formEncType: submission.formEncType,\n formData: submission.formData,\n json: submission.json,\n text: submission.text\n };\n return navigation;\n } else {\n let navigation = {\n state: \"loading\",\n location,\n formMethod: undefined,\n formAction: undefined,\n formEncType: undefined,\n formData: undefined,\n json: undefined,\n text: undefined\n };\n return navigation;\n }\n}\nfunction getSubmittingNavigation(location, submission) {\n let navigation = {\n state: \"submitting\",\n location,\n formMethod: submission.formMethod,\n formAction: submission.formAction,\n formEncType: submission.formEncType,\n formData: submission.formData,\n json: submission.json,\n text: submission.text\n };\n return navigation;\n}\nfunction getLoadingFetcher(submission, data) {\n if (submission) {\n let fetcher = {\n state: \"loading\",\n formMethod: submission.formMethod,\n formAction: submission.formAction,\n formEncType: submission.formEncType,\n formData: submission.formData,\n json: submission.json,\n text: submission.text,\n data,\n \" _hasFetcherDoneAnything \": true\n };\n return fetcher;\n } else {\n let fetcher = {\n state: \"loading\",\n formMethod: undefined,\n formAction: undefined,\n formEncType: undefined,\n formData: undefined,\n json: undefined,\n text: undefined,\n data,\n \" _hasFetcherDoneAnything \": true\n };\n return fetcher;\n }\n}\nfunction getSubmittingFetcher(submission, existingFetcher) {\n let fetcher = {\n state: \"submitting\",\n formMethod: submission.formMethod,\n formAction: submission.formAction,\n formEncType: submission.formEncType,\n formData: submission.formData,\n json: submission.json,\n text: submission.text,\n data: existingFetcher ? existingFetcher.data : undefined,\n \" _hasFetcherDoneAnything \": true\n };\n return fetcher;\n}\nfunction getDoneFetcher(data) {\n let fetcher = {\n state: \"idle\",\n formMethod: undefined,\n formAction: undefined,\n formEncType: undefined,\n formData: undefined,\n json: undefined,\n text: undefined,\n data,\n \" _hasFetcherDoneAnything \": true\n };\n return fetcher;\n}\n//#endregion\n\n\n//# sourceMappingURL=router.js.map\n\n\n//# sourceURL=webpack://parus_8_panels_plugin/./node_modules/@remix-run/router/dist/router.js?"); + +/***/ }), + +/***/ "./app.text.js": +/*!*********************!*\ + !*** ./app.text.js ***! + \*********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ BUTTONS: () => (/* binding */ BUTTONS),\n/* harmony export */ ERROR: () => (/* binding */ ERROR),\n/* harmony export */ ERROR_HTTP: () => (/* binding */ ERROR_HTTP),\n/* harmony export */ INPUTS: () => (/* binding */ INPUTS),\n/* harmony export */ TEXTS: () => (/* binding */ TEXTS),\n/* harmony export */ TITLES: () => (/* binding */ TITLES)\n/* harmony export */ });\n/*\r\n Парус 8 - Панели мониторинга\r\n Текстовые ресурсы и константы\r\n*/\n\n//----------------\n//Интерфейс модуля\n//----------------\n\n//Заголовки\nconst TITLES = {\n MAIN_MENU: \"Доступные панели\",\n //Главное меню\n INFO: \"Информация\",\n //Информационный блок\n WARN: \"Предупреждение\",\n //Блок предупреждения\n ERR: \"Ошибка\",\n //Информация об ошибке\n DEFAULT_PANELS_GROUP: \"Без привязки к группе\" //Заголовок группы панелей по умолчанию\n};\n\n//Текст\nconst TEXTS = {\n LOADING: \"Ожидайте...\",\n //Ожидание завершения процесса\n NO_DATA_FOUND: \"Данных не найдено\" //Отсутствие данных\n};\n\n//Текст кнопок\nconst BUTTONS = {\n NAVIGATE_HOME: \"Домой\",\n //Переход к домашней странице\n NAVIGATE_BACK: \"Назад\",\n //Возврат назад по навигации\n NAVIGATE: \"Перейти\",\n //Переход к разделу/панели/адресу\n OK: \"ОК\",\n //Ок\n CANCEL: \"Отмена\",\n //Отмена\n CLOSE: \"Закрыть\",\n //Сокрытие\n CLEAR: \"Очистить\",\n //Очистка\n ORDER_ASC: \"По возрастанию\",\n //Сортировка по возрастанию\n ORDER_DESC: \"По убыванию\",\n //Сортировка по убыванию\n FILTER: \"Фильтр\",\n //Фильтрация\n MORE: \"Ещё\" //Догрузка данных\n};\n\n//Текст элементов ввода\nconst INPUTS = {\n VALUE: \"Значение\",\n VALUE_FROM: \"С\",\n VALUE_TO: \"По\"\n};\n\n//Типовые сообщения об ошибках\nconst ERROR = {\n UNDER_CONSTRUCTION: \"Панель в разработке\",\n P8O_API_UNAVAILABLE: '\"ПАРУС 8 Онлайн\" недоступен',\n DEFAULT: \"Неожиданная ошибка\"\n};\n\n//Типовые сообщения для ошибок HTTP\nconst ERROR_HTTP = {\n 404: \"Адрес не найден\"\n};\n\n//# sourceURL=webpack://parus_8_panels_plugin/./app.text.js?"); + +/***/ }), + +/***/ "./app/app.js": +/*!********************!*\ + !*** ./app/app.js ***! + \********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ App: () => (/* binding */ App)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! prop-types */ \"./node_modules/prop-types/index.js\");\n/* harmony import */ var prop_types__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(prop_types__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! react-router-dom */ \"./node_modules/react-router/dist/index.js\");\n/* harmony import */ var react_router_dom__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! react-router-dom */ \"./node_modules/react-router-dom/dist/index.js\");\nObject(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/application'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }());\nObject(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/navigation'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }());\nObject(function webpackMissingModule() { var e = new Error(\"Cannot find module './components/p8p_app_error_page'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }());\nObject(function webpackMissingModule() { var e = new Error(\"Cannot find module './components/p8p_app_workspace'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }());\nObject(function webpackMissingModule() { var e = new Error(\"Cannot find module './components/p8p_panels_menu'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }());\n/* harmony import */ var _app_text__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../app.text */ \"./app.text.js\");\n/*\r\n Парус 8 - Панели мониторинга\r\n Приложение\r\n*/\n\n//---------------------\n//Подключение библиотек\n//---------------------\n\n //Классы React\n //Контроль свойств компонента\n //Роутер\n //Контекст приложения\n //Контекст навигации\n //Страница с ошибкой\n //Рабочее пространство панели\n //Меню панелей\n //Текстовые ресурсы и константы\n\n//--------------------------\n//Вспомогательные компоненты\n//--------------------------\n\n//Обработка ошибок роутинга\nconst RouterError = ({\n homePath\n}) => {\n //Подключение к контексту навигации\n const {\n navigateTo\n } = (0,react__WEBPACK_IMPORTED_MODULE_0__.useContext)(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/navigation'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()));\n\n //Извлечем ошибку роутинга\n const routeError = (0,react_router_dom__WEBPACK_IMPORTED_MODULE_3__.useRouteError)();\n\n //Отработка нажатия на кнопку навигации\n const handleNavigate = () => navigateTo({\n path: `${homePath.startsWith(\"/\") ? \"\" : \"/\"}${homePath}`\n });\n\n //Генерация содержимого\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './components/p8p_app_error_page'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()), {\n errorMessage: _app_text__WEBPACK_IMPORTED_MODULE_2__.ERROR_HTTP[routeError.status] ? _app_text__WEBPACK_IMPORTED_MODULE_2__.ERROR_HTTP[routeError.status] : _app_text__WEBPACK_IMPORTED_MODULE_2__.ERROR.DEFAULT,\n onNavigate: handleNavigate,\n navigateCaption: _app_text__WEBPACK_IMPORTED_MODULE_2__.BUTTONS.NAVIGATE_HOME\n });\n};\n\n//Контроль свойств - обработка ошибок роутинга\nRouterError.propTypes = {\n homePath: (prop_types__WEBPACK_IMPORTED_MODULE_4___default().string).isRequired\n};\n\n//Главное меню приложения\nconst MainMenu = ({\n panels = []\n} = {}) => {\n //Подключение к контексту навигации\n const {\n navigatePanel\n } = (0,react__WEBPACK_IMPORTED_MODULE_0__.useContext)(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/navigation'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()));\n\n //Отработка действия навигации элемента меню\n const handleItemNavigate = panel => navigatePanel(panel);\n\n //Генерация содержимого\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './components/p8p_panels_menu'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()), {\n panels: panels,\n title: _app_text__WEBPACK_IMPORTED_MODULE_2__.TITLES.MAIN_MENU,\n onItemNavigate: handleItemNavigate,\n navigateCaption: _app_text__WEBPACK_IMPORTED_MODULE_2__.BUTTONS.NAVIGATE,\n defaultGroupTytle: _app_text__WEBPACK_IMPORTED_MODULE_2__.TITLES.DEFAULT_PANELS_GROUP\n });\n};\n\n//Контроль свойств - главное меню приложения\nMainMenu.propTypes = {\n panels: prop_types__WEBPACK_IMPORTED_MODULE_4___default().arrayOf(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './components/p8p_panels_menu'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }())).isRequired\n};\n\n//Рабочее пространство панели\nconst Workspace = ({\n panels = [],\n selectedPanel,\n children\n} = {}) => {\n //Подключение к контексту навигации\n const {\n navigateRoot,\n navigatePanel\n } = (0,react__WEBPACK_IMPORTED_MODULE_0__.useContext)(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/navigation'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()));\n\n //Отработка действия навигации домой\n const handleHomeNavigate = () => navigateRoot();\n\n //Отработка действия навигации элемента меню\n const handleItemNavigate = panel => navigatePanel(panel);\n\n //Генерация содержимого\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './components/p8p_app_workspace'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()), {\n panels: panels,\n selectedPanel: selectedPanel,\n closeCaption: _app_text__WEBPACK_IMPORTED_MODULE_2__.BUTTONS.CLOSE,\n homeCaption: _app_text__WEBPACK_IMPORTED_MODULE_2__.BUTTONS.NAVIGATE_HOME,\n onHomeNavigate: handleHomeNavigate,\n onItemNavigate: handleItemNavigate\n }, children);\n};\n\n//Контроль свойств - главное меню приложения\nWorkspace.propTypes = {\n panels: prop_types__WEBPACK_IMPORTED_MODULE_4___default().arrayOf(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './components/p8p_panels_menu'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }())).isRequired,\n selectedPanel: Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './components/p8p_panels_menu'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()),\n children: (prop_types__WEBPACK_IMPORTED_MODULE_4___default().element)\n};\n\n//Обёртывание элемента в контекст навигации\nconst wrapNavigationContext = children => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/navigation'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()), null, children);\n\n//-----------\n//Тело модуля\n//-----------\n\n//Приложение\nconst App = () => {\n //Собственное состояние\n const [routes, setRoutes] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)([]);\n\n //Подключение к контексту приложения\n const {\n appState\n } = (0,react__WEBPACK_IMPORTED_MODULE_0__.useContext)(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/application'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()));\n\n //Инициализация роутера\n const content = routes.length > 0 ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react_router_dom__WEBPACK_IMPORTED_MODULE_3__.RouterProvider, {\n router: (0,react_router_dom__WEBPACK_IMPORTED_MODULE_5__.createHashRouter)(routes)\n }) : null;\n\n //При изменении состояния загрузки панелей\n (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {\n if (appState.panelsLoaded) {\n //Сборка \"веток\" для панелей\n let routes = [{\n path: Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/navigation'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }())(),\n element: wrapNavigationContext( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(MainMenu, {\n panels: appState.panels\n })),\n errorElement: wrapNavigationContext( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(RouterError, {\n homePath: Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/navigation'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }())()\n }))\n }];\n for (const panel of appState.panels) {\n // eslint-disable-next-line no-undef\n const p = __webpack_require__(\"./app/panels sync recursive ^\\\\.\\\\/.*$\")(`./${panel.path}`);\n routes.push({\n path: `${panel.url}/*`,\n element: wrapNavigationContext( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(Workspace, {\n panels: appState.panels,\n selectedPanel: panel\n }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(p.RootClass, null))),\n errorElement: wrapNavigationContext( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(RouterError, {\n homePath: panel.url\n }))\n });\n }\n setRoutes(routes);\n }\n }, [appState.panels, appState.panelsLoaded]);\n\n //Генерация содержимого\n return content;\n};\n\n//----------------\n//Интерфейс модуля\n//----------------\n\n\n\n//# sourceURL=webpack://parus_8_panels_plugin/./app/app.js?"); + +/***/ }), + +/***/ "./app/index.js": +/*!**********************!*\ + !*** ./app/index.js ***! + \**********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var react_dom_client__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom/client */ \"./node_modules/react-dom/client.js\");\n/* harmony import */ var _root__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./root */ \"./app/root.js\");\n/*\r\n Парус 8 - Панели мониторинга\r\n Точка входа в приложение\r\n*/\n\n//---------------------\n//Подключение библиотек\n//---------------------\n\n //React\n //Работа с DOM в React\n //Корневой компонент приложения\n\n//-----------\n//Точка входа\n//-----------\n\nconst root = (0,react_dom_client__WEBPACK_IMPORTED_MODULE_1__.createRoot)(document.getElementById(\"app-content\"));\nroot.render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_root__WEBPACK_IMPORTED_MODULE_2__[\"default\"], null));\n\n//# sourceURL=webpack://parus_8_panels_plugin/./app/index.js?"); + +/***/ }), + +/***/ "./app/root.js": +/*!*********************!*\ + !*** ./app/root.js ***! + \*********************/ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +"use strict"; +eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\nObject(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/messaging'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }());\nObject(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/backend'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }());\nObject(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/application'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }());\n/* harmony import */ var _app__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./app */ \"./app/app.js\");\n/*\r\n Парус 8 - Панели мониторинга\r\n Корневой класс приложения (обёртка для контекста)\r\n*/\n\n//---------------------\n//Подключение библиотек\n//---------------------\n\n //Классы React\n //Контекст сообщений\n //Контекст взаимодействия с сервером\n //Контекст приложения\n //Приложение\n\n//-----------\n//Тело модуля\n//-----------\n\n//Обёртка для контекста\nconst Root = () => {\n return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/messaging'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/backend'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(Object(function webpackMissingModule() { var e = new Error(\"Cannot find module './context/application'\"); e.code = 'MODULE_NOT_FOUND'; throw e; }()), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_app__WEBPACK_IMPORTED_MODULE_2__.App, null))));\n};\n\n//----------------\n//Интерфейс модуля\n//----------------\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (Root);\n\n//# sourceURL=webpack://parus_8_panels_plugin/./app/root.js?"); + +/***/ }), + +/***/ "./node_modules/object-assign/index.js": +/*!*********************************************!*\ + !*** ./node_modules/object-assign/index.js ***! + \*********************************************/ +/***/ ((module) => { + +"use strict"; +eval("/*\nobject-assign\n(c) Sindre Sorhus\n@license MIT\n*/\n\n\n/* eslint-disable no-unused-vars */\nvar getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\nvar propIsEnumerable = Object.prototype.propertyIsEnumerable;\n\nfunction toObject(val) {\n\tif (val === null || val === undefined) {\n\t\tthrow new TypeError('Object.assign cannot be called with null or undefined');\n\t}\n\n\treturn Object(val);\n}\n\nfunction shouldUseNative() {\n\ttry {\n\t\tif (!Object.assign) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Detect buggy property enumeration order in older V8 versions.\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=4118\n\t\tvar test1 = new String('abc'); // eslint-disable-line no-new-wrappers\n\t\ttest1[5] = 'de';\n\t\tif (Object.getOwnPropertyNames(test1)[0] === '5') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=3056\n\t\tvar test2 = {};\n\t\tfor (var i = 0; i < 10; i++) {\n\t\t\ttest2['_' + String.fromCharCode(i)] = i;\n\t\t}\n\t\tvar order2 = Object.getOwnPropertyNames(test2).map(function (n) {\n\t\t\treturn test2[n];\n\t\t});\n\t\tif (order2.join('') !== '0123456789') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// https://bugs.chromium.org/p/v8/issues/detail?id=3056\n\t\tvar test3 = {};\n\t\t'abcdefghijklmnopqrst'.split('').forEach(function (letter) {\n\t\t\ttest3[letter] = letter;\n\t\t});\n\t\tif (Object.keys(Object.assign({}, test3)).join('') !==\n\t\t\t\t'abcdefghijklmnopqrst') {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t} catch (err) {\n\t\t// We don't expect any of the above to throw, but better to be safe.\n\t\treturn false;\n\t}\n}\n\nmodule.exports = shouldUseNative() ? Object.assign : function (target, source) {\n\tvar from;\n\tvar to = toObject(target);\n\tvar symbols;\n\n\tfor (var s = 1; s < arguments.length; s++) {\n\t\tfrom = Object(arguments[s]);\n\n\t\tfor (var key in from) {\n\t\t\tif (hasOwnProperty.call(from, key)) {\n\t\t\t\tto[key] = from[key];\n\t\t\t}\n\t\t}\n\n\t\tif (getOwnPropertySymbols) {\n\t\t\tsymbols = getOwnPropertySymbols(from);\n\t\t\tfor (var i = 0; i < symbols.length; i++) {\n\t\t\t\tif (propIsEnumerable.call(from, symbols[i])) {\n\t\t\t\t\tto[symbols[i]] = from[symbols[i]];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn to;\n};\n\n\n//# sourceURL=webpack://parus_8_panels_plugin/./node_modules/object-assign/index.js?"); + +/***/ }), + +/***/ "./node_modules/prop-types/checkPropTypes.js": +/*!***************************************************!*\ + !*** ./node_modules/prop-types/checkPropTypes.js ***! + \***************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +"use strict"; +eval("/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nvar printWarning = function() {};\n\nif (true) {\n var ReactPropTypesSecret = __webpack_require__(/*! ./lib/ReactPropTypesSecret */ \"./node_modules/prop-types/lib/ReactPropTypesSecret.js\");\n var loggedTypeFailures = {};\n var has = __webpack_require__(/*! ./lib/has */ \"./node_modules/prop-types/lib/has.js\");\n\n printWarning = function(text) {\n var message = 'Warning: ' + text;\n if (typeof console !== 'undefined') {\n console.error(message);\n }\n try {\n // --- Welcome to debugging React ---\n // This error was thrown as a convenience so that you can use this stack\n // to find the callsite that caused this warning to fire.\n throw new Error(message);\n } catch (x) { /**/ }\n };\n}\n\n/**\n * Assert that the values match with the type specs.\n * Error messages are memorized and will only be shown once.\n *\n * @param {object} typeSpecs Map of name to a ReactPropType\n * @param {object} values Runtime values that need to be type-checked\n * @param {string} location e.g. \"prop\", \"context\", \"child context\"\n * @param {string} componentName Name of the component for error messages.\n * @param {?Function} getStack Returns the component stack.\n * @private\n */\nfunction checkPropTypes(typeSpecs, values, location, componentName, getStack) {\n if (true) {\n for (var typeSpecName in typeSpecs) {\n if (has(typeSpecs, typeSpecName)) {\n var error;\n // Prop type validation may throw. In case they do, we don't want to\n // fail the render phase where it didn't fail before. So we log it.\n // After these have been cleaned up, we'll let them throw.\n try {\n // This is intentionally an invariant that gets caught. It's the same\n // behavior as without this statement except with a better message.\n if (typeof typeSpecs[typeSpecName] !== 'function') {\n var err = Error(\n (componentName || 'React class') + ': ' + location + ' type `' + typeSpecName + '` is invalid; ' +\n 'it must be a function, usually from the `prop-types` package, but received `' + typeof typeSpecs[typeSpecName] + '`.' +\n 'This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.'\n );\n err.name = 'Invariant Violation';\n throw err;\n }\n error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret);\n } catch (ex) {\n error = ex;\n }\n if (error && !(error instanceof Error)) {\n printWarning(\n (componentName || 'React class') + ': type specification of ' +\n location + ' `' + typeSpecName + '` is invalid; the type checker ' +\n 'function must return `null` or an `Error` but returned a ' + typeof error + '. ' +\n 'You may have forgotten to pass an argument to the type checker ' +\n 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' +\n 'shape all require an argument).'\n );\n }\n if (error instanceof Error && !(error.message in loggedTypeFailures)) {\n // Only monitor this failure once because there tends to be a lot of the\n // same error.\n loggedTypeFailures[error.message] = true;\n\n var stack = getStack ? getStack() : '';\n\n printWarning(\n 'Failed ' + location + ' type: ' + error.message + (stack != null ? stack : '')\n );\n }\n }\n }\n }\n}\n\n/**\n * Resets warning cache when testing.\n *\n * @private\n */\ncheckPropTypes.resetWarningCache = function() {\n if (true) {\n loggedTypeFailures = {};\n }\n}\n\nmodule.exports = checkPropTypes;\n\n\n//# sourceURL=webpack://parus_8_panels_plugin/./node_modules/prop-types/checkPropTypes.js?"); + +/***/ }), + +/***/ "./node_modules/prop-types/factoryWithTypeCheckers.js": +/*!************************************************************!*\ + !*** ./node_modules/prop-types/factoryWithTypeCheckers.js ***! + \************************************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +"use strict"; +eval("/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nvar ReactIs = __webpack_require__(/*! react-is */ \"./node_modules/react-is/index.js\");\nvar assign = __webpack_require__(/*! object-assign */ \"./node_modules/object-assign/index.js\");\n\nvar ReactPropTypesSecret = __webpack_require__(/*! ./lib/ReactPropTypesSecret */ \"./node_modules/prop-types/lib/ReactPropTypesSecret.js\");\nvar has = __webpack_require__(/*! ./lib/has */ \"./node_modules/prop-types/lib/has.js\");\nvar checkPropTypes = __webpack_require__(/*! ./checkPropTypes */ \"./node_modules/prop-types/checkPropTypes.js\");\n\nvar printWarning = function() {};\n\nif (true) {\n printWarning = function(text) {\n var message = 'Warning: ' + text;\n if (typeof console !== 'undefined') {\n console.error(message);\n }\n try {\n // --- Welcome to debugging React ---\n // This error was thrown as a convenience so that you can use this stack\n // to find the callsite that caused this warning to fire.\n throw new Error(message);\n } catch (x) {}\n };\n}\n\nfunction emptyFunctionThatReturnsNull() {\n return null;\n}\n\nmodule.exports = function(isValidElement, throwOnDirectAccess) {\n /* global Symbol */\n var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;\n var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec.\n\n /**\n * Returns the iterator method function contained on the iterable object.\n *\n * Be sure to invoke the function with the iterable as context:\n *\n * var iteratorFn = getIteratorFn(myIterable);\n * if (iteratorFn) {\n * var iterator = iteratorFn.call(myIterable);\n * ...\n * }\n *\n * @param {?object} maybeIterable\n * @return {?function}\n */\n function getIteratorFn(maybeIterable) {\n var iteratorFn = maybeIterable && (ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]);\n if (typeof iteratorFn === 'function') {\n return iteratorFn;\n }\n }\n\n /**\n * Collection of methods that allow declaration and validation of props that are\n * supplied to React components. Example usage:\n *\n * var Props = require('ReactPropTypes');\n * var MyArticle = React.createClass({\n * propTypes: {\n * // An optional string prop named \"description\".\n * description: Props.string,\n *\n * // A required enum prop named \"category\".\n * category: Props.oneOf(['News','Photos']).isRequired,\n *\n * // A prop named \"dialog\" that requires an instance of Dialog.\n * dialog: Props.instanceOf(Dialog).isRequired\n * },\n * render: function() { ... }\n * });\n *\n * A more formal specification of how these methods are used:\n *\n * type := array|bool|func|object|number|string|oneOf([...])|instanceOf(...)\n * decl := ReactPropTypes.{type}(.isRequired)?\n *\n * Each and every declaration produces a function with the same signature. This\n * allows the creation of custom validation functions. For example:\n *\n * var MyLink = React.createClass({\n * propTypes: {\n * // An optional string or URI prop named \"href\".\n * href: function(props, propName, componentName) {\n * var propValue = props[propName];\n * if (propValue != null && typeof propValue !== 'string' &&\n * !(propValue instanceof URI)) {\n * return new Error(\n * 'Expected a string or an URI for ' + propName + ' in ' +\n * componentName\n * );\n * }\n * }\n * },\n * render: function() {...}\n * });\n *\n * @internal\n */\n\n var ANONYMOUS = '<>';\n\n // Important!\n // Keep this list in sync with production version in `./factoryWithThrowingShims.js`.\n var ReactPropTypes = {\n array: createPrimitiveTypeChecker('array'),\n bigint: createPrimitiveTypeChecker('bigint'),\n bool: createPrimitiveTypeChecker('boolean'),\n func: createPrimitiveTypeChecker('function'),\n number: createPrimitiveTypeChecker('number'),\n object: createPrimitiveTypeChecker('object'),\n string: createPrimitiveTypeChecker('string'),\n symbol: createPrimitiveTypeChecker('symbol'),\n\n any: createAnyTypeChecker(),\n arrayOf: createArrayOfTypeChecker,\n element: createElementTypeChecker(),\n elementType: createElementTypeTypeChecker(),\n instanceOf: createInstanceTypeChecker,\n node: createNodeChecker(),\n objectOf: createObjectOfTypeChecker,\n oneOf: createEnumTypeChecker,\n oneOfType: createUnionTypeChecker,\n shape: createShapeTypeChecker,\n exact: createStrictShapeTypeChecker,\n };\n\n /**\n * inlined Object.is polyfill to avoid requiring consumers ship their own\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is\n */\n /*eslint-disable no-self-compare*/\n function is(x, y) {\n // SameValue algorithm\n if (x === y) {\n // Steps 1-5, 7-10\n // Steps 6.b-6.e: +0 != -0\n return x !== 0 || 1 / x === 1 / y;\n } else {\n // Step 6.a: NaN == NaN\n return x !== x && y !== y;\n }\n }\n /*eslint-enable no-self-compare*/\n\n /**\n * We use an Error-like object for backward compatibility as people may call\n * PropTypes directly and inspect their output. However, we don't use real\n * Errors anymore. We don't inspect their stack anyway, and creating them\n * is prohibitively expensive if they are created too often, such as what\n * happens in oneOfType() for any type before the one that matched.\n */\n function PropTypeError(message, data) {\n this.message = message;\n this.data = data && typeof data === 'object' ? data: {};\n this.stack = '';\n }\n // Make `instanceof Error` still work for returned errors.\n PropTypeError.prototype = Error.prototype;\n\n function createChainableTypeChecker(validate) {\n if (true) {\n var manualPropTypeCallCache = {};\n var manualPropTypeWarningCount = 0;\n }\n function checkType(isRequired, props, propName, componentName, location, propFullName, secret) {\n componentName = componentName || ANONYMOUS;\n propFullName = propFullName || propName;\n\n if (secret !== ReactPropTypesSecret) {\n if (throwOnDirectAccess) {\n // New behavior only for users of `prop-types` package\n var err = new Error(\n 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' +\n 'Use `PropTypes.checkPropTypes()` to call them. ' +\n 'Read more at http://fb.me/use-check-prop-types'\n );\n err.name = 'Invariant Violation';\n throw err;\n } else if ( true && typeof console !== 'undefined') {\n // Old behavior for people using React.PropTypes\n var cacheKey = componentName + ':' + propName;\n if (\n !manualPropTypeCallCache[cacheKey] &&\n // Avoid spamming the console because they are often not actionable except for lib authors\n manualPropTypeWarningCount < 3\n ) {\n printWarning(\n 'You are manually calling a React.PropTypes validation ' +\n 'function for the `' + propFullName + '` prop on `' + componentName + '`. This is deprecated ' +\n 'and will throw in the standalone `prop-types` package. ' +\n 'You may be seeing this warning due to a third-party PropTypes ' +\n 'library. See https://fb.me/react-warning-dont-call-proptypes ' + 'for details.'\n );\n manualPropTypeCallCache[cacheKey] = true;\n manualPropTypeWarningCount++;\n }\n }\n }\n if (props[propName] == null) {\n if (isRequired) {\n if (props[propName] === null) {\n return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.'));\n }\n return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.'));\n }\n return null;\n } else {\n return validate(props, propName, componentName, location, propFullName);\n }\n }\n\n var chainedCheckType = checkType.bind(null, false);\n chainedCheckType.isRequired = checkType.bind(null, true);\n\n return chainedCheckType;\n }\n\n function createPrimitiveTypeChecker(expectedType) {\n function validate(props, propName, componentName, location, propFullName, secret) {\n var propValue = props[propName];\n var propType = getPropType(propValue);\n if (propType !== expectedType) {\n // `propValue` being instance of, say, date/regexp, pass the 'object'\n // check, but we can offer a more precise error message here rather than\n // 'of type `object`'.\n var preciseType = getPreciseType(propValue);\n\n return new PropTypeError(\n 'Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '`, expected ') + ('`' + expectedType + '`.'),\n {expectedType: expectedType}\n );\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createAnyTypeChecker() {\n return createChainableTypeChecker(emptyFunctionThatReturnsNull);\n }\n\n function createArrayOfTypeChecker(typeChecker) {\n function validate(props, propName, componentName, location, propFullName) {\n if (typeof typeChecker !== 'function') {\n return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside arrayOf.');\n }\n var propValue = props[propName];\n if (!Array.isArray(propValue)) {\n var propType = getPropType(propValue);\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an array.'));\n }\n for (var i = 0; i < propValue.length; i++) {\n var error = typeChecker(propValue, i, componentName, location, propFullName + '[' + i + ']', ReactPropTypesSecret);\n if (error instanceof Error) {\n return error;\n }\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createElementTypeChecker() {\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n if (!isValidElement(propValue)) {\n var propType = getPropType(propValue);\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createElementTypeTypeChecker() {\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n if (!ReactIs.isValidElementType(propValue)) {\n var propType = getPropType(propValue);\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement type.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createInstanceTypeChecker(expectedClass) {\n function validate(props, propName, componentName, location, propFullName) {\n if (!(props[propName] instanceof expectedClass)) {\n var expectedClassName = expectedClass.name || ANONYMOUS;\n var actualClassName = getClassName(props[propName]);\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName + '`, expected ') + ('instance of `' + expectedClassName + '`.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createEnumTypeChecker(expectedValues) {\n if (!Array.isArray(expectedValues)) {\n if (true) {\n if (arguments.length > 1) {\n printWarning(\n 'Invalid arguments supplied to oneOf, expected an array, got ' + arguments.length + ' arguments. ' +\n 'A common mistake is to write oneOf(x, y, z) instead of oneOf([x, y, z]).'\n );\n } else {\n printWarning('Invalid argument supplied to oneOf, expected an array.');\n }\n }\n return emptyFunctionThatReturnsNull;\n }\n\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n for (var i = 0; i < expectedValues.length; i++) {\n if (is(propValue, expectedValues[i])) {\n return null;\n }\n }\n\n var valuesString = JSON.stringify(expectedValues, function replacer(key, value) {\n var type = getPreciseType(value);\n if (type === 'symbol') {\n return String(value);\n }\n return value;\n });\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of value `' + String(propValue) + '` ' + ('supplied to `' + componentName + '`, expected one of ' + valuesString + '.'));\n }\n return createChainableTypeChecker(validate);\n }\n\n function createObjectOfTypeChecker(typeChecker) {\n function validate(props, propName, componentName, location, propFullName) {\n if (typeof typeChecker !== 'function') {\n return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside objectOf.');\n }\n var propValue = props[propName];\n var propType = getPropType(propValue);\n if (propType !== 'object') {\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an object.'));\n }\n for (var key in propValue) {\n if (has(propValue, key)) {\n var error = typeChecker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);\n if (error instanceof Error) {\n return error;\n }\n }\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createUnionTypeChecker(arrayOfTypeCheckers) {\n if (!Array.isArray(arrayOfTypeCheckers)) {\n true ? printWarning('Invalid argument supplied to oneOfType, expected an instance of array.') : 0;\n return emptyFunctionThatReturnsNull;\n }\n\n for (var i = 0; i < arrayOfTypeCheckers.length; i++) {\n var checker = arrayOfTypeCheckers[i];\n if (typeof checker !== 'function') {\n printWarning(\n 'Invalid argument supplied to oneOfType. Expected an array of check functions, but ' +\n 'received ' + getPostfixForTypeWarning(checker) + ' at index ' + i + '.'\n );\n return emptyFunctionThatReturnsNull;\n }\n }\n\n function validate(props, propName, componentName, location, propFullName) {\n var expectedTypes = [];\n for (var i = 0; i < arrayOfTypeCheckers.length; i++) {\n var checker = arrayOfTypeCheckers[i];\n var checkerResult = checker(props, propName, componentName, location, propFullName, ReactPropTypesSecret);\n if (checkerResult == null) {\n return null;\n }\n if (checkerResult.data && has(checkerResult.data, 'expectedType')) {\n expectedTypes.push(checkerResult.data.expectedType);\n }\n }\n var expectedTypesMessage = (expectedTypes.length > 0) ? ', expected one of type [' + expectedTypes.join(', ') + ']': '';\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`' + expectedTypesMessage + '.'));\n }\n return createChainableTypeChecker(validate);\n }\n\n function createNodeChecker() {\n function validate(props, propName, componentName, location, propFullName) {\n if (!isNode(props[propName])) {\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.'));\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function invalidValidatorError(componentName, location, propFullName, key, type) {\n return new PropTypeError(\n (componentName || 'React class') + ': ' + location + ' type `' + propFullName + '.' + key + '` is invalid; ' +\n 'it must be a function, usually from the `prop-types` package, but received `' + type + '`.'\n );\n }\n\n function createShapeTypeChecker(shapeTypes) {\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n var propType = getPropType(propValue);\n if (propType !== 'object') {\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));\n }\n for (var key in shapeTypes) {\n var checker = shapeTypes[key];\n if (typeof checker !== 'function') {\n return invalidValidatorError(componentName, location, propFullName, key, getPreciseType(checker));\n }\n var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);\n if (error) {\n return error;\n }\n }\n return null;\n }\n return createChainableTypeChecker(validate);\n }\n\n function createStrictShapeTypeChecker(shapeTypes) {\n function validate(props, propName, componentName, location, propFullName) {\n var propValue = props[propName];\n var propType = getPropType(propValue);\n if (propType !== 'object') {\n return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));\n }\n // We need to check all keys in case some are required but missing from props.\n var allKeys = assign({}, props[propName], shapeTypes);\n for (var key in allKeys) {\n var checker = shapeTypes[key];\n if (has(shapeTypes, key) && typeof checker !== 'function') {\n return invalidValidatorError(componentName, location, propFullName, key, getPreciseType(checker));\n }\n if (!checker) {\n return new PropTypeError(\n 'Invalid ' + location + ' `' + propFullName + '` key `' + key + '` supplied to `' + componentName + '`.' +\n '\\nBad object: ' + JSON.stringify(props[propName], null, ' ') +\n '\\nValid keys: ' + JSON.stringify(Object.keys(shapeTypes), null, ' ')\n );\n }\n var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);\n if (error) {\n return error;\n }\n }\n return null;\n }\n\n return createChainableTypeChecker(validate);\n }\n\n function isNode(propValue) {\n switch (typeof propValue) {\n case 'number':\n case 'string':\n case 'undefined':\n return true;\n case 'boolean':\n return !propValue;\n case 'object':\n if (Array.isArray(propValue)) {\n return propValue.every(isNode);\n }\n if (propValue === null || isValidElement(propValue)) {\n return true;\n }\n\n var iteratorFn = getIteratorFn(propValue);\n if (iteratorFn) {\n var iterator = iteratorFn.call(propValue);\n var step;\n if (iteratorFn !== propValue.entries) {\n while (!(step = iterator.next()).done) {\n if (!isNode(step.value)) {\n return false;\n }\n }\n } else {\n // Iterator will provide entry [k,v] tuples rather than values.\n while (!(step = iterator.next()).done) {\n var entry = step.value;\n if (entry) {\n if (!isNode(entry[1])) {\n return false;\n }\n }\n }\n }\n } else {\n return false;\n }\n\n return true;\n default:\n return false;\n }\n }\n\n function isSymbol(propType, propValue) {\n // Native Symbol.\n if (propType === 'symbol') {\n return true;\n }\n\n // falsy value can't be a Symbol\n if (!propValue) {\n return false;\n }\n\n // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol'\n if (propValue['@@toStringTag'] === 'Symbol') {\n return true;\n }\n\n // Fallback for non-spec compliant Symbols which are polyfilled.\n if (typeof Symbol === 'function' && propValue instanceof Symbol) {\n return true;\n }\n\n return false;\n }\n\n // Equivalent of `typeof` but with special handling for array and regexp.\n function getPropType(propValue) {\n var propType = typeof propValue;\n if (Array.isArray(propValue)) {\n return 'array';\n }\n if (propValue instanceof RegExp) {\n // Old webkits (at least until Android 4.0) return 'function' rather than\n // 'object' for typeof a RegExp. We'll normalize this here so that /bla/\n // passes PropTypes.object.\n return 'object';\n }\n if (isSymbol(propType, propValue)) {\n return 'symbol';\n }\n return propType;\n }\n\n // This handles more types than `getPropType`. Only used for error messages.\n // See `createPrimitiveTypeChecker`.\n function getPreciseType(propValue) {\n if (typeof propValue === 'undefined' || propValue === null) {\n return '' + propValue;\n }\n var propType = getPropType(propValue);\n if (propType === 'object') {\n if (propValue instanceof Date) {\n return 'date';\n } else if (propValue instanceof RegExp) {\n return 'regexp';\n }\n }\n return propType;\n }\n\n // Returns a string that is postfixed to a warning about an invalid type.\n // For example, \"undefined\" or \"of type array\"\n function getPostfixForTypeWarning(value) {\n var type = getPreciseType(value);\n switch (type) {\n case 'array':\n case 'object':\n return 'an ' + type;\n case 'boolean':\n case 'date':\n case 'regexp':\n return 'a ' + type;\n default:\n return type;\n }\n }\n\n // Returns class name of the object, if any.\n function getClassName(propValue) {\n if (!propValue.constructor || !propValue.constructor.name) {\n return ANONYMOUS;\n }\n return propValue.constructor.name;\n }\n\n ReactPropTypes.checkPropTypes = checkPropTypes;\n ReactPropTypes.resetWarningCache = checkPropTypes.resetWarningCache;\n ReactPropTypes.PropTypes = ReactPropTypes;\n\n return ReactPropTypes;\n};\n\n\n//# sourceURL=webpack://parus_8_panels_plugin/./node_modules/prop-types/factoryWithTypeCheckers.js?"); + +/***/ }), + +/***/ "./node_modules/prop-types/index.js": +/*!******************************************!*\ + !*** ./node_modules/prop-types/index.js ***! + \******************************************/ +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + +eval("/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nif (true) {\n var ReactIs = __webpack_require__(/*! react-is */ \"./node_modules/react-is/index.js\");\n\n // By explicitly using `prop-types` you are opting into new development behavior.\n // http://fb.me/prop-types-in-prod\n var throwOnDirectAccess = true;\n module.exports = __webpack_require__(/*! ./factoryWithTypeCheckers */ \"./node_modules/prop-types/factoryWithTypeCheckers.js\")(ReactIs.isElement, throwOnDirectAccess);\n} else {}\n\n\n//# sourceURL=webpack://parus_8_panels_plugin/./node_modules/prop-types/index.js?"); + +/***/ }), + +/***/ "./node_modules/prop-types/lib/ReactPropTypesSecret.js": +/*!*************************************************************!*\ + !*** ./node_modules/prop-types/lib/ReactPropTypesSecret.js ***! + \*************************************************************/ +/***/ ((module) => { + +"use strict"; +eval("/**\n * Copyright (c) 2013-present, Facebook, Inc.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nvar ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED';\n\nmodule.exports = ReactPropTypesSecret;\n\n\n//# sourceURL=webpack://parus_8_panels_plugin/./node_modules/prop-types/lib/ReactPropTypesSecret.js?"); + +/***/ }), + +/***/ "./node_modules/prop-types/lib/has.js": +/*!********************************************!*\ + !*** ./node_modules/prop-types/lib/has.js ***! + \********************************************/ +/***/ ((module) => { + +eval("module.exports = Function.call.bind(Object.prototype.hasOwnProperty);\n\n\n//# sourceURL=webpack://parus_8_panels_plugin/./node_modules/prop-types/lib/has.js?"); + +/***/ }), + +/***/ "./node_modules/react-dom/cjs/react-dom.development.js": +/*!*************************************************************!*\ + !*** ./node_modules/react-dom/cjs/react-dom.development.js ***! + \*************************************************************/ +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +"use strict"; +eval("/**\n * @license React\n * react-dom.development.js\n *\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n\n\nif (true) {\n (function() {\n\n 'use strict';\n\n/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */\nif (\n typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' &&\n typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart ===\n 'function'\n) {\n __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());\n}\n var React = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\nvar Scheduler = __webpack_require__(/*! scheduler */ \"./node_modules/scheduler/index.js\");\n\nvar ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;\n\nvar suppressWarning = false;\nfunction setSuppressWarning(newSuppressWarning) {\n {\n suppressWarning = newSuppressWarning;\n }\n} // In DEV, calls to console.warn and console.error get replaced\n// by calls to these methods by a Babel plugin.\n//\n// In PROD (or in packages without access to React internals),\n// they are left as they are instead.\n\nfunction warn(format) {\n {\n if (!suppressWarning) {\n for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n args[_key - 1] = arguments[_key];\n }\n\n printWarning('warn', format, args);\n }\n }\n}\nfunction error(format) {\n {\n if (!suppressWarning) {\n for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {\n args[_key2 - 1] = arguments[_key2];\n }\n\n printWarning('error', format, args);\n }\n }\n}\n\nfunction printWarning(level, format, args) {\n // When changing this logic, you might want to also\n // update consoleWithStackDev.www.js as well.\n {\n var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;\n var stack = ReactDebugCurrentFrame.getStackAddendum();\n\n if (stack !== '') {\n format += '%s';\n args = args.concat([stack]);\n } // eslint-disable-next-line react-internal/safe-string-coercion\n\n\n var argsWithFormat = args.map(function (item) {\n return String(item);\n }); // Careful: RN currently depends on this prefix\n\n argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it\n // breaks IE9: https://github.com/facebook/react/issues/13610\n // eslint-disable-next-line react-internal/no-production-logging\n\n Function.prototype.apply.call(console[level], console, argsWithFormat);\n }\n}\n\nvar FunctionComponent = 0;\nvar ClassComponent = 1;\nvar IndeterminateComponent = 2; // Before we know whether it is function or class\n\nvar HostRoot = 3; // Root of a host tree. Could be nested inside another node.\n\nvar HostPortal = 4; // A subtree. Could be an entry point to a different renderer.\n\nvar HostComponent = 5;\nvar HostText = 6;\nvar Fragment = 7;\nvar Mode = 8;\nvar ContextConsumer = 9;\nvar ContextProvider = 10;\nvar ForwardRef = 11;\nvar Profiler = 12;\nvar SuspenseComponent = 13;\nvar MemoComponent = 14;\nvar SimpleMemoComponent = 15;\nvar LazyComponent = 16;\nvar IncompleteClassComponent = 17;\nvar DehydratedFragment = 18;\nvar SuspenseListComponent = 19;\nvar ScopeComponent = 21;\nvar OffscreenComponent = 22;\nvar LegacyHiddenComponent = 23;\nvar CacheComponent = 24;\nvar TracingMarkerComponent = 25;\n\n// -----------------------------------------------------------------------------\n\nvar enableClientRenderFallbackOnTextMismatch = true; // TODO: Need to review this code one more time before landing\n// the react-reconciler package.\n\nvar enableNewReconciler = false; // Support legacy Primer support on internal FB www\n\nvar enableLazyContextPropagation = false; // FB-only usage. The new API has different semantics.\n\nvar enableLegacyHidden = false; // Enables unstable_avoidThisFallback feature in Fiber\n\nvar enableSuspenseAvoidThisFallback = false; // Enables unstable_avoidThisFallback feature in Fizz\n// React DOM Chopping Block\n//\n// Similar to main Chopping Block but only flags related to React DOM. These are\n// grouped because we will likely batch all of them into a single major release.\n// -----------------------------------------------------------------------------\n// Disable support for comment nodes as React DOM containers. Already disabled\n// in open source, but www codebase still relies on it. Need to remove.\n\nvar disableCommentsAsDOMContainers = true; // Disable javascript: URL strings in href for XSS protection.\n// and client rendering, mostly to allow JSX attributes to apply to the custom\n// element's object properties instead of only HTML attributes.\n// https://github.com/facebook/react/issues/11347\n\nvar enableCustomElementPropertySupport = false; // Disables children for