Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b49e0e9a80 |
121
app/components/editors/p8p_actions.js
Normal file
121
app/components/editors/p8p_actions.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Компонент: Действия
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Icon, Button } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { P8PCAEditor } from "./p8p_component_action/editor"; //Редактор действия
|
||||||
|
import { P8PChipList } from "./p8p_chip_list"; //Дополнительные настройки редактора
|
||||||
|
import { P8P_CA_SHAPE, P8P_CAS_INITIAL, P8P_CA_TYPE } from "./p8p_component_action/common"; //Общие ресурсы действий
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
const getActionChipText = (type, params) => {
|
||||||
|
//Определяем от типа
|
||||||
|
switch (type) {
|
||||||
|
//Открыть раздел
|
||||||
|
case P8P_CA_TYPE.openUnit.code:
|
||||||
|
return params.unitName;
|
||||||
|
//Открыть панель
|
||||||
|
case P8P_CA_TYPE.openPanel.code:
|
||||||
|
return params.panelValue.value;
|
||||||
|
//Установить переменную
|
||||||
|
case P8P_CA_TYPE.setVariable.code:
|
||||||
|
return params.reduce((prev, cur) => [...prev, cur.variableSource], []).join(", ");
|
||||||
|
//Для всех остальных
|
||||||
|
default:
|
||||||
|
return "Не определен";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Действия
|
||||||
|
const P8PActions = ({ actions = P8P_CAS_INITIAL, valueProviders = {}, areas = [], valueTypes = [], onChange = null } = {}) => {
|
||||||
|
//Собственное состояние - редактор действий
|
||||||
|
const [actionEditor, setActionEditor] = useState({ display: false, index: null });
|
||||||
|
|
||||||
|
//При изменении действий
|
||||||
|
const handleActionsChange = actions => onChange && onChange(actions);
|
||||||
|
|
||||||
|
//При добавлении действия
|
||||||
|
const handleActionAdd = () => setActionEditor({ display: true, index: null });
|
||||||
|
|
||||||
|
//При нажатии на действие
|
||||||
|
const handleActionClick = i => setActionEditor({ display: true, index: i });
|
||||||
|
|
||||||
|
//При удалении действия
|
||||||
|
const handleActionDelete = i => {
|
||||||
|
const newActions = [...actions];
|
||||||
|
newActions.splice(i, 1);
|
||||||
|
handleActionsChange(newActions);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При отмене сохранения изменений действия
|
||||||
|
const handleActionCancel = () => setActionEditor({ display: false, index: null });
|
||||||
|
|
||||||
|
//При сохранении изменений действия
|
||||||
|
const handleActionSave = action => {
|
||||||
|
const newActions = [...actions];
|
||||||
|
actionEditor.index == null ? newActions.push({ ...action }) : (newActions[actionEditor.index] = { ...action });
|
||||||
|
handleActionsChange(newActions);
|
||||||
|
setActionEditor({ display: false, index: null });
|
||||||
|
};
|
||||||
|
|
||||||
|
//Определяем структуру действий для отображения
|
||||||
|
const actionChips = actions.map(item => {
|
||||||
|
//Собираем текст действия
|
||||||
|
let text = getActionChipText(item.type, item.params);
|
||||||
|
//Формируем структуру для отображения карточки действия
|
||||||
|
return { text: text, title: text, icon: P8P_CA_TYPE[item.type]?.icon, iconTitle: P8P_CA_TYPE[item.type]?.name };
|
||||||
|
});
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{actionEditor.display && (
|
||||||
|
<P8PCAEditor
|
||||||
|
areas={areas}
|
||||||
|
valueTypes={valueTypes}
|
||||||
|
action={actionEditor.index !== null ? { ...actions[actionEditor.index] } : null}
|
||||||
|
onCancel={handleActionCancel}
|
||||||
|
onOk={handleActionSave}
|
||||||
|
valueProviders={valueProviders}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<P8PChipList items={actionChips} onClick={handleActionClick} onDelete={handleActionDelete} />
|
||||||
|
<Button startIcon={<Icon>add</Icon>} onClick={handleActionAdd}>
|
||||||
|
Добавить действие
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - действия
|
||||||
|
P8PActions.propTypes = {
|
||||||
|
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||||
|
valueProviders: PropTypes.object,
|
||||||
|
areas: PropTypes.array,
|
||||||
|
valueTypes: PropTypes.array,
|
||||||
|
onChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8PActions };
|
||||||
88
app/components/editors/p8p_chip_list.js
Normal file
88
app/components/editors/p8p_chip_list.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Компонент: Список элементов
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Chip, Typography, Stack, Icon } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { STYLES as COMMON_STYLES } from "./p8p_editors_common"; //Общие ресурсы редакторов
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CHIP_ITEM: { ...COMMON_STYLES.CHIP(true, false) },
|
||||||
|
STACK_ITEM: { alignItems: "center" },
|
||||||
|
TYPOGRAPHY_INFO: { overflow: "hidden" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Список элементов
|
||||||
|
const P8PChipList = ({ items, labelRender, onClick, onDelete }) => {
|
||||||
|
//При нажатии на элемент
|
||||||
|
const handleClick = index => onClick && onClick(index);
|
||||||
|
|
||||||
|
//При удалении элемента
|
||||||
|
const handleDelete = index => onDelete && onDelete(index);
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{Array.isArray(items) &&
|
||||||
|
items.length > 0 &&
|
||||||
|
items.map((item, i) => (
|
||||||
|
<Chip
|
||||||
|
key={i}
|
||||||
|
label={
|
||||||
|
labelRender ? (
|
||||||
|
labelRender(item)
|
||||||
|
) : (
|
||||||
|
<Stack direction="row" spacing={1} sx={STYLES.STACK_ITEM}>
|
||||||
|
{item.icon ? (
|
||||||
|
<>
|
||||||
|
<Icon sx={{ color: "grey" }} title={item?.iconTitle}>
|
||||||
|
{item.icon}
|
||||||
|
</Icon>
|
||||||
|
<Typography variant="body2">-</Typography>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
<Typography variant="body2" title={item?.title} sx={STYLES.TYPOGRAPHY_INFO}>
|
||||||
|
{item.text}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
variant={"outlined"}
|
||||||
|
onClick={onClick && !item.disableClick ? () => handleClick(i) : null}
|
||||||
|
onDelete={onDelete && !item.disableDelete ? () => handleDelete(i) : null}
|
||||||
|
deleteIcon={item.deleteIcon ? <Icon>{item.deleteIcon}</Icon> : null}
|
||||||
|
sx={STYLES.CHIP_ITEM}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - список элементов
|
||||||
|
P8PChipList.propTypes = {
|
||||||
|
items: PropTypes.array,
|
||||||
|
labelRender: PropTypes.func,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
onDelete: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8PChipList };
|
||||||
148
app/components/editors/p8p_component_action/common.js
Normal file
148
app/components/editors/p8p_component_action/common.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Общие ресурсы компонента "Редактор действия"
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Значения типа по умолчанию
|
||||||
|
const P8P_CA_DEF_TYPE_VALUE = {
|
||||||
|
TEXT_VALUE: "Значение",
|
||||||
|
VARIABLE: "Переменная"
|
||||||
|
};
|
||||||
|
|
||||||
|
//Типы значений по умолчанию
|
||||||
|
const P8P_CA_DEF_VALUE_TYPES = [...Object.values(P8P_CA_DEF_TYPE_VALUE)];
|
||||||
|
|
||||||
|
//Типы действия
|
||||||
|
const P8P_CA_TYPE = {
|
||||||
|
openUnit: { code: "openUnit", name: "Открыть раздел", icon: "article" },
|
||||||
|
openPanel: { code: "openPanel", name: "Открыть панель", icon: "storage" },
|
||||||
|
setVariable: { code: "setVariable", name: "Установить переменную", icon: "mediation" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//Список полей связываемых с проводником значений
|
||||||
|
const P8P_CA_SOURCE_FIELDS = ["variableSource"];
|
||||||
|
|
||||||
|
//Список полей хранящих объект типа {"Тип", "Значение"}, где может храниться связь с проводником значений
|
||||||
|
const P8P_CA_OBJECT_SOURCE_FIELDS = ["resultValue", "tabValue", "panelValue"];
|
||||||
|
|
||||||
|
//Структура значения с типом
|
||||||
|
const P8P_CA_OBJECT_VALUE_SHAPE = PropTypes.shape({
|
||||||
|
type: PropTypes.string,
|
||||||
|
value: PropTypes.string
|
||||||
|
});
|
||||||
|
|
||||||
|
//Структура входных параметров метода вызова
|
||||||
|
const P8P_CA_INPUT_PARAM_SHAPE = PropTypes.shape({
|
||||||
|
parameter: PropTypes.string,
|
||||||
|
inputParameter: PropTypes.string,
|
||||||
|
resultValue: P8P_CA_OBJECT_VALUE_SHAPE
|
||||||
|
});
|
||||||
|
|
||||||
|
//Структура параметров действия "Открыть раздел"
|
||||||
|
const P8P_CA_OPEN_UNIT_PARAMS_SHAPE = PropTypes.shape({
|
||||||
|
unitCode: PropTypes.string,
|
||||||
|
unitName: PropTypes.string,
|
||||||
|
showMethod: PropTypes.string,
|
||||||
|
showMethodName: PropTypes.string,
|
||||||
|
inputParams: PropTypes.arrayOf(P8P_CA_INPUT_PARAM_SHAPE),
|
||||||
|
modal: PropTypes.bool
|
||||||
|
});
|
||||||
|
|
||||||
|
//Структура параметров действия "Открыть панель"
|
||||||
|
const P8P_CA_OPEN_PANEL_PARAMS_SHAPE = PropTypes.shape({
|
||||||
|
tabValue: P8P_CA_OBJECT_VALUE_SHAPE,
|
||||||
|
panelValue: P8P_CA_OBJECT_VALUE_SHAPE
|
||||||
|
});
|
||||||
|
|
||||||
|
//Структура связи переменной действия "Установить переменную"
|
||||||
|
const P8P_CA_VARIABLE_LINK_SHAPE = PropTypes.shape({
|
||||||
|
variableSource: PropTypes.string,
|
||||||
|
resultValue: P8P_CA_OBJECT_VALUE_SHAPE
|
||||||
|
});
|
||||||
|
|
||||||
|
//Структура действия
|
||||||
|
const P8P_CA_SHAPE = PropTypes.shape({
|
||||||
|
type: PropTypes.oneOf(Object.keys(P8P_CA_TYPE)).isRequired,
|
||||||
|
area: PropTypes.string,
|
||||||
|
element: PropTypes.string,
|
||||||
|
params: PropTypes.oneOfType([P8P_CA_OPEN_UNIT_PARAMS_SHAPE, P8P_CA_OPEN_PANEL_PARAMS_SHAPE, PropTypes.arrayOf(P8P_CA_VARIABLE_LINK_SHAPE)])
|
||||||
|
});
|
||||||
|
|
||||||
|
//Начальное состояние объекта значения с типом
|
||||||
|
const P8P_CA_OBJECT_VALUE_INITIAL = {
|
||||||
|
type: P8P_CA_DEF_TYPE_VALUE.TEXT_VALUE,
|
||||||
|
value: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
//Начальное состояние входного параметра метода вызова
|
||||||
|
const P8P_CA_INPUT_PARAM_INITIAL = {
|
||||||
|
parameter: "",
|
||||||
|
inputParameter: "",
|
||||||
|
resultValue: { ...P8P_CA_OBJECT_VALUE_INITIAL }
|
||||||
|
};
|
||||||
|
|
||||||
|
//Начальное состояние параметров действия "Открыть раздел"
|
||||||
|
const P8P_CA_OPEN_UNIT_INITIAL = {
|
||||||
|
unitCode: "",
|
||||||
|
unitName: "",
|
||||||
|
showMethod: "",
|
||||||
|
showMethodName: "",
|
||||||
|
inputParams: [],
|
||||||
|
modal: true
|
||||||
|
};
|
||||||
|
|
||||||
|
//Начальное состояние параметров действия "Открыть панель"
|
||||||
|
const P8P_CA_OPEN_PANEL_INITIAL = {
|
||||||
|
tabValue: { ...P8P_CA_OBJECT_VALUE_INITIAL },
|
||||||
|
panelValue: { ...P8P_CA_OBJECT_VALUE_INITIAL }
|
||||||
|
};
|
||||||
|
|
||||||
|
//Начальное состояние параметра связи действия "Установить переменную"
|
||||||
|
const P8P_CA_VARIABLE_LINK_INITIAL = {
|
||||||
|
variableSource: "",
|
||||||
|
resultValue: { ...P8P_CA_OBJECT_VALUE_INITIAL }
|
||||||
|
};
|
||||||
|
|
||||||
|
//Начальное состояние действия
|
||||||
|
const P8P_CA_INITIAL = {
|
||||||
|
type: P8P_CA_TYPE.openUnit.code,
|
||||||
|
area: "",
|
||||||
|
element: "",
|
||||||
|
params: { ...P8P_CA_OPEN_UNIT_INITIAL }
|
||||||
|
};
|
||||||
|
|
||||||
|
//Начальное состояние действий
|
||||||
|
const P8P_CAS_INITIAL = [];
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export {
|
||||||
|
P8P_CA_DEF_TYPE_VALUE,
|
||||||
|
P8P_CA_DEF_VALUE_TYPES,
|
||||||
|
P8P_CA_TYPE,
|
||||||
|
P8P_CA_SOURCE_FIELDS,
|
||||||
|
P8P_CA_OBJECT_SOURCE_FIELDS,
|
||||||
|
P8P_CA_OBJECT_VALUE_SHAPE,
|
||||||
|
P8P_CA_OPEN_UNIT_PARAMS_SHAPE,
|
||||||
|
P8P_CA_OPEN_PANEL_PARAMS_SHAPE,
|
||||||
|
P8P_CA_VARIABLE_LINK_SHAPE,
|
||||||
|
P8P_CA_SHAPE,
|
||||||
|
P8P_CA_INPUT_PARAM_INITIAL,
|
||||||
|
P8P_CA_OPEN_UNIT_INITIAL,
|
||||||
|
P8P_CA_OPEN_PANEL_INITIAL,
|
||||||
|
P8P_CA_VARIABLE_LINK_INITIAL,
|
||||||
|
P8P_CA_INITIAL,
|
||||||
|
P8P_CAS_INITIAL
|
||||||
|
};
|
||||||
159
app/components/editors/p8p_component_action/config_panel_open.js
Normal file
159
app/components/editors/p8p_component_action/config_panel_open.js
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Компонент: Редактор действия "Открыть панель"
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Box } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { P8P_CA_DEF_TYPE_VALUE, P8P_CA_OPEN_PANEL_PARAMS_SHAPE } from "./common"; //Общие ресурсы действий
|
||||||
|
import { P8PCAFieldWithType } from "./field_with_type"; //Поле с выбором типа значения
|
||||||
|
import { PanelsManager } from "../../../panels/panels_editor/components/panels_manager/panels_manager"; //Менеджер панелей
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CONTAINER_UNIT: { display: "flex", flexDirection: "column", gap: "10px" },
|
||||||
|
BOX_SETTINGS: { display: "flex", flexDirection: "row", gap: "15px" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//Ключ значений о странице
|
||||||
|
const SPROP_TAB = "tabValue";
|
||||||
|
//Ключа значений о панели
|
||||||
|
const SPROP_PANEL = "panelValue";
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Редактор действия "Открыть панель"
|
||||||
|
const P8PCAPanelOpenOptions = ({ panel, valueTypes, isValues, onStateChange, onValueSourceMenuClick }) => {
|
||||||
|
//Собственное состояние - Отображение менеджера панелей
|
||||||
|
const [openPanelsManager, setOpenPanelsManager] = useState(false);
|
||||||
|
|
||||||
|
//При открытии менеджера панелей
|
||||||
|
const handlePanelsManagerOpen = () => setOpenPanelsManager(true);
|
||||||
|
|
||||||
|
//При закрытии менеджера панелей
|
||||||
|
const handlePanelsManagerClose = () => setOpenPanelsManager(false);
|
||||||
|
|
||||||
|
//При выборе панели в менеджере панелей
|
||||||
|
const handlePanelSelect = selectedPanel => {
|
||||||
|
//Устанавливаем мнемокод выбранной панели
|
||||||
|
onStateChange({ ...panel, panelValue: { ...panel.panelValue, value: selectedPanel.code } });
|
||||||
|
//Закрываем менеджер панелей
|
||||||
|
handlePanelsManagerClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
//При изменении типа значения
|
||||||
|
const handleValueTypeChange = (value, keyProp) =>
|
||||||
|
onStateChange({
|
||||||
|
[keyProp]: { type: value, value: "" }
|
||||||
|
});
|
||||||
|
|
||||||
|
//При изменении значения
|
||||||
|
const handleValueChange = (value, keyProp) =>
|
||||||
|
onStateChange({
|
||||||
|
[keyProp]: { ...panel[keyProp], value: value }
|
||||||
|
});
|
||||||
|
|
||||||
|
//При очистке значения
|
||||||
|
const handleClearValueClick = keyProp =>
|
||||||
|
onStateChange({
|
||||||
|
[keyProp]: { ...panel[keyProp], value: "" }
|
||||||
|
});
|
||||||
|
|
||||||
|
//При изменении переменной
|
||||||
|
const handleValueSourceChange = (valueSource, keyProp) =>
|
||||||
|
onStateChange({
|
||||||
|
[keyProp]: { ...panel[keyProp], value: valueSource }
|
||||||
|
});
|
||||||
|
|
||||||
|
//При нажатии на выбор переменной
|
||||||
|
const handleValueSourceSelect = (e, keyProp) =>
|
||||||
|
isValues
|
||||||
|
? onValueSourceMenuClick &&
|
||||||
|
onValueSourceMenuClick(e, valueSource => {
|
||||||
|
handleValueSourceChange(valueSource, keyProp);
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Box sx={STYLES.CONTAINER_UNIT}>
|
||||||
|
{openPanelsManager && (
|
||||||
|
<PanelsManager
|
||||||
|
current={panel.panelValue.value}
|
||||||
|
isEditable={false}
|
||||||
|
onPanelSelect={handlePanelSelect}
|
||||||
|
onCancel={handlePanelsManagerClose}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Box sx={STYLES.BOX_SETTINGS}>
|
||||||
|
<P8PCAFieldWithType
|
||||||
|
valueTypes={valueTypes}
|
||||||
|
name={SPROP_TAB}
|
||||||
|
valueLabel={"Наименование вкладки"}
|
||||||
|
groupLabel={"Настройки вкладки"}
|
||||||
|
item={panel.tabValue}
|
||||||
|
isValueReadOnly={panel.tabValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
||||||
|
onTypeChange={e => handleValueTypeChange(e.target.value, SPROP_TAB)}
|
||||||
|
onValueChange={e => handleValueChange(e.target.value, SPROP_TAB)}
|
||||||
|
endAdornments={[
|
||||||
|
{ icon: "clear", onClick: () => handleClearValueClick(SPROP_TAB), isDisabled: false },
|
||||||
|
{
|
||||||
|
icon: "settings_ethernet",
|
||||||
|
onClick: e => handleValueSourceSelect(e, SPROP_TAB),
|
||||||
|
isDisabled: panel.tabValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<P8PCAFieldWithType
|
||||||
|
valueTypes={valueTypes}
|
||||||
|
name={SPROP_PANEL}
|
||||||
|
valueLabel={"Панель"}
|
||||||
|
groupLabel={"Настройки панели"}
|
||||||
|
item={panel.panelValue}
|
||||||
|
isValueReadOnly={panel.panelValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
||||||
|
onTypeChange={e => handleValueTypeChange(e.target.value, SPROP_PANEL)}
|
||||||
|
onValueChange={e => handleValueChange(e.target.value, SPROP_PANEL)}
|
||||||
|
endAdornments={[
|
||||||
|
{ icon: "clear", onClick: () => handleClearValueClick(SPROP_PANEL), isDisabled: false },
|
||||||
|
{
|
||||||
|
icon: "list",
|
||||||
|
onClick: handlePanelsManagerOpen,
|
||||||
|
isDisabled: panel.panelValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "settings_ethernet",
|
||||||
|
onClick: e => handleValueSourceSelect(e, SPROP_PANEL),
|
||||||
|
isDisabled: panel.panelValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - редактор действия "Открыть панель"
|
||||||
|
P8PCAPanelOpenOptions.propTypes = {
|
||||||
|
panel: P8P_CA_OPEN_PANEL_PARAMS_SHAPE,
|
||||||
|
valueTypes: PropTypes.array,
|
||||||
|
isValues: PropTypes.bool.isRequired,
|
||||||
|
onStateChange: PropTypes.func.isRequired,
|
||||||
|
onValueSourceMenuClick: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8PCAPanelOpenOptions };
|
||||||
285
app/components/editors/p8p_component_action/config_unit_open.js
Normal file
285
app/components/editors/p8p_component_action/config_unit_open.js
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Компонент: Редактор действия "Открыть раздел"
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useContext } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { TextField, Box, InputAdornment, IconButton, Icon, FormControlLabel, Checkbox } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { ApplicationСtx } from "../../../context/application"; //Контекст приложения
|
||||||
|
import { P8P_CA_OPEN_UNIT_PARAMS_SHAPE, P8P_CA_INPUT_PARAM_INITIAL, P8P_CA_DEF_TYPE_VALUE } from "./common"; //Общие ресурсы действий
|
||||||
|
import { P8PCAFieldWithType } from "./field_with_type"; //Поле с выбором типа значения
|
||||||
|
import { P8PCATablePropValues } from "./table_prop_values"; //Таблица значений свойств действия
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CONTAINER_UNIT: { display: "flex", flexDirection: "column", gap: "10px" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Редактор действия "Открыть раздел"
|
||||||
|
const P8PCAUnitOpenOptions = ({ unit, valueTypes, isValues, onStateChange, onValueSourceMenuClick }) => {
|
||||||
|
//Подключение к контексту приложения
|
||||||
|
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
|
//При изменении параметра открытия
|
||||||
|
const handleModalChange = () => onStateChange({ modal: !unit.modal });
|
||||||
|
|
||||||
|
//При нажатии на очистку раздела
|
||||||
|
const handleClearUnitClick = () =>
|
||||||
|
onStateChange({
|
||||||
|
unitCode: "",
|
||||||
|
unitName: "",
|
||||||
|
showMethod: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
//При нажатии на выбор раздела
|
||||||
|
const handleSelectUnitClick = () => {
|
||||||
|
pOnlineShowDictionary({
|
||||||
|
unitCode: "Units",
|
||||||
|
showMethod: "methods",
|
||||||
|
inputParameters: [
|
||||||
|
{ name: "pos_unit_name", value: unit.unitName },
|
||||||
|
{ name: "pos_method_name", value: unit.showMethodName }
|
||||||
|
],
|
||||||
|
callBack: res =>
|
||||||
|
res.success &&
|
||||||
|
onStateChange({
|
||||||
|
unitCode: res.outParameters.unit_code,
|
||||||
|
unitName: res.outParameters.unit_name,
|
||||||
|
showMethod: res.outParameters.method_code,
|
||||||
|
showMethodName: res.outParameters.method_name
|
||||||
|
})
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//При добавлении связи входного параметра
|
||||||
|
const handleInputParameAdd = () => {
|
||||||
|
onStateChange({
|
||||||
|
inputParams: [...unit.inputParams, { ...P8P_CA_INPUT_PARAM_INITIAL }]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//При удалении связи входного параметра
|
||||||
|
const handleInputParamDelete = index => {
|
||||||
|
//Копируем связываемые переменные
|
||||||
|
let newInputParams = [...unit.inputParams];
|
||||||
|
//Удаляем по индексу
|
||||||
|
newInputParams.splice(index, 1);
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange({ inputParams: [...newInputParams] });
|
||||||
|
};
|
||||||
|
|
||||||
|
//При очистке входного параметра
|
||||||
|
const handleClearInputParameterClick = index => {
|
||||||
|
//Копируем связываемые переменные
|
||||||
|
let newInputParams = [...unit.inputParams];
|
||||||
|
//Очищаем значение по индексу
|
||||||
|
newInputParams[index]["parameter"] = "";
|
||||||
|
newInputParams[index]["inputParameter"] = "";
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange({ inputParams: [...newInputParams] });
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на выбор параметра метода вызова
|
||||||
|
const handleSelectUnitParameterClick = index => {
|
||||||
|
unit.unitCode &&
|
||||||
|
unit.showMethod &&
|
||||||
|
pOnlineShowDictionary({
|
||||||
|
unitCode: "UnitParams",
|
||||||
|
showMethod: "main",
|
||||||
|
inputParameters: [
|
||||||
|
{ name: "in_UNITCODE", value: unit.unitCode },
|
||||||
|
{ name: "in_PARENT_METHOD_CODE", value: unit.showMethod },
|
||||||
|
{ name: "in_PARAMNAME", value: unit.inputParams[index].parameter }
|
||||||
|
],
|
||||||
|
callBack: res => {
|
||||||
|
if (res.success) {
|
||||||
|
//Копируем связываемые переменные
|
||||||
|
let newInputParams = [...unit.inputParams];
|
||||||
|
//Очищаем значение по индексу
|
||||||
|
newInputParams[index]["parameter"] = res.outParameters.out_PARAMNAME;
|
||||||
|
newInputParams[index]["inputParameter"] = res.outParameters.out_IN_CODE;
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange({ inputParams: [...newInputParams] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
//При изменении типа значения входного параметра
|
||||||
|
const handleInputParamValueTypeChange = (value, index) => {
|
||||||
|
//Копируем связываемые переменные
|
||||||
|
let newInputParams = [...unit.inputParams];
|
||||||
|
//Очищаем значение по индексу
|
||||||
|
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, type: value, value: "" };
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange({ inputParams: [...newInputParams] });
|
||||||
|
};
|
||||||
|
|
||||||
|
//При очистке значения параметра
|
||||||
|
const handleClearInputParamValueClick = index => {
|
||||||
|
//Копируем связываемые переменные
|
||||||
|
let newInputParams = [...unit.inputParams];
|
||||||
|
//Очищаем значение по индексу
|
||||||
|
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, value: "" };
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange({ inputParams: [...newInputParams] });
|
||||||
|
};
|
||||||
|
|
||||||
|
//При изменении значения входного параметра
|
||||||
|
const handleInputParamValueChange = (value, index) => {
|
||||||
|
//Копируем связываемые переменные
|
||||||
|
let newInputParams = [...unit.inputParams];
|
||||||
|
//Очищаем значение по индексу
|
||||||
|
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, value: value };
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange({ inputParams: [...newInputParams] });
|
||||||
|
};
|
||||||
|
|
||||||
|
//При связывании значения входного параметра с переменной
|
||||||
|
const handleInputParamValueSourceChange = (valueSource, index) => {
|
||||||
|
//Копируем связываемые переменные
|
||||||
|
let newInputParams = [...unit.inputParams];
|
||||||
|
//Очищаем значение по индексу
|
||||||
|
newInputParams[index].resultValue = { ...newInputParams[index].resultValue, value: valueSource };
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange({ inputParams: [...newInputParams] });
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на выбор переменной
|
||||||
|
const handleValueSourceSelect = (e, index) =>
|
||||||
|
isValues
|
||||||
|
? onValueSourceMenuClick &&
|
||||||
|
onValueSourceMenuClick(e, valueSource => {
|
||||||
|
handleInputParamValueSourceChange(valueSource, index);
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
|
//Рендер отображения колонки свойства
|
||||||
|
const handlePropertyCellRender = (item, index) => {
|
||||||
|
//Значение первой колонки
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
id={`parameter_${index}`}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={item.parameter}
|
||||||
|
name={"parameter"}
|
||||||
|
fullWidth
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={() => handleClearInputParameterClick(index)}>
|
||||||
|
<Icon>clear</Icon>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={() => handleSelectUnitParameterClick(index)} disabled={!unit.showMethodName}>
|
||||||
|
<Icon>list</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Рендер отображения колонки значения
|
||||||
|
const handleValueCellRender = (item, index) => {
|
||||||
|
//Формирование представления колонки значения
|
||||||
|
return (
|
||||||
|
<P8PCAFieldWithType
|
||||||
|
valueTypes={valueTypes}
|
||||||
|
name={`inputValue_${index}`}
|
||||||
|
item={item.resultValue}
|
||||||
|
isValueReadOnly={item.resultValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
||||||
|
onTypeChange={e => handleInputParamValueTypeChange(e.target.value, index)}
|
||||||
|
onValueChange={e => handleInputParamValueChange(e.target.value, index)}
|
||||||
|
endAdornments={[
|
||||||
|
{ icon: "clear", onClick: () => handleClearInputParamValueClick(index), isDisabled: false },
|
||||||
|
{
|
||||||
|
icon: "settings_ethernet",
|
||||||
|
onClick: e => handleValueSourceSelect(e, index),
|
||||||
|
isDisabled: item.resultValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Box sx={STYLES.CONTAINER_UNIT}>
|
||||||
|
<TextField
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={unit.unitName}
|
||||||
|
label={"Раздел"}
|
||||||
|
InputLabelProps={{ shrink: unit.unitName ? true : false }}
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={handleClearUnitClick}>
|
||||||
|
<Icon>clear</Icon>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={handleSelectUnitClick}>
|
||||||
|
<Icon>list</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={unit.showMethodName}
|
||||||
|
label={"Метод вызова"}
|
||||||
|
InputLabelProps={{ shrink: unit.showMethodName ? true : false }}
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true
|
||||||
|
}}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
<P8PCATablePropValues
|
||||||
|
name={"Входные параметры"}
|
||||||
|
items={unit.inputParams}
|
||||||
|
propertyCellName={"Входной параметр"}
|
||||||
|
valueCellName={"Значение"}
|
||||||
|
onPropertyCellRender={(item, index) => handlePropertyCellRender(item, index)}
|
||||||
|
onValueCellRender={(item, index) => handleValueCellRender(item, index)}
|
||||||
|
onAddRow={handleInputParameAdd}
|
||||||
|
onRowDelete={index => handleInputParamDelete(index)}
|
||||||
|
/>
|
||||||
|
<FormControlLabel control={<Checkbox checked={unit.modal} onChange={handleModalChange} />} label="Открывать модально" />
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - редактор действия "Открыть раздел"
|
||||||
|
P8PCAUnitOpenOptions.propTypes = {
|
||||||
|
unit: P8P_CA_OPEN_UNIT_PARAMS_SHAPE,
|
||||||
|
valueTypes: PropTypes.array,
|
||||||
|
isValues: PropTypes.bool.isRequired,
|
||||||
|
onStateChange: PropTypes.func.isRequired,
|
||||||
|
onValueSourceMenuClick: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8PCAUnitOpenOptions };
|
||||||
202
app/components/editors/p8p_component_action/config_var_set.js
Normal file
202
app/components/editors/p8p_component_action/config_var_set.js
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Компонент: Редактор действия "Установить переменную"
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { TextField, InputAdornment, IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { P8P_CA_VARIABLE_LINK_INITIAL, P8P_CA_VARIABLE_LINK_SHAPE, P8P_CA_DEF_TYPE_VALUE } from "./common"; //Общие ресурсы действий
|
||||||
|
import { P8PCAFieldWithType } from "./field_with_type"; //Поле с выбором типа значения
|
||||||
|
import { P8PCATablePropValues } from "./table_prop_values"; //Таблица значений свойств действия
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Редактор действия "Установить переменную"
|
||||||
|
const P8PCAVarSetOptions = ({ variables, valueTypes, isValues, onStateChange, onValueSourceMenuClick }) => {
|
||||||
|
//При добавлении связи переменной
|
||||||
|
const handleVariableAdd = () => {
|
||||||
|
onStateChange([...variables, { ...P8P_CA_VARIABLE_LINK_INITIAL }]);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При удалении связи переменной
|
||||||
|
const handleVariableDelete = index => {
|
||||||
|
//Копируем связываемые переменные
|
||||||
|
let newVariables = [...variables];
|
||||||
|
//Удаляем по индексу
|
||||||
|
newVariables.splice(index, 1);
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange([...newVariables]);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При очистке значения переменной
|
||||||
|
const handleClearVariableClick = index => {
|
||||||
|
//Копируем текущее состояние связываемых переменных
|
||||||
|
let newParams = [...variables];
|
||||||
|
//Очищаем значения у элемента по индексу
|
||||||
|
newParams[index].variableSource = "";
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange([...newParams]);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При связывании переменной
|
||||||
|
const handleVariablesSourceChange = (valueSource, index) => {
|
||||||
|
//Копируем связываемые переменные
|
||||||
|
let newVariables = [...variables];
|
||||||
|
//Очищаем значение по индексу
|
||||||
|
newVariables[index].variableSource = valueSource;
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange([...newVariables]);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При изменении типа значения
|
||||||
|
const handleResultValueTypeChange = (value, index) => {
|
||||||
|
//Копируем связываемые переменные
|
||||||
|
let newVariables = [...variables];
|
||||||
|
//Очищаем значение по индексу
|
||||||
|
newVariables[index].resultValue = { ...newVariables[index].resultValue, type: value, value: "" };
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange([...newVariables]);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При очистке значения
|
||||||
|
const handleClearResultValueClick = index => {
|
||||||
|
//Копируем текущее состояние связываемых переменных
|
||||||
|
let newVariables = [...variables];
|
||||||
|
//Очищаем значения у элемента по индексу
|
||||||
|
newVariables[index].resultValue = { ...newVariables[index].resultValue, value: "" };
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange([...newVariables]);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При ручном изменении значения
|
||||||
|
const handleResultValueChange = (value, index) => {
|
||||||
|
//Копируем связываемые переменные
|
||||||
|
let newVariables = [...variables];
|
||||||
|
//Очищаем значение по индексу
|
||||||
|
newVariables[index].resultValue = { ...newVariables[index].resultValue, value: value };
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange([...newVariables]);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При связывании значения
|
||||||
|
const handleResultValueSourceChange = (valueSource, index) => {
|
||||||
|
//Копируем связываемые переменные
|
||||||
|
let newVariables = [...variables];
|
||||||
|
//Очищаем значение по индексу
|
||||||
|
newVariables[index].resultValue = { ...newVariables[index].resultValue, value: valueSource };
|
||||||
|
//Устанавливаем новый массив
|
||||||
|
onStateChange([...newVariables]);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на выбор переменной (значение)
|
||||||
|
const handleValueSourceSelect = (e, index) =>
|
||||||
|
isValues
|
||||||
|
? onValueSourceMenuClick &&
|
||||||
|
onValueSourceMenuClick(e, valueSource => {
|
||||||
|
handleResultValueSourceChange(valueSource, index);
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
|
//При нажатии на выбор переменной (переменная)
|
||||||
|
const handleVariableSourceSelect = (e, index) =>
|
||||||
|
isValues
|
||||||
|
? onValueSourceMenuClick &&
|
||||||
|
onValueSourceMenuClick(e, valueSource => {
|
||||||
|
handleVariablesSourceChange(valueSource, index);
|
||||||
|
})
|
||||||
|
: null;
|
||||||
|
|
||||||
|
//Рендер отображения колонки свойства
|
||||||
|
const handlePropertyCellRender = (item, index) => {
|
||||||
|
//Формирование представления колонки свойства
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
id={`variableSource_${index}`}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={item.variableSource}
|
||||||
|
name={"variableSource"}
|
||||||
|
fullWidth
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={() => handleClearVariableClick(index)}>
|
||||||
|
<Icon>clear</Icon>
|
||||||
|
</IconButton>
|
||||||
|
{isValues && (
|
||||||
|
<IconButton onClick={e => handleVariableSourceSelect(e, index)}>
|
||||||
|
<Icon>settings_ethernet</Icon>
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Рендер отображения колонки значения
|
||||||
|
const handleValueCellRender = (item, index) => {
|
||||||
|
//Формирование представления колонки значения
|
||||||
|
return (
|
||||||
|
<P8PCAFieldWithType
|
||||||
|
valueTypes={valueTypes}
|
||||||
|
name={`resultValue_${index}`}
|
||||||
|
item={item.resultValue}
|
||||||
|
isValueReadOnly={item.resultValue.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE}
|
||||||
|
onTypeChange={e => handleResultValueTypeChange(e.target.value, index)}
|
||||||
|
onValueChange={e => handleResultValueChange(e.target.value, index)}
|
||||||
|
endAdornments={[
|
||||||
|
{ icon: "clear", onClick: () => handleClearResultValueClick(index), isDisabled: false },
|
||||||
|
{
|
||||||
|
icon: "settings_ethernet",
|
||||||
|
onClick: e => handleValueSourceSelect(e, index),
|
||||||
|
isDisabled: item.resultValue.type !== P8P_CA_DEF_TYPE_VALUE.VARIABLE
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<P8PCATablePropValues
|
||||||
|
name={"Список параметров"}
|
||||||
|
items={variables}
|
||||||
|
propertyCellName={"Переменная"}
|
||||||
|
valueCellName={"Значение"}
|
||||||
|
onPropertyCellRender={(item, index) => handlePropertyCellRender(item, index)}
|
||||||
|
onValueCellRender={(item, index) => handleValueCellRender(item, index)}
|
||||||
|
onAddRow={handleVariableAdd}
|
||||||
|
onRowDelete={index => handleVariableDelete(index)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - редактор действия "Установить переменную"
|
||||||
|
P8PCAVarSetOptions.propTypes = {
|
||||||
|
variables: PropTypes.arrayOf(P8P_CA_VARIABLE_LINK_SHAPE),
|
||||||
|
valueTypes: PropTypes.array,
|
||||||
|
isValues: PropTypes.bool.isRequired,
|
||||||
|
onStateChange: PropTypes.func.isRequired,
|
||||||
|
onValueSourceMenuClick: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8PCAVarSetOptions };
|
||||||
250
app/components/editors/p8p_component_action/editor.js
Normal file
250
app/components/editors/p8p_component_action/editor.js
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Компонент: Редактор действия
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { TextField, Select, Menu, MenuItem, FormControl, InputLabel, Stack, Box } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { TITLES } from "../../../../app.text"; //Общие текстовые ресурсы
|
||||||
|
import { P8PConfigDialog } from "../p8p_config_dialog"; //Диалог настройки
|
||||||
|
import { deepCopyObject } from "../../../core/utils"; //Вспомогательные функции
|
||||||
|
import { P8P_CA_DEF_VALUE_TYPES, P8P_CA_TYPE, P8P_CA_SHAPE, P8P_CA_INPUT_PARAM_INITIAL, P8P_CA_OPEN_UNIT_INITIAL, P8P_CA_INITIAL } from "./common"; //Общие ресурсы действий
|
||||||
|
import { P8PCAUnitOpenOptions } from "./config_unit_open"; //Компонент редактора действия "Открыть раздел"
|
||||||
|
import { P8PCAPanelOpenOptions } from "./config_panel_open"; //Компонент редактора действия "Открыть панель"
|
||||||
|
import { P8PCAVarSetOptions } from "./config_var_set"; //Компонент редактора действия "Установить переменную"
|
||||||
|
import { isActionOkDisabled, getActionTypeParams } from "./util"; //Вспомогательные ресурсы действий
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CONTAINER: { display: "flex", flexDirection: "column", gap: "10px", minWidth: "300px" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Редактор действия
|
||||||
|
const P8PCAEditor = ({ areas = [], valueTypes = [], action = null, onOk = null, onCancel = null, valueProviders = {} }) => {
|
||||||
|
//Собственное состояние - признак инициализиации
|
||||||
|
const [init, setInit] = useState(true);
|
||||||
|
|
||||||
|
//Собственное состояние - параметры действия
|
||||||
|
const [state, setState] = useState({});
|
||||||
|
|
||||||
|
//Собственное состояние - доступные типы значений компонента
|
||||||
|
const [availableValueTypes, setAvailableValueTypes] = useState([]);
|
||||||
|
|
||||||
|
//Собственное состояние - доступные области компонента
|
||||||
|
const [availableAreas, setAvailableAreas] = useState([]);
|
||||||
|
|
||||||
|
//Собственное состояние - доступность поля "Элемент"
|
||||||
|
const [hasElement, setHasElement] = useState(false);
|
||||||
|
|
||||||
|
//Собственное состояние - элемент привязки меню выбора источника
|
||||||
|
const [valueProvidersMenuAnchorEl, setValueProvidersMenuAnchorEl] = useState({
|
||||||
|
target: null,
|
||||||
|
onChange: null
|
||||||
|
});
|
||||||
|
|
||||||
|
//Открытие/сокрытие меню выбора источника
|
||||||
|
const toggleValueProvidersMenu = (target, onChange) =>
|
||||||
|
setValueProvidersMenuAnchorEl(target instanceof Element ? { target, onChange } : { target: null, onChange: null });
|
||||||
|
|
||||||
|
//При отображении меню связывания значения с поставщиком данных
|
||||||
|
const handleValueSourceLinkMenuClick = (e, onChange) => setValueProvidersMenuAnchorEl({ target: e.currentTarget, onChange });
|
||||||
|
|
||||||
|
//При закрытии редактора с сохранением
|
||||||
|
const handleOk = () => onOk && onOk({ ...state });
|
||||||
|
|
||||||
|
//При закрытии редактора с отменой
|
||||||
|
const handleCancel = () => onCancel && onCancel();
|
||||||
|
|
||||||
|
//При изменении типа действия
|
||||||
|
const handleTypeChange = e => {
|
||||||
|
//Иницилизируем параметры типа
|
||||||
|
let newParams = getActionTypeParams(e.target.value);
|
||||||
|
//Изменяем тип действия с изменение структуры параметров
|
||||||
|
setState(pv => ({
|
||||||
|
...pv,
|
||||||
|
type: e.target.value,
|
||||||
|
params: Array.isArray(newParams) ? [...newParams] : { ...newParams }
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
//При ручном изменении общего параметра действия
|
||||||
|
const handleChange = e => setState(pv => ({ ...pv, [e.target.name]: e.target.value }));
|
||||||
|
|
||||||
|
//При изменении области действия
|
||||||
|
const handleAreaChange = e => {
|
||||||
|
//Устанавливаем значение
|
||||||
|
setState(pv => ({ ...pv, [e.target.name]: e.target.value, name: "" }));
|
||||||
|
//Устанавливаем доступность "Элемент"
|
||||||
|
setHasElement(availableAreas.find(item => item.area === e.target.value).hasElement);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При изменении полей параметров действия
|
||||||
|
const handleParamsChange = params => {
|
||||||
|
setState(pv => ({ ...pv, params: { ...pv.params, ...params } }));
|
||||||
|
};
|
||||||
|
|
||||||
|
//При изменении переменных параметров действия "Установить переменную"
|
||||||
|
const handleVariablesParamsChange = variables => {
|
||||||
|
setState(pv => ({ ...pv, params: [...variables] }));
|
||||||
|
};
|
||||||
|
|
||||||
|
//Список значений
|
||||||
|
const values = Object.keys(valueProviders);
|
||||||
|
|
||||||
|
//Наличие значений
|
||||||
|
const isValues = values && values.length > 0 ? true : false;
|
||||||
|
|
||||||
|
//Меню привязки к поставщикам значений
|
||||||
|
const valueProvidersMenu = isValues && (
|
||||||
|
<Menu anchorEl={valueProvidersMenuAnchorEl.target} open={Boolean(valueProvidersMenuAnchorEl.target)} onClose={toggleValueProvidersMenu}>
|
||||||
|
{values.map((value, i) => (
|
||||||
|
<MenuItem
|
||||||
|
key={i}
|
||||||
|
onClick={() => {
|
||||||
|
//Выполняем выбор параметра
|
||||||
|
valueProvidersMenuAnchorEl.onChange(value);
|
||||||
|
//Закрываем меню выбора переменной
|
||||||
|
toggleValueProvidersMenu();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
//При инициализации действия
|
||||||
|
useEffect(() => {
|
||||||
|
//Если это иницализация
|
||||||
|
if (init) {
|
||||||
|
//Если это открытие действия - берем его параметры, иначе - инициализируем изначальными
|
||||||
|
const initAction = action
|
||||||
|
? action
|
||||||
|
: {
|
||||||
|
...P8P_CA_INITIAL,
|
||||||
|
area: areas[0].area,
|
||||||
|
params: { ...P8P_CA_OPEN_UNIT_INITIAL, inputParams: [{ ...P8P_CA_INPUT_PARAM_INITIAL }] }
|
||||||
|
};
|
||||||
|
//Устанавливаем параметры действия
|
||||||
|
setState(deepCopyObject(initAction));
|
||||||
|
//Заполняем доступные области
|
||||||
|
setAvailableAreas([...areas]);
|
||||||
|
//Определяем доступность "Элемент"
|
||||||
|
setHasElement(areas.find(item => item.area === initAction.area)?.hasElement || false);
|
||||||
|
//Заполняем доступные типы значений
|
||||||
|
setAvailableValueTypes([...P8P_CA_DEF_VALUE_TYPES, ...valueTypes]);
|
||||||
|
//Сбрасываем признак инициализации
|
||||||
|
setInit(false);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [init]);
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!init ? (
|
||||||
|
<P8PConfigDialog
|
||||||
|
title={`${action ? TITLES.UPDATE : TITLES.INSERT} действия`}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
width={"xl"}
|
||||||
|
okDisabled={isActionOkDisabled(state)}
|
||||||
|
>
|
||||||
|
<Stack direction={"column"} spacing={1}>
|
||||||
|
{valueProvidersMenu}
|
||||||
|
<Box sx={STYLES.CONTAINER}>
|
||||||
|
{availableAreas.length !== 0 ? (
|
||||||
|
<FormControl variant={"standard"} fullWidth>
|
||||||
|
<InputLabel id={"areaLabel-label"}>Область</InputLabel>
|
||||||
|
<Select name={"area"} value={state.area} labelId={"area-label"} label={"Область"} onChange={handleAreaChange}>
|
||||||
|
{availableAreas.map((item, index) => (
|
||||||
|
<MenuItem value={item.area} key={index}>
|
||||||
|
{item.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
) : null}
|
||||||
|
{hasElement && (
|
||||||
|
<TextField
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={state.element}
|
||||||
|
label={"Элемент"}
|
||||||
|
name={"element"}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<FormControl variant={"standard"} fullWidth>
|
||||||
|
<InputLabel id={"type-label"}>Тип</InputLabel>
|
||||||
|
<Select name={"type"} value={state.type} labelId={"type-label"} label={"Тип"} onChange={handleTypeChange}>
|
||||||
|
{Object.keys(P8P_CA_TYPE).map((item, index) => (
|
||||||
|
<MenuItem value={P8P_CA_TYPE[item].code} key={index}>
|
||||||
|
{P8P_CA_TYPE[item].name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</Box>
|
||||||
|
{state.type === P8P_CA_TYPE.openUnit.code && (
|
||||||
|
<P8PCAUnitOpenOptions
|
||||||
|
unit={state.params}
|
||||||
|
valueTypes={availableValueTypes}
|
||||||
|
isValues={isValues}
|
||||||
|
onStateChange={handleParamsChange}
|
||||||
|
onValueSourceMenuClick={handleValueSourceLinkMenuClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{state.type === P8P_CA_TYPE.openPanel.code && (
|
||||||
|
<P8PCAPanelOpenOptions
|
||||||
|
panel={state.params}
|
||||||
|
valueTypes={availableValueTypes}
|
||||||
|
isValues={isValues}
|
||||||
|
onStateChange={handleParamsChange}
|
||||||
|
onValueSourceMenuClick={handleValueSourceLinkMenuClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{state.type === P8P_CA_TYPE.setVariable.code && (
|
||||||
|
<P8PCAVarSetOptions
|
||||||
|
variables={state.params}
|
||||||
|
valueTypes={availableValueTypes}
|
||||||
|
isValues={isValues}
|
||||||
|
onStateChange={handleVariablesParamsChange}
|
||||||
|
onValueSourceMenuClick={handleValueSourceLinkMenuClick}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</P8PConfigDialog>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Редактор условия
|
||||||
|
P8PCAEditor.propTypes = {
|
||||||
|
areas: PropTypes.array,
|
||||||
|
valueTypes: PropTypes.array,
|
||||||
|
action: P8P_CA_SHAPE,
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
valueProviders: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8PCAEditor };
|
||||||
105
app/components/editors/p8p_component_action/field_with_type.js
Normal file
105
app/components/editors/p8p_component_action/field_with_type.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Компонент: Поле с выбором типа значения
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { TextField, Select, MenuItem, FormControl, Box, InputAdornment, IconButton, Icon, FormLabel } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { P8P_CA_OBJECT_VALUE_SHAPE } from "./common"; //Общие ресурсы действий
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CONTAINER: { display: "flex", flexDirection: "column", gap: "10px", alignItems: "center" },
|
||||||
|
LEGEND_SETTINGS: { paddingTop: "10px", marginBottom: "-5px" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Поле с выбором типа значения
|
||||||
|
const P8PCAFieldWithType = ({
|
||||||
|
valueTypes,
|
||||||
|
item,
|
||||||
|
name,
|
||||||
|
valueLabel = null,
|
||||||
|
groupLabel = null,
|
||||||
|
isValueReadOnly = false,
|
||||||
|
onTypeChange,
|
||||||
|
onValueChange = null,
|
||||||
|
endAdornments = []
|
||||||
|
}) => {
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<Box sx={STYLES.CONTAINER}>
|
||||||
|
{groupLabel ? (
|
||||||
|
<FormLabel component="legend" sx={STYLES.LEGEND_SETTINGS}>
|
||||||
|
{groupLabel}
|
||||||
|
</FormLabel>
|
||||||
|
) : null}
|
||||||
|
<FormControl variant={"standard"} fullWidth>
|
||||||
|
<Select name={`${name}_type`} value={item.type} labelId={`${name}-type-label`} onChange={onTypeChange} fullWidth>
|
||||||
|
{valueTypes.map((type, index) => (
|
||||||
|
<MenuItem value={type} key={index}>
|
||||||
|
{type}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<TextField
|
||||||
|
id={`${name}_value`}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={item.value}
|
||||||
|
name={`${name}`}
|
||||||
|
label={valueLabel}
|
||||||
|
onChange={e => {
|
||||||
|
onValueChange && onValueChange(e);
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
InputProps={{
|
||||||
|
readOnly: isValueReadOnly,
|
||||||
|
endAdornment:
|
||||||
|
endAdornments.length !== 0 ? (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
{endAdornments.map((item, index) => (
|
||||||
|
<IconButton onClick={e => item.onClick(e)} key={index} disabled={item.isDisabled}>
|
||||||
|
<Icon>{item.icon}</Icon>
|
||||||
|
</IconButton>
|
||||||
|
))}
|
||||||
|
</InputAdornment>
|
||||||
|
) : null
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - поле с выбором типа значения
|
||||||
|
P8PCAFieldWithType.propTypes = {
|
||||||
|
valueTypes: PropTypes.array.isRequired,
|
||||||
|
item: P8P_CA_OBJECT_VALUE_SHAPE.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
valueLabel: PropTypes.string,
|
||||||
|
groupLabel: PropTypes.string,
|
||||||
|
isValueReadOnly: PropTypes.bool,
|
||||||
|
onTypeChange: PropTypes.func.isRequired,
|
||||||
|
onValueChange: PropTypes.func,
|
||||||
|
endAdornments: PropTypes.arrayOf(PropTypes.object)
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8PCAFieldWithType };
|
||||||
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Компонент: Таблица значений свойств действия
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Box, IconButton, Icon, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, Typography } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { APP_STYLES } from "../../../../app.styles"; //Типовые стили
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CONTAINER_VARIABLES: { maxHeight: "240px", overflow: "auto", ...APP_STYLES.SCROLL },
|
||||||
|
BOX_VARIABLES_INFO: { display: "flex", justifyContent: "space-between", alignItems: "center", paddingTop: "10px" },
|
||||||
|
TABLE_HEAD_CELL_PROPERTY: { width: "45%", padding: "8px" },
|
||||||
|
TABLE_HEAD_CELL_VALUE: { width: "45%", padding: "8px" },
|
||||||
|
TABLE_HEAD_CELL_DELETE: { width: "10%", padding: "8px" },
|
||||||
|
TABLE_VARIABLES: { maxWidth: "700px", minWidth: "700px" },
|
||||||
|
TABLE_ROW_VARIABLES: { "&:last-child td, &:last-child th": { border: 0 } },
|
||||||
|
TABLE_CELL_VARIABLES: { padding: "8px 16px" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Таблица значений свойств действия
|
||||||
|
const P8PCATablePropValues = ({ name, items, propertyCellName, valueCellName, onPropertyCellRender, onValueCellRender, onAddRow, onRowDelete }) => {
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={STYLES.BOX_VARIABLES_INFO}>
|
||||||
|
<Typography pl={1}>{name}</Typography>
|
||||||
|
<IconButton onClick={onAddRow}>
|
||||||
|
<Icon>add</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
<TableContainer component={Paper} sx={STYLES.CONTAINER_VARIABLES}>
|
||||||
|
<Table sx={STYLES.TABLE_VARIABLES} aria-label="simple table">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell align="center" sx={STYLES.TABLE_HEAD_CELL_PROPERTY}>
|
||||||
|
{propertyCellName}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" sx={STYLES.TABLE_HEAD_CELL_VALUE}>
|
||||||
|
{valueCellName}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" sx={STYLES.TABLE_HEAD_CELL_DELETE}></TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{Array.isArray(items) &&
|
||||||
|
items.map((item, i) => (
|
||||||
|
<TableRow key={i} sx={STYLES.TABLE_ROW_VARIABLES}>
|
||||||
|
<TableCell align="center" sx={STYLES.TABLE_CELL_VARIABLES}>
|
||||||
|
{onPropertyCellRender ? onPropertyCellRender(item, i) : null}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" sx={STYLES.TABLE_CELL_VARIABLES}>
|
||||||
|
{onValueCellRender ? onValueCellRender(item, i) : null}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="center" sx={STYLES.TABLE_CELL_VARIABLES}>
|
||||||
|
<IconButton onClick={() => onRowDelete(i)}>
|
||||||
|
<Icon>clear</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - таблица значений свойств действия
|
||||||
|
P8PCATablePropValues.propTypes = {
|
||||||
|
name: PropTypes.string,
|
||||||
|
items: PropTypes.array,
|
||||||
|
propertyCellName: PropTypes.string,
|
||||||
|
valueCellName: PropTypes.string,
|
||||||
|
onPropertyCellRender: PropTypes.func,
|
||||||
|
onValueCellRender: PropTypes.func,
|
||||||
|
onAddRow: PropTypes.func,
|
||||||
|
onRowDelete: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8PCATablePropValues };
|
||||||
212
app/components/editors/p8p_component_action/util.js
Normal file
212
app/components/editors/p8p_component_action/util.js
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Вспомогательные ресурсы компонента "Редактор действия"
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import { genUID } from "../../../core/utils"; //Вспомогательные функции
|
||||||
|
import {
|
||||||
|
P8P_CA_DEF_TYPE_VALUE,
|
||||||
|
P8P_CA_TYPE,
|
||||||
|
P8P_CA_SOURCE_FIELDS,
|
||||||
|
P8P_CA_OBJECT_SOURCE_FIELDS,
|
||||||
|
P8P_CA_OPEN_UNIT_INITIAL,
|
||||||
|
P8P_CA_INPUT_PARAM_INITIAL,
|
||||||
|
P8P_CA_OPEN_PANEL_INITIAL,
|
||||||
|
P8P_CA_VARIABLE_LINK_INITIAL
|
||||||
|
} from "./common"; //Общие ресурсы действий
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Доступность сохранения настроек действия
|
||||||
|
const isActionOkDisabled = action => {
|
||||||
|
//Если область или тип не указаны
|
||||||
|
if (!action.area || !action.type) return true;
|
||||||
|
//Исходим от типа
|
||||||
|
switch (action.type) {
|
||||||
|
//Открыть раздел
|
||||||
|
case P8P_CA_TYPE.openUnit.code:
|
||||||
|
return !action.params.unitCode || !action.params.showMethod;
|
||||||
|
//Открыть панель
|
||||||
|
case P8P_CA_TYPE.openPanel.code:
|
||||||
|
return !action.params.tabValue.type || !action.params.tabValue.value || !action.params.panelValue.type || !action.params.panelValue.value;
|
||||||
|
//Установить переменную
|
||||||
|
case P8P_CA_TYPE.setVariable.code:
|
||||||
|
return action.params.length === 0 || action.params.some(item => !item.variableSource);
|
||||||
|
//Для всех остальных
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Считывание параметров типа действия
|
||||||
|
const getActionTypeParams = type => {
|
||||||
|
//Определяем от типа
|
||||||
|
switch (type) {
|
||||||
|
//Открыть раздел
|
||||||
|
case P8P_CA_TYPE.openUnit.code:
|
||||||
|
return { ...P8P_CA_OPEN_UNIT_INITIAL, inputParams: [{ ...P8P_CA_INPUT_PARAM_INITIAL }] };
|
||||||
|
//Открыть панель
|
||||||
|
case P8P_CA_TYPE.openPanel.code:
|
||||||
|
return { ...P8P_CA_OPEN_PANEL_INITIAL };
|
||||||
|
//Установить переменную
|
||||||
|
case P8P_CA_TYPE.setVariable.code:
|
||||||
|
return [{ ...P8P_CA_VARIABLE_LINK_INITIAL }];
|
||||||
|
//Для всех остальных
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Определение значения действия по типу
|
||||||
|
const getActionValueByType = (type, value, values, prms = {}, getCustomTypeValue = null) => {
|
||||||
|
//Исходим от типа действия
|
||||||
|
switch (type) {
|
||||||
|
//Значение (стандартное)
|
||||||
|
case P8P_CA_DEF_TYPE_VALUE.TEXT_VALUE:
|
||||||
|
return value;
|
||||||
|
//Переменная (стандартное)
|
||||||
|
case P8P_CA_DEF_TYPE_VALUE.VARIABLE:
|
||||||
|
return values[value];
|
||||||
|
//Кастомный тип
|
||||||
|
default:
|
||||||
|
return getCustomTypeValue ? getCustomTypeValue({ type, value, values, prms }) : null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Определения функции действия
|
||||||
|
const getActionFunction = (item, onOpenUnit, configUrlBase, onOpenPanel, onProviderChange, getCustomTypeValue = null) => {
|
||||||
|
//Выполняем исходя из типа действия
|
||||||
|
switch (item.type) {
|
||||||
|
//Открыть раздел
|
||||||
|
case P8P_CA_TYPE.openUnit.code: {
|
||||||
|
return ({ event, values, prms }) => {
|
||||||
|
onOpenUnit({
|
||||||
|
unitCode: item.params.unitCode,
|
||||||
|
showMethod: item.params.showMethod,
|
||||||
|
inputParameters: item.params.inputParams.reduce(
|
||||||
|
(prev, cur) =>
|
||||||
|
cur.inputParameter
|
||||||
|
? [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
name: cur.inputParameter,
|
||||||
|
value: getActionValueByType(cur.resultValue.type, cur.resultValue.value, values, prms, getCustomTypeValue)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [...prev],
|
||||||
|
[]
|
||||||
|
),
|
||||||
|
modal: item.params.modal
|
||||||
|
});
|
||||||
|
event?.stopPropagation && event.stopPropagation();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//Открыть панель
|
||||||
|
case P8P_CA_TYPE.openPanel.code: {
|
||||||
|
return ({ event, values, prms }) => {
|
||||||
|
onOpenPanel({
|
||||||
|
id: genUID(),
|
||||||
|
url: `${configUrlBase}panels_editor?SCODE=${getActionValueByType(
|
||||||
|
item.params.panelValue.type,
|
||||||
|
item.params.panelValue.value,
|
||||||
|
values,
|
||||||
|
prms,
|
||||||
|
getCustomTypeValue
|
||||||
|
)}`,
|
||||||
|
caption:
|
||||||
|
getActionValueByType(item.params.tabValue.type, item.params.tabValue.value, values, prms, getCustomTypeValue) ||
|
||||||
|
"Редактор панелей"
|
||||||
|
});
|
||||||
|
event?.stopPropagation && event.stopPropagation();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//Установить переменную
|
||||||
|
case P8P_CA_TYPE.setVariable.code: {
|
||||||
|
//Устанавливаем новые значения проводников
|
||||||
|
return ({ event, values, prms }) => {
|
||||||
|
//Собираем объект новых значений проводников
|
||||||
|
let changedValues = item.params.reduce(
|
||||||
|
(prev, cur) => ({
|
||||||
|
...prev,
|
||||||
|
...{
|
||||||
|
[cur.variableSource]: getActionValueByType(cur.resultValue.type, cur.resultValue.value, values, prms, getCustomTypeValue)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
onProviderChange({ ...changedValues });
|
||||||
|
event?.stopPropagation && event.stopPropagation();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Выполнение действий компонента
|
||||||
|
const getHandlersByActions = (actions, onOpenUnit, configUrlBase, onOpenPanel, onProviderChange, getCustomTypeValue = null) => {
|
||||||
|
//Инициализируем обработчики
|
||||||
|
const handlers = actions.reduce((prev, cur) => {
|
||||||
|
//Ключ обработчика
|
||||||
|
let key = `${cur.area}.${cur.element}`;
|
||||||
|
//Если уже был добавлен - не добавляем, иначе добавляем
|
||||||
|
return !prev[key] ? { ...prev, [key]: { area: cur.area, element: cur.element, fn: null } } : { ...prev };
|
||||||
|
}, {});
|
||||||
|
//Обходим уникальные обработчики
|
||||||
|
for (const handler in handlers) {
|
||||||
|
//Считываем действия области
|
||||||
|
let areaActions = actions.filter(item => item.area === handlers[handler].area && item.element === handlers[handler].element);
|
||||||
|
//Собираем массив обработчиков области
|
||||||
|
let areaFunctions = areaActions.reduce(
|
||||||
|
(fns, action) => [...fns, getActionFunction(action, onOpenUnit, configUrlBase, onOpenPanel, onProviderChange, getCustomTypeValue)],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
//Устанавливаем области последовательный вызов обработчиков
|
||||||
|
handlers[handler].fn = ({ event, values, prms }) => {
|
||||||
|
areaFunctions.map(fn => fn({ event, values, prms }));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//Возвращаем обработчики
|
||||||
|
return handlers;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Считывание всех связанных переменных рекурсивно
|
||||||
|
const getActionsVariablesRecursive = (objValue, propName = null) => {
|
||||||
|
//Если это значение
|
||||||
|
if (!(typeof objValue === "object") && !Array.isArray(objValue)) {
|
||||||
|
//Если поле из списка хранящих ссылку - берем его
|
||||||
|
return P8P_CA_SOURCE_FIELDS.includes(propName) && objValue ? [objValue] : [];
|
||||||
|
}
|
||||||
|
//Если это объект хранящий тип и значение - может хранить ссылку на переменную
|
||||||
|
if (P8P_CA_OBJECT_SOURCE_FIELDS.includes(propName)) {
|
||||||
|
//Если тип "Переменная" - указываем значение
|
||||||
|
return objValue?.type === P8P_CA_DEF_TYPE_VALUE.VARIABLE && objValue?.value ? [objValue.value] : [];
|
||||||
|
}
|
||||||
|
//Если это массив
|
||||||
|
if (Array.isArray(objValue)) {
|
||||||
|
return objValue.reduce((prev, cur) => [...prev, ...getActionsVariablesRecursive(cur)], []);
|
||||||
|
} else {
|
||||||
|
//Если это объект
|
||||||
|
return Object.keys(objValue).reduce((prev, cur) => [...prev, ...getActionsVariablesRecursive(objValue[cur], cur)], []);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Считывание всех связанных переменных действий
|
||||||
|
const getActionsVariables = actions => {
|
||||||
|
//Считываем связанные переменные действий рекурсивно
|
||||||
|
return getActionsVariablesRecursive(actions);
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { isActionOkDisabled, getActionTypeParams, getActionsVariables, getHandlersByActions };
|
||||||
82
app/components/editors/p8p_component_condition/common.js
Normal file
82
app/components/editors/p8p_component_condition/common.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Общие ресурсы компонента "Редактор условия"
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Доступные операторы условия
|
||||||
|
const P8P_CC_OPERATORS = [
|
||||||
|
{ name: "==", value: "equal" },
|
||||||
|
{ name: "!=", value: "notEqual" },
|
||||||
|
{ name: "<=", value: "lessEqual" },
|
||||||
|
{ name: "<", value: "less" },
|
||||||
|
{ name: ">=", value: "greaterEqual" },
|
||||||
|
{ name: ">", value: "greater" },
|
||||||
|
{ name: "in", value: "in" }
|
||||||
|
];
|
||||||
|
|
||||||
|
//Структура параметра поля условия
|
||||||
|
const P8P_CC_FIELD_PRM_SHAPE = PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.string.isRequired,
|
||||||
|
hasElement: PropTypes.bool,
|
||||||
|
icon: PropTypes.string
|
||||||
|
});
|
||||||
|
|
||||||
|
//Структура поля условия
|
||||||
|
const P8P_CC_FIELD_SHAPE = PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.string.isRequired
|
||||||
|
});
|
||||||
|
|
||||||
|
//Структура оператора условия
|
||||||
|
const P8P_CC_OPERATOR_SHAPE = PropTypes.shape({
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.string.isRequired
|
||||||
|
});
|
||||||
|
|
||||||
|
//Структура условия
|
||||||
|
const P8P_CC_SHAPE = PropTypes.shape({
|
||||||
|
condField: P8P_CC_FIELD_SHAPE.isRequired,
|
||||||
|
condOperator: P8P_CC_OPERATOR_SHAPE.isRequired,
|
||||||
|
condElement: PropTypes.string, //Пока
|
||||||
|
condValue: PropTypes.string.isRequired,
|
||||||
|
resField: P8P_CC_FIELD_SHAPE.isRequired,
|
||||||
|
resElement: PropTypes.string, //Пока
|
||||||
|
resValue: PropTypes.string.isRequired
|
||||||
|
});
|
||||||
|
|
||||||
|
//Начальное состояние поля условия
|
||||||
|
const P8P_CC_FIELD_INITIAL = {
|
||||||
|
name: "",
|
||||||
|
value: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
//Начальное состояние условия
|
||||||
|
const P8P_CC_INITIAL = {
|
||||||
|
condField: { ...P8P_CC_FIELD_INITIAL },
|
||||||
|
condOperator: { ...P8P_CC_OPERATORS[0] },
|
||||||
|
condElement: "",
|
||||||
|
condValue: "",
|
||||||
|
resField: { ...P8P_CC_FIELD_INITIAL },
|
||||||
|
resElement: "",
|
||||||
|
resValue: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
//Начальное состояние условий
|
||||||
|
const P8P_CCS_INITIAL = [];
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8P_CC_OPERATORS, P8P_CC_FIELD_PRM_SHAPE, P8P_CC_FIELD_SHAPE, P8P_CC_SHAPE, P8P_CC_INITIAL, P8P_CCS_INITIAL };
|
||||||
239
app/components/editors/p8p_component_condition/editor.js
Normal file
239
app/components/editors/p8p_component_condition/editor.js
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Компонент: Редактор условия
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState, useEffect } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { TextField, Select, MenuItem, FormControl, InputLabel, Stack, Box } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { TITLES } from "../../../../app.text"; //Общие текстовые ресурсы
|
||||||
|
import { P8PConfigDialog } from "../p8p_config_dialog"; //Диалог настройки
|
||||||
|
import { P8P_CC_INITIAL, P8P_CC_SHAPE, P8P_CC_OPERATORS, P8P_CC_FIELD_PRM_SHAPE } from "./common"; //Общие ресурсы условий
|
||||||
|
import { deepCopyObject } from "../../../core/utils"; //Вспомогательные функции
|
||||||
|
import { isConditionOkDisabled } from "./util"; //Вспомогательные ресурсы условий
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
BOX_COND_GROUP: { display: "flex", flexDirection: "row", gap: "10px", paddingBottom: "10px" },
|
||||||
|
BOX_COND_ELEMENT: width => ({ minWidth: width })
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Редактор условия
|
||||||
|
const P8PCCEditor = ({ condition = null, onOk = null, onCancel = null, condFields = [], resFields = [] }) => {
|
||||||
|
//Собственное состояние - признак инициализиации
|
||||||
|
const [init, setInit] = useState(true);
|
||||||
|
|
||||||
|
//Собственное состояние - параметры элемента формы
|
||||||
|
const [state, setState] = useState({});
|
||||||
|
|
||||||
|
//При закрытии редактора с сохранением
|
||||||
|
const handleOk = () => onOk && onOk({ ...state });
|
||||||
|
|
||||||
|
//Собственное состояние - доступность поля "Элемент условия"
|
||||||
|
const [hasCondElement, setHasCondElement] = useState(false);
|
||||||
|
|
||||||
|
//Собственное состояние - доступность поля "Элемент результата"
|
||||||
|
const [hasResElement, setHasResElement] = useState(false);
|
||||||
|
|
||||||
|
//При закрытии редактора с отменой
|
||||||
|
const handleCancel = () => onCancel && onCancel();
|
||||||
|
|
||||||
|
//При изменении параметра элемента
|
||||||
|
const handleChange = e => setState(pv => ({ ...pv, [e.target.name]: e.target.value }));
|
||||||
|
|
||||||
|
//При изменении поля условия
|
||||||
|
const handleCondFieldChange = e => {
|
||||||
|
//Считываем нужный элемент
|
||||||
|
const newCondField = condFields.find(item => item.name === e.target.value);
|
||||||
|
//Обновляем поле (объект)
|
||||||
|
setState(pv => ({ ...pv, [e.target.name]: { name: newCondField.name, value: newCondField.value } }));
|
||||||
|
//Определяем доступность поля "Элемент условия"
|
||||||
|
setHasCondElement(newCondField?.hasElement || false);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При изменении поля результата
|
||||||
|
const handleResFieldChange = e => {
|
||||||
|
//Считываем нужный элемент
|
||||||
|
const newResField = resFields.find(item => item.name === e.target.value);
|
||||||
|
//Обновляем поле результата
|
||||||
|
setState(pv => ({ ...pv, [e.target.name]: { name: newResField.name, value: newResField.value } }));
|
||||||
|
//Определяем доступность поля "Элемент результата"
|
||||||
|
setHasResElement(newResField?.hasElement || false);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При изменении оператора условия
|
||||||
|
const handleOperatorChange = e => {
|
||||||
|
//Считываем нужный элемент
|
||||||
|
const newOperator = P8P_CC_OPERATORS.find(item => item.name === e.target.value);
|
||||||
|
//Обновляем оператор
|
||||||
|
setState(pv => ({ ...pv, [e.target.name]: { ...newOperator } }));
|
||||||
|
};
|
||||||
|
|
||||||
|
//При инициализации условия
|
||||||
|
useEffect(() => {
|
||||||
|
//Если это иницализация
|
||||||
|
if (init) {
|
||||||
|
//Если это открытие условия - берем его параметры, иначе - инициализируем изначальными
|
||||||
|
const initCondition = condition
|
||||||
|
? condition
|
||||||
|
: {
|
||||||
|
...P8P_CC_INITIAL,
|
||||||
|
condField: { name: condFields[0].name, value: condFields[0].value },
|
||||||
|
resField: { name: resFields[0].name, value: resFields[0].value }
|
||||||
|
};
|
||||||
|
//Устанавливаем параметры действия
|
||||||
|
setState(deepCopyObject(initCondition));
|
||||||
|
//Определяем доступность "Элемент условия"
|
||||||
|
setHasCondElement(condFields.find(item => item.name === initCondition.condField.name)?.hasElement || false);
|
||||||
|
//Определяем доступность "Элемент результата"
|
||||||
|
setHasResElement(resFields.find(item => item.name === initCondition.resField.name)?.hasElement || false);
|
||||||
|
//Сбрасываем признак инициализации
|
||||||
|
setInit(false);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [init]);
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!init ? (
|
||||||
|
<P8PConfigDialog
|
||||||
|
title={`${condition ? TITLES.UPDATE : TITLES.INSERT} условия`}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
okDisabled={isConditionOkDisabled(state)}
|
||||||
|
>
|
||||||
|
<Stack direction={"column"} spacing={1}>
|
||||||
|
<Box>
|
||||||
|
<Box sx={STYLES.BOX_COND_GROUP}>
|
||||||
|
<FormControl variant={"standard"} sx={STYLES.BOX_COND_ELEMENT("160px")}>
|
||||||
|
<InputLabel id={"condField-label"}>Поле</InputLabel>
|
||||||
|
<Select
|
||||||
|
name={"condField"}
|
||||||
|
value={state.condField.name}
|
||||||
|
labelId={"condField-label"}
|
||||||
|
label={"Поле"}
|
||||||
|
onChange={handleCondFieldChange}
|
||||||
|
>
|
||||||
|
{condFields.map((item, index) => (
|
||||||
|
<MenuItem value={item.name} key={index}>
|
||||||
|
{item.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl variant={"standard"} sx={STYLES.BOX_COND_ELEMENT("70px")}>
|
||||||
|
<InputLabel id={"condOperator-label"}>Оператор</InputLabel>
|
||||||
|
<Select
|
||||||
|
name={"condOperator"}
|
||||||
|
value={state.condOperator.name}
|
||||||
|
labelId={"condOperator-label"}
|
||||||
|
label={"Оператор"}
|
||||||
|
onChange={handleOperatorChange}
|
||||||
|
>
|
||||||
|
{P8P_CC_OPERATORS.map((item, index) => (
|
||||||
|
<MenuItem value={item.name} key={index}>
|
||||||
|
{item.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<TextField
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={state.condValue}
|
||||||
|
label={"Значение"}
|
||||||
|
name={"condValue"}
|
||||||
|
onChange={handleChange}
|
||||||
|
sx={STYLES.BOX_COND_ELEMENT("160px")}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{hasCondElement ? (
|
||||||
|
<TextField
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
sx={{ pb: 1.25 }}
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={state.condElement}
|
||||||
|
label={"Элемент условия"}
|
||||||
|
name={"condElement"}
|
||||||
|
onChange={handleChange}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<Box sx={STYLES.BOX_COND_GROUP}>
|
||||||
|
<FormControl variant={"standard"} sx={STYLES.BOX_COND_ELEMENT("160px")}>
|
||||||
|
<InputLabel id={"resField-label"}>Поле результата</InputLabel>
|
||||||
|
<Select
|
||||||
|
name={"resField"}
|
||||||
|
value={state.resField.name}
|
||||||
|
labelId={"resField-label"}
|
||||||
|
label={"Поле результата"}
|
||||||
|
onChange={handleResFieldChange}
|
||||||
|
>
|
||||||
|
{resFields.map((item, index) => (
|
||||||
|
<MenuItem value={item.name} key={index}>
|
||||||
|
{item.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
<TextField
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={state.resValue}
|
||||||
|
label={"Результат"}
|
||||||
|
name={"resValue"}
|
||||||
|
onChange={handleChange}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{hasResElement ? (
|
||||||
|
<TextField
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
sx={{ pb: 1.25 }}
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={state.resElement}
|
||||||
|
label={"Элемент результата"}
|
||||||
|
name={"resElement"}
|
||||||
|
onChange={handleChange}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</P8PConfigDialog>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Редактор условия
|
||||||
|
P8PCCEditor.propTypes = {
|
||||||
|
condition: P8P_CC_SHAPE,
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
condFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired,
|
||||||
|
resFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8PCCEditor };
|
||||||
89
app/components/editors/p8p_component_condition/util.js
Normal file
89
app/components/editors/p8p_component_condition/util.js
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Вспомогательные ресурсы компонента "Редактор условия"
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Функции операторов условий
|
||||||
|
const P8P_CC_OPERATOR_FUNC = {
|
||||||
|
equal: (objValue, value) => objValue == value,
|
||||||
|
notEqual: (objValue, value) => objValue != value,
|
||||||
|
lessEqual: (objValue, value) => objValue <= value,
|
||||||
|
less: (objValue, value) => objValue < value,
|
||||||
|
greaterEqual: (objValue, value) => objValue >= value,
|
||||||
|
greater: (objValue, value) => objValue > value,
|
||||||
|
in: (objValue, value) =>
|
||||||
|
value
|
||||||
|
.split(",")
|
||||||
|
.map(item => item.trim())
|
||||||
|
.includes(objValue + "")
|
||||||
|
};
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Загрузка условий в объект
|
||||||
|
const loadConditionsToObject = (obj, conditions) => {
|
||||||
|
//Инициализируем новый объект
|
||||||
|
let newObj = { ...obj };
|
||||||
|
//Инициализируем функцию оператора
|
||||||
|
let operatorFunc;
|
||||||
|
//Если изначальный индикатор загружен
|
||||||
|
if (Object.keys(newObj).length !== 0) {
|
||||||
|
//Обходим условия
|
||||||
|
conditions.map(item => {
|
||||||
|
//Функция оператора
|
||||||
|
operatorFunc = P8P_CC_OPERATOR_FUNC[item.condOperator.value];
|
||||||
|
//Проверяем условие
|
||||||
|
if (operatorFunc && operatorFunc(newObj[item.condField.value], item.condValue)) {
|
||||||
|
//Устанавливаем поле в новое значение
|
||||||
|
newObj[item.resField.value] = item.resValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//Возвращаем новый объект
|
||||||
|
return newObj;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Считывание результатов условия
|
||||||
|
const getConditionsValues = (obj, conditions, element = "") => {
|
||||||
|
//Инициализируем функцию оператора
|
||||||
|
let operatorFunc;
|
||||||
|
//Инициализируем значение поля
|
||||||
|
let condFieldValue = "";
|
||||||
|
//Инициализируем результат
|
||||||
|
let resObject = {};
|
||||||
|
//Обходим условия
|
||||||
|
conditions.map(item => {
|
||||||
|
//Определяем значение поля условия
|
||||||
|
condFieldValue = item.condElement ? obj[item.condElement] : obj[item.condField.value];
|
||||||
|
//Функция оператора
|
||||||
|
operatorFunc = P8P_CC_OPERATOR_FUNC[item.condOperator.value];
|
||||||
|
//Проверяем условие
|
||||||
|
if (operatorFunc && operatorFunc(condFieldValue, item.condValue)) {
|
||||||
|
//Если в условии нет элемента результата или он равен текущему элементу
|
||||||
|
if (!item.resElement || (element && item.resElement === element)) {
|
||||||
|
resObject[item.resField.value] = item.resValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//Возвращаем новый объект
|
||||||
|
return resObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Доступность сохранения настроек условия
|
||||||
|
const isConditionOkDisabled = condition => (!condition.condField.value || !condition.condOperator.value || !condition.resField.value ? true : false);
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { loadConditionsToObject, getConditionsValues, isConditionOkDisabled };
|
||||||
41
app/components/editors/p8p_component_settings.js
Normal file
41
app/components/editors/p8p_component_settings.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Компонент: Дополнительные настройки источников
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Наименование параметра данных компонента, полученных с сервера
|
||||||
|
const P8P_COMPONENT_SETTINGS_RESP_ARGS = {
|
||||||
|
INDICATOR: "XINDICATOR",
|
||||||
|
CHART: "XCHART",
|
||||||
|
TABLE: "XDATA_GRID"
|
||||||
|
};
|
||||||
|
|
||||||
|
//Наименование путей компонента
|
||||||
|
const P8P_COMPONENT_SETTINGS_PATHS = {
|
||||||
|
INDICATOR: "indicator",
|
||||||
|
CHART: "chart",
|
||||||
|
TABLE: "table",
|
||||||
|
FORM: "form"
|
||||||
|
};
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8P_COMPONENT_SETTINGS_RESP_ARGS, P8P_COMPONENT_SETTINGS_PATHS };
|
||||||
99
app/components/editors/p8p_conditions.js
Normal file
99
app/components/editors/p8p_conditions.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Компонент: Условия
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Icon, Button } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { P8PCCEditor } from "./p8p_component_condition/editor"; //Редактор условия
|
||||||
|
import { P8PChipList } from "./p8p_chip_list"; //Дополнительные настройки редактора
|
||||||
|
import { P8P_CCS_INITIAL, P8P_CC_SHAPE, P8P_CC_FIELD_PRM_SHAPE } from "./p8p_component_condition/common"; //Общие ресурсы компонента "Редактор условия"
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Условия
|
||||||
|
const P8PConditions = ({ conditions = P8P_CCS_INITIAL, condFields, resFields, onChange = null } = {}) => {
|
||||||
|
//Собственное состояние - редактор условий
|
||||||
|
const [conditionEditor, setConditionEditor] = useState({ display: false, index: null });
|
||||||
|
|
||||||
|
//При изменении условий
|
||||||
|
const handleConditionsChange = conditions => onChange && onChange(conditions);
|
||||||
|
|
||||||
|
//При добавлении нового условия
|
||||||
|
const handleConditionAdd = () => setConditionEditor({ display: true, index: null });
|
||||||
|
|
||||||
|
//При нажатии на условие
|
||||||
|
const handleConditionClick = i => setConditionEditor({ display: true, index: i });
|
||||||
|
|
||||||
|
//При удалении условия
|
||||||
|
const handleConditionDelete = i => {
|
||||||
|
const newConditions = [...conditions];
|
||||||
|
newConditions.splice(i, 1);
|
||||||
|
handleConditionsChange(newConditions);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При отмене сохранения изменений условия
|
||||||
|
const handleConditionCancel = () => setConditionEditor({ display: false, index: null });
|
||||||
|
|
||||||
|
//При сохранении изменений условия
|
||||||
|
const handleConditionSave = condition => {
|
||||||
|
const newConditions = [...conditions];
|
||||||
|
conditionEditor.index == null ? newConditions.push({ ...condition }) : (newConditions[conditionEditor.index] = { ...condition });
|
||||||
|
handleConditionsChange(newConditions);
|
||||||
|
setConditionEditor({ display: false, index: null });
|
||||||
|
};
|
||||||
|
|
||||||
|
//Определяем структуру условий для отображения
|
||||||
|
const conditionChips = conditions.map(item => {
|
||||||
|
//Собираем текст условия
|
||||||
|
let text = `${item.condField.name} ${item.condOperator.name} ${item.condValue}`;
|
||||||
|
//Считываем поле результата
|
||||||
|
let resField = resFields.find(field => field.name === item.resField.name);
|
||||||
|
//Формируем структуру для отображения карточки действия
|
||||||
|
return { text: text, title: text, icon: resField?.icon, iconTitle: resField?.name };
|
||||||
|
});
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{conditionEditor.display && (
|
||||||
|
<P8PCCEditor
|
||||||
|
condition={conditionEditor.index !== null ? { ...conditions[conditionEditor.index] } : null}
|
||||||
|
onCancel={handleConditionCancel}
|
||||||
|
onOk={handleConditionSave}
|
||||||
|
condFields={condFields}
|
||||||
|
resFields={resFields}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<P8PChipList items={conditionChips} onClick={handleConditionClick} onDelete={handleConditionDelete} />
|
||||||
|
<Button startIcon={<Icon>add</Icon>} onClick={handleConditionAdd}>
|
||||||
|
Добавить условие
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - условия
|
||||||
|
P8PConditions.propTypes = {
|
||||||
|
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||||
|
condFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired,
|
||||||
|
resFields: PropTypes.arrayOf(P8P_CC_FIELD_PRM_SHAPE).isRequired,
|
||||||
|
onChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8PConditions };
|
||||||
@ -9,17 +9,17 @@
|
|||||||
|
|
||||||
import React from "react"; //Классы React
|
import React from "react"; //Классы React
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { P8PDialog } from "../p8p_dialog"; //Типовой диалог
|
import { P8PDialog, P8P_DIALOG_WIDTH } from "../p8p_dialog"; //Типовой диалог
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Диалог настройки
|
//Диалог настройки
|
||||||
const P8PConfigDialog = ({ title, children, onOk, onCancel }) => {
|
const P8PConfigDialog = ({ title, children, width, onOk, onCancel, okDisabled = false }) => {
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<P8PDialog title={title} onOk={onOk} onCancel={onCancel}>
|
<P8PDialog title={title} onOk={onOk} onCancel={onCancel} width={width} okDisabled={okDisabled}>
|
||||||
{children}
|
{children}
|
||||||
</P8PDialog>
|
</P8PDialog>
|
||||||
);
|
);
|
||||||
@ -29,8 +29,10 @@ const P8PConfigDialog = ({ title, children, onOk, onCancel }) => {
|
|||||||
P8PConfigDialog.propTypes = {
|
P8PConfigDialog.propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
||||||
|
width: PropTypes.oneOf(Object.values(P8P_DIALOG_WIDTH)),
|
||||||
onOk: PropTypes.func,
|
onOk: PropTypes.func,
|
||||||
onCancel: PropTypes.func
|
onCancel: PropTypes.func,
|
||||||
|
okDisabled: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
|
|||||||
@ -9,11 +9,11 @@
|
|||||||
|
|
||||||
import React, { useState } from "react"; //Классы React
|
import React, { useState } from "react"; //Классы React
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { Stack, IconButton, Icon, Typography, Chip, Button, Card, CardContent, CardActions, CardActionArea } from "@mui/material"; //Интерфейсные элементы
|
import { Stack, IconButton, Icon, Typography, Button, Card, CardContent, CardActions, CardActionArea } from "@mui/material"; //Интерфейсные элементы
|
||||||
import { BUTTONS, TEXTS } from "../../../app.text"; //Общие текстовые ресурсы
|
import { BUTTONS, TEXTS } from "../../../app.text"; //Общие текстовые ресурсы
|
||||||
import { STYLES as COMMON_STYLES } from "./p8p_editors_common"; //Общие ресурсы редаторов
|
|
||||||
import { P8P_DATA_SOURCE_SHAPE, P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_TYPE_NAME, P8P_DATA_SOURCE_INITIAL } from "./p8p_data_source_common"; //Общие ресурсы компонента "Источник данных"
|
import { P8P_DATA_SOURCE_SHAPE, P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_TYPE_NAME, P8P_DATA_SOURCE_INITIAL } from "./p8p_data_source_common"; //Общие ресурсы компонента "Источник данных"
|
||||||
import { P8PDataSourceConfigDialog } from "./p8p_data_source_config_dialog"; //Диалог настройки источника данных
|
import { P8PDataSourceConfigDialog } from "./p8p_data_source_config_dialog"; //Диалог настройки источника данных
|
||||||
|
import { P8PChipList } from "./p8p_chip_list"; //Дополнительные настройки редактора
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
@ -45,17 +45,9 @@ const P8PDataSource = ({ dataSource = null, valueProviders = {}, onChange = null
|
|||||||
//Расчет флага "настроенности"
|
//Расчет флага "настроенности"
|
||||||
const configured = dataSource?.type ? true : false;
|
const configured = dataSource?.type ? true : false;
|
||||||
|
|
||||||
//Список аргументов
|
//Структура параметров для отображения
|
||||||
const args =
|
const argChips =
|
||||||
configured &&
|
configured && dataSource.arguments.map(argument => ({ text: `:${argument.name} = ${argument.valueSource || argument.value || "NULL"}` }));
|
||||||
dataSource.arguments.map((argument, i) => (
|
|
||||||
<Chip
|
|
||||||
key={i}
|
|
||||||
label={`:${argument.name} = ${argument.valueSource || argument.value || "NULL"}`}
|
|
||||||
variant={"outlined"}
|
|
||||||
sx={COMMON_STYLES.CHIP(true)}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
@ -79,7 +71,7 @@ const P8PDataSource = ({ dataSource = null, valueProviders = {}, onChange = null
|
|||||||
{P8P_DATA_SOURCE_TYPE_NAME[dataSource.type] || TEXTS.UNKNOWN_SOURCE_TYPE}
|
{P8P_DATA_SOURCE_TYPE_NAME[dataSource.type] || TEXTS.UNKNOWN_SOURCE_TYPE}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack direction={"column"} spacing={1} pt={2}>
|
<Stack direction={"column"} spacing={1} pt={2}>
|
||||||
{args}
|
<P8PChipList items={argChips} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardActionArea>
|
</CardActionArea>
|
||||||
|
|||||||
@ -67,6 +67,7 @@ const P8P_DATA_SOURCE_SHAPE = PropTypes.shape({
|
|||||||
const P8P_DATA_SOURCE_INITIAL = {
|
const P8P_DATA_SOURCE_INITIAL = {
|
||||||
type: "",
|
type: "",
|
||||||
userProc: "",
|
userProc: "",
|
||||||
|
query: "",
|
||||||
stored: "",
|
stored: "",
|
||||||
respArg: "",
|
respArg: "",
|
||||||
arguments: []
|
arguments: []
|
||||||
|
|||||||
@ -15,6 +15,8 @@ import { TITLES, CAPTIONS } from "../../../app.text"; //Общие тексто
|
|||||||
import { P8PConfigDialog } from "./p8p_config_dialog"; //Типовой диалог настройки
|
import { P8PConfigDialog } from "./p8p_config_dialog"; //Типовой диалог настройки
|
||||||
import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_SHAPE, P8P_DATA_SOURCE_ARGUMENT_INITIAL, P8P_DATA_SOURCE_INITIAL } from "./p8p_data_source_common"; //Общие ресурсы компонента "Источник данных"
|
import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_SHAPE, P8P_DATA_SOURCE_ARGUMENT_INITIAL, P8P_DATA_SOURCE_INITIAL } from "./p8p_data_source_common"; //Общие ресурсы компонента "Источник данных"
|
||||||
import { useUserProcDesc } from "./p8p_data_source_hooks"; //Хуки источников данных
|
import { useUserProcDesc } from "./p8p_data_source_hooks"; //Хуки источников данных
|
||||||
|
import { P8PDataSourceQuerySelector } from "./p8p_data_source_query_selector"; //Диалог выбора записи редактора запросов
|
||||||
|
import { hasValue } from "../../core/utils"; //Вспомогательные функции
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
@ -34,6 +36,12 @@ const P8PDataSourceConfigDialog = ({ dataSource = null, valueProviders = {}, onO
|
|||||||
//Описание выбранной пользовательской процедуры
|
//Описание выбранной пользовательской процедуры
|
||||||
const [userProcDesc] = useUserProcDesc({ code: state.userProc, refresh: refresh.userProcDesc });
|
const [userProcDesc] = useUserProcDesc({ code: state.userProc, refresh: refresh.userProcDesc });
|
||||||
|
|
||||||
|
//Собственное состояние - отображение диалога выбора запроса
|
||||||
|
const [openQuerySelector, setOpenQuerySelector] = useState(false);
|
||||||
|
|
||||||
|
//Собственное состояние - доступность полей выбора источника
|
||||||
|
const [disabledFields, setDisabledFields] = useState({ query: false, userProc: false });
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
//Подключение к контексту приложения
|
||||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
@ -86,6 +94,21 @@ const P8PDataSourceConfigDialog = ({ dataSource = null, valueProviders = {}, onO
|
|||||||
//При вводе значения аргумента
|
//При вводе значения аргумента
|
||||||
const handleArgumentChange = (index, value) => setArgumentValueSource(index, value, "");
|
const handleArgumentChange = (index, value) => setArgumentValueSource(index, value, "");
|
||||||
|
|
||||||
|
//Открытие диалога выбора запроса
|
||||||
|
const handleOpenQuerySelector = () => setOpenQuerySelector(true);
|
||||||
|
|
||||||
|
//Закрытие диалога выбора запроса
|
||||||
|
const handleCancelQuerySelector = () => setOpenQuerySelector(false);
|
||||||
|
|
||||||
|
//При нажатии на очистку мнемокода запроса
|
||||||
|
const handleQueryClearClick = () => setState({ ...P8P_DATA_SOURCE_INITIAL });
|
||||||
|
|
||||||
|
//При нажатии на выбор запроса в качестве источника данных
|
||||||
|
const handleQuerySelectClick = query => {
|
||||||
|
setState(pv => ({ ...pv, type: P8P_DATA_SOURCE_TYPE.QUERY, query: query.code }));
|
||||||
|
handleCancelQuerySelector();
|
||||||
|
};
|
||||||
|
|
||||||
//При изменении описания пользовательской процедуры
|
//При изменении описания пользовательской процедуры
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (userProcDesc)
|
if (userProcDesc)
|
||||||
@ -97,8 +120,22 @@ const P8PDataSourceConfigDialog = ({ dataSource = null, valueProviders = {}, onO
|
|||||||
}));
|
}));
|
||||||
}, [userProcDesc]);
|
}, [userProcDesc]);
|
||||||
|
|
||||||
|
//При изменении источника
|
||||||
|
useEffect(() => {
|
||||||
|
/*
|
||||||
|
Если выбран запрос - блокируем выбор процедуры
|
||||||
|
Если выбрана процедура - блокируем выбор запроса
|
||||||
|
Ничего не выбрано - доступны все варианты
|
||||||
|
*/
|
||||||
|
hasValue(state.query)
|
||||||
|
? setDisabledFields({ query: false, userProc: true })
|
||||||
|
: hasValue(state.userProc)
|
||||||
|
? setDisabledFields({ query: true, userProc: false })
|
||||||
|
: setDisabledFields({ query: false, userProc: false });
|
||||||
|
}, [state.query, state.userProc]);
|
||||||
|
|
||||||
//Список значений
|
//Список значений
|
||||||
const values = Object.keys(valueProviders).reduce((res, key) => [...res, ...Object.keys(valueProviders[key])], []);
|
const values = Object.keys(valueProviders);
|
||||||
|
|
||||||
//Наличие значений
|
//Наличие значений
|
||||||
const isValues = values && values.length > 0 ? true : false;
|
const isValues = values && values.length > 0 ? true : false;
|
||||||
@ -114,59 +151,90 @@ const P8PDataSourceConfigDialog = ({ dataSource = null, valueProviders = {}, onO
|
|||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//Доступность сохранения настройки источника данных
|
||||||
|
const okDisabled = !state.query && !state.userProc ? true : false;
|
||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<P8PConfigDialog title={TITLES.DATA_SOURCE_CONFIG} onOk={handleOk} onCancel={handleCancel}>
|
<>
|
||||||
<Stack direction={"column"} spacing={1}>
|
{openQuerySelector ? (
|
||||||
{valueProvidersMenu}
|
<P8PDataSourceQuerySelector current={state.query} onSelect={handleQuerySelectClick} onCancel={handleCancelQuerySelector} />
|
||||||
<TextField
|
) : null}
|
||||||
type={"text"}
|
<P8PConfigDialog title={TITLES.DATA_SOURCE_CONFIG} onOk={handleOk} onCancel={handleCancel} okDisabled={okDisabled}>
|
||||||
variant={"standard"}
|
<Stack direction={"column"} spacing={1}>
|
||||||
value={state.userProc}
|
{valueProvidersMenu}
|
||||||
label={CAPTIONS.USER_PROC}
|
{/* ДОРАБАТЫВАТЬ ПОСЛЕ РЕАЛИЗАЦИИ ЗАПРОСОВ */}
|
||||||
InputLabelProps={{ shrink: true }}
|
<TextField
|
||||||
InputProps={{
|
type={"text"}
|
||||||
readOnly: true,
|
variant={"standard"}
|
||||||
endAdornment: (
|
value={state.query}
|
||||||
<InputAdornment position="end">
|
label={CAPTIONS.QUERY}
|
||||||
<IconButton onClick={handleUserProcClearClick}>
|
InputLabelProps={{ shrink: true }}
|
||||||
<Icon>clear</Icon>
|
InputProps={{
|
||||||
</IconButton>
|
readOnly: true,
|
||||||
<IconButton onClick={handleUserProcSelectClick}>
|
endAdornment: (
|
||||||
<Icon>list</Icon>
|
<InputAdornment position="end">
|
||||||
</IconButton>
|
<IconButton onClick={handleQueryClearClick} disabled={disabledFields.query}>
|
||||||
</InputAdornment>
|
<Icon>clear</Icon>
|
||||||
)
|
</IconButton>
|
||||||
}}
|
<IconButton onClick={handleOpenQuerySelector} disabled={disabledFields.query}>
|
||||||
/>
|
<Icon>list</Icon>
|
||||||
{Array.isArray(state?.arguments) &&
|
</IconButton>
|
||||||
state.arguments.map((argument, i) => (
|
</InputAdornment>
|
||||||
<TextField
|
)
|
||||||
key={i}
|
}}
|
||||||
type={"text"}
|
disabled={disabledFields.query}
|
||||||
variant={"standard"}
|
/>
|
||||||
value={argument.value || argument.valueSource}
|
<TextField
|
||||||
label={argument.caption}
|
type={"text"}
|
||||||
onChange={e => handleArgumentChange(i, e.target.value)}
|
variant={"standard"}
|
||||||
InputLabelProps={{ shrink: true }}
|
value={state.userProc}
|
||||||
InputProps={{
|
label={CAPTIONS.USER_PROC}
|
||||||
endAdornment: (
|
InputLabelProps={{ shrink: true }}
|
||||||
<InputAdornment position="end">
|
InputProps={{
|
||||||
<IconButton onClick={() => handleArgumentClearClick(i)}>
|
readOnly: true,
|
||||||
<Icon>clear</Icon>
|
endAdornment: (
|
||||||
</IconButton>
|
<InputAdornment position="end">
|
||||||
{isValues && (
|
<IconButton onClick={handleUserProcClearClick} disabled={disabledFields.userProc}>
|
||||||
<IconButton id={i} onClick={handleArgumentLinkMenuClick}>
|
<Icon>clear</Icon>
|
||||||
<Icon>settings_ethernet</Icon>
|
</IconButton>
|
||||||
|
<IconButton onClick={handleUserProcSelectClick} disabled={disabledFields.userProc}>
|
||||||
|
<Icon>list</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
disabled={disabledFields.userProc}
|
||||||
|
/>
|
||||||
|
{Array.isArray(state?.arguments) &&
|
||||||
|
state.arguments.map((argument, i) => (
|
||||||
|
<TextField
|
||||||
|
key={i}
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={argument.value || argument.valueSource}
|
||||||
|
label={argument.caption}
|
||||||
|
onChange={e => handleArgumentChange(i, e.target.value)}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={() => handleArgumentClearClick(i)}>
|
||||||
|
<Icon>clear</Icon>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
{isValues && (
|
||||||
</InputAdornment>
|
<IconButton id={i} onClick={handleArgumentLinkMenuClick}>
|
||||||
)
|
<Icon>settings_ethernet</Icon>
|
||||||
}}
|
</IconButton>
|
||||||
/>
|
)}
|
||||||
))}
|
</InputAdornment>
|
||||||
</Stack>
|
)
|
||||||
</P8PConfigDialog>
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</P8PConfigDialog>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,9 @@ import { ERRORS } from "../../../app.text"; //Общие текстовые ре
|
|||||||
import { formatErrorMessage } from "../../core/utils"; //Общие вспомогательные функции
|
import { formatErrorMessage } from "../../core/utils"; //Общие вспомогательные функции
|
||||||
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE } from "./p8p_data_source_common"; //Общие ресурсы источника данных
|
import { P8P_DATA_SOURCE_TYPE, P8P_DATA_SOURCE_ARGUMENT_DATA_TYPE } from "./p8p_data_source_common"; //Общие ресурсы источника данных
|
||||||
|
import { getConditionsValues } from "./p8p_component_condition/util"; //Вспомогательные ресурсы условий
|
||||||
|
import { getHandlersByActions } from "./p8p_component_action/util"; //Вспомогательные ресурсы действий
|
||||||
|
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
@ -58,7 +61,7 @@ const useUserProcDesc = ({ code, refresh }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//Получение данных из источника
|
//Получение данных из источника
|
||||||
const useDataSource = ({ dataSource, values }) => {
|
const useDataSource = ({ dataSource, values, componentRespArg = "" }) => {
|
||||||
//Контроллер для прерывания запросов
|
//Контроллер для прерывания запросов
|
||||||
const abortController = useRef(null);
|
const abortController = useRef(null);
|
||||||
|
|
||||||
@ -69,14 +72,25 @@ const useDataSource = ({ dataSource, values }) => {
|
|||||||
const [isLoading, setLoading] = useState(false);
|
const [isLoading, setLoading] = useState(false);
|
||||||
|
|
||||||
//Собственное состояние - данные
|
//Собственное состояние - данные
|
||||||
const [data, setData] = useState({ init: false });
|
const [data, setData] = useState({ componentData: {}, init: false });
|
||||||
|
|
||||||
//Собственное состояние - ошибка получения данных
|
//Собственное состояние - ошибка получения данных
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
//Собственное состояние - наличие настроек
|
||||||
|
const [haveConfing, setHaveConfig] = useState(false);
|
||||||
|
|
||||||
|
//Собственное состояние - наличие данных
|
||||||
|
const [haveData, setHaveData] = useState(false);
|
||||||
|
|
||||||
//Подключение к контексту взаимодействия с сервером
|
//Подключение к контексту взаимодействия с сервером
|
||||||
const { executeStored } = useContext(BackEndСtx);
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
|
//При необходимости обновление информации о наличии данных
|
||||||
|
useEffect(() => {
|
||||||
|
setHaveData(data.init === true && !error ? true : false);
|
||||||
|
}, [data.init, error]);
|
||||||
|
|
||||||
//При необходимости обновить данные
|
//При необходимости обновить данные
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//Загрузка данных с сервера
|
//Загрузка данных с сервера
|
||||||
@ -94,11 +108,11 @@ const useDataSource = ({ dataSource, values }) => {
|
|||||||
showErrorMessage: false
|
showErrorMessage: false
|
||||||
});
|
});
|
||||||
setError(null);
|
setError(null);
|
||||||
setData({ ...data, init: true });
|
setData({ componentData: { ...data[componentRespArg] }, init: true });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.message !== client.ERR_ABORTED) {
|
if (e.message !== client.ERR_ABORTED) {
|
||||||
setError(formatErrorMessage(e.message).text);
|
setError(formatErrorMessage(e.message).text);
|
||||||
setData({ init: false });
|
setData({ componentData: {}, init: false });
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@ -106,12 +120,15 @@ const useDataSource = ({ dataSource, values }) => {
|
|||||||
};
|
};
|
||||||
if (state.reqSet) {
|
if (state.reqSet) {
|
||||||
if (state.stored) loadData();
|
if (state.stored) loadData();
|
||||||
} else setData({ init: false });
|
} else setData({ componentData: {}, init: false });
|
||||||
return () => abortController.current?.abort?.();
|
return () => abortController.current?.abort?.();
|
||||||
}, [state.stored, state.storedArgs, state.respArg, state.reqSet, executeStored]);
|
}, [state.stored, state.storedArgs, state.respArg, state.reqSet, executeStored, componentRespArg]);
|
||||||
|
|
||||||
//При изменении свойств
|
//При изменении свойств
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
//Устанавливаем признак наличия настроек
|
||||||
|
setHaveConfig(dataSource?.stored ? true : false);
|
||||||
|
//Устанавливаем параметры исполнения
|
||||||
setState(pv => {
|
setState(pv => {
|
||||||
if (dataSource?.type == P8P_DATA_SOURCE_TYPE.USER_PROC) {
|
if (dataSource?.type == P8P_DATA_SOURCE_TYPE.USER_PROC) {
|
||||||
const { stored, respArg } = dataSource;
|
const { stored, respArg } = dataSource;
|
||||||
@ -132,7 +149,7 @@ const useDataSource = ({ dataSource, values }) => {
|
|||||||
if (pv.stored != stored || pv.respArg != respArg || JSON.stringify(pv.storedArgs) != JSON.stringify(storedArgs)) {
|
if (pv.stored != stored || pv.respArg != respArg || JSON.stringify(pv.storedArgs) != JSON.stringify(storedArgs)) {
|
||||||
if (!reqSet) {
|
if (!reqSet) {
|
||||||
setError(ERRORS.DATA_SOURCE_NO_REQ_ARGS);
|
setError(ERRORS.DATA_SOURCE_NO_REQ_ARGS);
|
||||||
setData({ init: false });
|
setData({ componentData: {}, init: false });
|
||||||
}
|
}
|
||||||
return { stored, respArg, storedArgs, reqSet };
|
return { stored, respArg, storedArgs, reqSet };
|
||||||
} else return pv;
|
} else return pv;
|
||||||
@ -141,11 +158,72 @@ const useDataSource = ({ dataSource, values }) => {
|
|||||||
}, [dataSource, values]);
|
}, [dataSource, values]);
|
||||||
|
|
||||||
//Возвращаем интерфейс хука
|
//Возвращаем интерфейс хука
|
||||||
return [data, error, isLoading];
|
return [data.componentData, error, haveConfing, haveData, isLoading];
|
||||||
|
};
|
||||||
|
|
||||||
|
//Изменение данных компонента с учетом условий
|
||||||
|
const useConditions = ({ componentData, conditions }) => {
|
||||||
|
//Собственное состояние - текущие условия компонента
|
||||||
|
const [currentConditions, setCurrentConditions] = useState([]);
|
||||||
|
|
||||||
|
//Собственное состояние - данные
|
||||||
|
const [data, setData] = useState();
|
||||||
|
|
||||||
|
//При обновлении условий компонента
|
||||||
|
useEffect(() => {
|
||||||
|
//Если условия изменились
|
||||||
|
if (JSON.stringify(currentConditions) != JSON.stringify(conditions)) {
|
||||||
|
//Устанавливаем новые условия
|
||||||
|
setCurrentConditions(conditions);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [conditions]);
|
||||||
|
|
||||||
|
//При обновлении данных или условий компонента
|
||||||
|
useEffect(() => {
|
||||||
|
//Если есть текущие условия
|
||||||
|
if (currentConditions.length !== 0) {
|
||||||
|
//Устанавливаем данные с учетом условий
|
||||||
|
setData({ ...componentData, ...getConditionsValues(componentData, currentConditions) });
|
||||||
|
} else {
|
||||||
|
//Оставляем данные компонента
|
||||||
|
setData({ ...componentData });
|
||||||
|
}
|
||||||
|
}, [currentConditions, componentData]);
|
||||||
|
|
||||||
|
//Возвращаем интерфейс хука
|
||||||
|
return [data];
|
||||||
|
};
|
||||||
|
|
||||||
|
//Получение обработчиков компонента
|
||||||
|
const useComponentHandlers = ({ actions = [], onValuesChange = null, getCustomTypeValue = null }) => {
|
||||||
|
//Контроллер для текущего состояния действий
|
||||||
|
const currentActions = useRef([]);
|
||||||
|
|
||||||
|
//Собственное состояние - обработчики компонента
|
||||||
|
const [handlers, setHandlers] = useState({});
|
||||||
|
|
||||||
|
//Подключение к контексту приложения
|
||||||
|
const { configUrlBase, pOnlineShowTab, pOnlineShowUnit } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
|
//При необходимости обновления информации об обработчиках
|
||||||
|
useEffect(() => {
|
||||||
|
//Если изменились действия или параметры
|
||||||
|
if (JSON.stringify(currentActions.current) != JSON.stringify(actions)) {
|
||||||
|
//Считываем обработчики компонента
|
||||||
|
setHandlers(getHandlersByActions(actions, pOnlineShowUnit, configUrlBase, pOnlineShowTab, onValuesChange, getCustomTypeValue));
|
||||||
|
//Устанавливаем контроллер текущих действий
|
||||||
|
currentActions.current = actions;
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [actions]);
|
||||||
|
|
||||||
|
//Возвращаем интерфейс хука
|
||||||
|
return [handlers];
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
export { useUserProcDesc, useDataSource };
|
export { useUserProcDesc, useDataSource, useConditions, useComponentHandlers };
|
||||||
|
|||||||
75
app/components/editors/p8p_data_source_query_selector.js
Normal file
75
app/components/editors/p8p_data_source_query_selector.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редакторы панелей
|
||||||
|
Компонент: Диалог выбора записи редактора запросов
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Stack, List, ListItem, ListItemButton, ListItemText, Typography } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { P8PConfigDialog } from "./p8p_config_dialog"; //Типовой диалог настройки
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
LIST: { height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Диалог выбора записи редактора запросов
|
||||||
|
const P8PDataSourceQuerySelector = ({ current = null, onSelect, onCancel }) => {
|
||||||
|
//!!! НА РЕАЛИЗАЦИИ
|
||||||
|
const test = [{ code: "TestCode", name: "TestName" }];
|
||||||
|
//При выборе элемента списка
|
||||||
|
const handleSelectClick = query => {
|
||||||
|
onSelect && onSelect(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<P8PConfigDialog title={"Запросы"} onCancel={onCancel}>
|
||||||
|
<List sx={STYLES.LIST}>
|
||||||
|
{test.map((query, i) => {
|
||||||
|
const selected = query.code === current;
|
||||||
|
return (
|
||||||
|
<ListItem key={i}>
|
||||||
|
<ListItemButton onClick={() => handleSelectClick(query)} selected={selected}>
|
||||||
|
<ListItemText
|
||||||
|
primary={query.code}
|
||||||
|
secondaryTypographyProps={{ component: "div" }}
|
||||||
|
secondary={
|
||||||
|
<Stack direction={"column"}>
|
||||||
|
<Typography variant={"caption"}>{`${query.code}, ${query.name}`}</Typography>
|
||||||
|
</Stack>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</P8PConfigDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Диалог выбора записи редактора запросов
|
||||||
|
P8PDataSourceQuerySelector.propTypes = {
|
||||||
|
current: PropTypes.string,
|
||||||
|
onSelect: PropTypes.func.isRequired,
|
||||||
|
onCancel: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8PDataSourceQuerySelector };
|
||||||
@ -17,7 +17,7 @@ import { BUTTONS } from "../../../app.text"; //Общие текстовые р
|
|||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Контейнер редактора
|
//Контейнер редактора
|
||||||
const P8PEditorBox = ({ title, children, onSave }) => {
|
const P8PEditorBox = ({ title, children, onSave, allowClose = true }) => {
|
||||||
//При нажатии на "Сохранить"
|
//При нажатии на "Сохранить"
|
||||||
const handleSaveClick = (closeEditor = false) => onSave && onSave(closeEditor);
|
const handleSaveClick = (closeEditor = false) => onSave && onSave(closeEditor);
|
||||||
|
|
||||||
@ -36,9 +36,11 @@ const P8PEditorBox = ({ title, children, onSave }) => {
|
|||||||
<IconButton onClick={() => handleSaveClick(false)} title={BUTTONS.APPLY}>
|
<IconButton onClick={() => handleSaveClick(false)} title={BUTTONS.APPLY}>
|
||||||
<Icon>done</Icon>
|
<Icon>done</Icon>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton onClick={() => handleSaveClick(true)} title={BUTTONS.SAVE}>
|
{allowClose ? (
|
||||||
<Icon>done_all</Icon>
|
<IconButton onClick={() => handleSaveClick(true)} title={BUTTONS.SAVE}>
|
||||||
</IconButton>
|
<Icon>done_all</Icon>
|
||||||
|
</IconButton>
|
||||||
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@ -49,7 +51,8 @@ const P8PEditorBox = ({ title, children, onSave }) => {
|
|||||||
P8PEditorBox.propTypes = {
|
P8PEditorBox.propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
||||||
onSave: PropTypes.func
|
onSave: PropTypes.func,
|
||||||
|
allowClose: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import React from "react"; //Классы React
|
import React from "react"; //Классы React
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { IconButton, Icon, Stack } from "@mui/material"; //Интерфейсные компоненты MUI
|
import { IconButton, Icon, Stack, Grid } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -20,7 +20,8 @@ const P8P_EDITOR_TOOL_BAR_ITEM_SHAPE = PropTypes.shape({
|
|||||||
icon: PropTypes.string.isRequired,
|
icon: PropTypes.string.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
onClick: PropTypes.func.isRequired
|
onClick: PropTypes.func.isRequired,
|
||||||
|
customRenderer: PropTypes.func
|
||||||
});
|
});
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
@ -32,11 +33,19 @@ const P8PEditorToolBar = ({ items = [] }) => {
|
|||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<Stack direction={"row"} p={1}>
|
<Stack direction={"row"} p={1}>
|
||||||
{items.map((item, i) => (
|
<Grid container columns={items.length}>
|
||||||
<IconButton key={i} onClick={item.onClick} title={item.title} disabled={item?.disabled === true}>
|
{items.map((item, i) => (
|
||||||
<Icon>{item.icon}</Icon>
|
<Grid item size={{ xs: 2, sm: 4, md: 4 }} key={i}>
|
||||||
</IconButton>
|
{item.customRenderer ? (
|
||||||
))}
|
item.customRenderer({ icon: item.icon, title: item.title, disabled: item?.disabled === true, onClick: item.onClick })
|
||||||
|
) : (
|
||||||
|
<IconButton onClick={item.onClick} title={item.title} disabled={item?.disabled === true}>
|
||||||
|
<Icon>{item.icon}</Icon>
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -23,8 +23,17 @@ const STYLES = {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Проверка корректности наименования элемента формы
|
||||||
|
const isElementNameCorrect = elementName => {
|
||||||
|
return new RegExp(/^[\w\d_]*$/).test(elementName);
|
||||||
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
export { STYLES };
|
export { STYLES, isElementNameCorrect };
|
||||||
|
|||||||
@ -253,13 +253,13 @@ const P8PAppInlineInfo = props => buildVariantInlineMessage(props, P8P_APP_MESSA
|
|||||||
//Диалог подсказки
|
//Диалог подсказки
|
||||||
const P8PHintDialog = ({ title, hint, onOk }) => {
|
const P8PHintDialog = ({ title, hint, onOk }) => {
|
||||||
return (
|
return (
|
||||||
<Dialog open={true} onClose={() => (onOk ? onOk() : null)}>
|
<Dialog open={true} onClose={e => (onOk ? onOk(e) : null)}>
|
||||||
<DialogTitle>{title}</DialogTitle>
|
<DialogTitle>{title}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<div dangerouslySetInnerHTML={{ __html: hint }}></div>
|
<div dangerouslySetInnerHTML={{ __html: hint }}></div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => (onOk ? onOk() : null)}>{BUTTONS.OK}</Button>
|
<Button onClick={e => (onOk ? onOk(e) : null)}>{BUTTONS.OK}</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -61,7 +61,7 @@ const STYLES = {
|
|||||||
...(clickable
|
...(clickable
|
||||||
? {
|
? {
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
"&:hover": { backgroundColor: APP_COLORS.HOVER.color },
|
"&:hover": { filter: "brightness(0.92) !important" },
|
||||||
"&:active": { backgroundColor: APP_COLORS.ACTIVE.color }
|
"&:active": { backgroundColor: APP_COLORS.ACTIVE.color }
|
||||||
}
|
}
|
||||||
: {})
|
: {})
|
||||||
@ -69,7 +69,21 @@ const STYLES = {
|
|||||||
ICON: (state, userColor) => ({ fontSize: "50px", ...getColor(state, userColor) }),
|
ICON: (state, userColor) => ({ fontSize: "50px", ...getColor(state, userColor) }),
|
||||||
HINT_ICON: (state, userColor) => ({ fontSize: "1rem", ...getColor(state, userColor) }),
|
HINT_ICON: (state, userColor) => ({ fontSize: "1rem", ...getColor(state, userColor) }),
|
||||||
VALUE_CAPTION_STACK: { containerType: "inline-size", width: "100%", overflow: "hidden" },
|
VALUE_CAPTION_STACK: { containerType: "inline-size", width: "100%", overflow: "hidden" },
|
||||||
CAPTION_TYPOGRAPHY: { width: "99cqw" }
|
CAPTION_TYPOGRAPHY: clickable => ({
|
||||||
|
width: "99cqw",
|
||||||
|
...(clickable
|
||||||
|
? {
|
||||||
|
cursor: "pointer"
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
}),
|
||||||
|
VALUE_TYPOGRAPHY: clickable => ({
|
||||||
|
...(clickable
|
||||||
|
? {
|
||||||
|
cursor: "pointer"
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
//-----------------------
|
//-----------------------
|
||||||
@ -98,6 +112,8 @@ const P8PIndicator = ({
|
|||||||
variant = P8P_INDICATOR_VARIANT.ELEVATION,
|
variant = P8P_INDICATOR_VARIANT.ELEVATION,
|
||||||
hint = null,
|
hint = null,
|
||||||
onClick = null,
|
onClick = null,
|
||||||
|
onValueClick = null,
|
||||||
|
onCaptionClick = null,
|
||||||
backgroundColor = null,
|
backgroundColor = null,
|
||||||
color = null
|
color = null
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
@ -114,14 +130,39 @@ const P8PIndicator = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
//При нажатии на кнопку закрытия подсказки
|
//При нажатии на кнопку закрытия подсказки
|
||||||
const handleHintClose = () => setShowHint(false);
|
const handleHintClose = e => {
|
||||||
|
setShowHint(false);
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на заголовок
|
||||||
|
const handleCaptionClick = event => {
|
||||||
|
//Если есть функция нажатия на заголовок
|
||||||
|
onCaptionClick && onCaptionClick(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на значение
|
||||||
|
const handleValueClick = event => {
|
||||||
|
//Если есть функция нажатия на заголовок
|
||||||
|
onValueClick && onValueClick(event);
|
||||||
|
};
|
||||||
|
|
||||||
//Представление текста значения индикатора
|
//Представление текста значения индикатора
|
||||||
const valueTextView = <Typography variant={"h4"}>{[undefined, null, ""].includes(value) ? TEXTS.NO_DATA_FOUND_SHORT : value}</Typography>;
|
const valueTextView = (
|
||||||
|
<Typography variant={"h4"} sx={STYLES.VALUE_TYPOGRAPHY(onValueClick ? true : false)} onClick={handleValueClick}>
|
||||||
|
{[undefined, null, ""].includes(value) ? TEXTS.NO_DATA_FOUND_SHORT : value}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
|
||||||
//Представление текста подписи индикатора
|
//Представление текста подписи индикатора
|
||||||
const captionView = (
|
const captionView = (
|
||||||
<Typography align={"left"} noWrap={true} sx={STYLES.CAPTION_TYPOGRAPHY} title={caption}>
|
<Typography
|
||||||
|
align={"left"}
|
||||||
|
noWrap={true}
|
||||||
|
sx={STYLES.CAPTION_TYPOGRAPHY(onCaptionClick ? true : false)}
|
||||||
|
title={caption}
|
||||||
|
onClick={handleCaptionClick}
|
||||||
|
>
|
||||||
{caption}
|
{caption}
|
||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
@ -175,6 +216,8 @@ P8PIndicator.propTypes = {
|
|||||||
variant: PropTypes.oneOf(Object.values(P8P_INDICATOR_VARIANT)),
|
variant: PropTypes.oneOf(Object.values(P8P_INDICATOR_VARIANT)),
|
||||||
hint: PropTypes.string,
|
hint: PropTypes.string,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
|
onValueClick: PropTypes.func,
|
||||||
|
onCaptionClick: PropTypes.func,
|
||||||
backgroundColor: PropTypes.string,
|
backgroundColor: PropTypes.string,
|
||||||
color: PropTypes.string
|
color: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|||||||
@ -195,6 +195,9 @@ const genGUID = () =>
|
|||||||
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
|
(c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
//Формироваеие уникального идентификатора по текущему времени
|
||||||
|
const genUID = prefix => (prefix ? `${prefix}_${Date.now()}` : `${Date.now()}`);
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
@ -211,5 +214,6 @@ export {
|
|||||||
formatDateJSONDateOnly,
|
formatDateJSONDateOnly,
|
||||||
formatNumberRFCurrency,
|
formatNumberRFCurrency,
|
||||||
formatErrorMessage,
|
formatErrorMessage,
|
||||||
genGUID
|
genGUID,
|
||||||
|
genUID
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,24 +12,35 @@ import PropTypes from "prop-types"; //Контроль свойств компо
|
|||||||
import { Box, Typography } from "@mui/material"; //Интерфейсные элементы
|
import { Box, Typography } from "@mui/material"; //Интерфейсные элементы
|
||||||
import { useComponentModule } from "./components/components_hooks"; //Хуки компонентов
|
import { useComponentModule } from "./components/components_hooks"; //Хуки компонентов
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
COMPONENT_BOX: isDragging => {
|
||||||
|
return isDragging ? { pointerEvents: "none" } : {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Представление компонента панели
|
//Представление компонента панели
|
||||||
const ComponentView = ({ id, path, settings = {}, values = {}, onValuesChange = null } = {}) => {
|
const ComponentView = ({ id, isDragging, path, settings = {}, values = {}, onValuesChange = null } = {}) => {
|
||||||
//Подгрузка модуля представления компонента (lazy здесь постоянно обновлялся при смене props, поэтому на хуке, от props независимого)
|
//Подгрузка модуля представления компонента (lazy здесь постоянно обновлялся при смене props, поэтому на хуке, от props независимого)
|
||||||
const [ComponentView, init] = useComponentModule({ path, module: "view" });
|
const [ComponentView, init] = useComponentModule({ path, module: "view" });
|
||||||
|
|
||||||
//При смене значений
|
//При смене значений
|
||||||
const handleValuesChange = values => onValuesChange && onValuesChange(id, values);
|
const handleValuesChange = values => onValuesChange && onValuesChange(values);
|
||||||
|
|
||||||
//Расчёт флага наличия компонента
|
//Расчёт флага наличия компонента
|
||||||
const haveComponent = path ? true : false;
|
const haveComponent = path ? true : false;
|
||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<Box className={"component-view__wrap"}>
|
<Box className={"component-view__wrap"} sx={STYLES.COMPONENT_BOX(isDragging)}>
|
||||||
{haveComponent && init && <ComponentView.default id={id} {...settings} values={values} onValuesChange={handleValuesChange} />}
|
{haveComponent && init && <ComponentView.default id={id} {...settings} values={values} onValuesChange={handleValuesChange} />}
|
||||||
{!haveComponent && <Typography align={"center"}>Компонент не определён</Typography>}
|
{!haveComponent && <Typography align={"center"}>Компонент не определён</Typography>}
|
||||||
</Box>
|
</Box>
|
||||||
@ -39,6 +50,7 @@ const ComponentView = ({ id, path, settings = {}, values = {}, onValuesChange =
|
|||||||
//Контроль свойств компонента - компонент панели
|
//Контроль свойств компонента - компонент панели
|
||||||
ComponentView.propTypes = {
|
ComponentView.propTypes = {
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
|
isDragging: PropTypes.bool.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
settings: PropTypes.object,
|
settings: PropTypes.object,
|
||||||
values: PropTypes.object,
|
values: PropTypes.object,
|
||||||
|
|||||||
51
app/panels/panels_editor/components/chart/action.js
Normal file
51
app/panels/panels_editor/components/chart/action.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: График (общие ресурсы действия)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Собственные значения типа действия
|
||||||
|
const P8P_CA_CHART_TYPE_VALUE = {
|
||||||
|
GRPH_ELEMENT: "Элемент графика"
|
||||||
|
};
|
||||||
|
|
||||||
|
//Типы значений действий графика
|
||||||
|
const P8P_CA_CHART_VALUE_TYPES = [...Object.values(P8P_CA_CHART_TYPE_VALUE)];
|
||||||
|
|
||||||
|
//Доступные области действий графика
|
||||||
|
const P8P_CA_CHART_ACTION_AREAS = [
|
||||||
|
{ name: "Компонент", area: "component", hasElement: false },
|
||||||
|
{ name: "Элемент графика", area: "chart_item", hasElement: false }
|
||||||
|
];
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Получение значения собственного типа действия
|
||||||
|
const getChartCustomTypeValue = ({ type, value, prms }) => {
|
||||||
|
//Если это элемента графика - возвращаем нужное значение, иначе - null
|
||||||
|
return type === P8P_CA_CHART_TYPE_VALUE.GRPH_ELEMENT ? prms.item[value] : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Считывание обработчиков графика
|
||||||
|
const getChartHandlers = handlers => {
|
||||||
|
//Возвращаем объект хэндлеров (имеют формат "[Область].[Сегмент]", где [Сегмент] - необязательный)
|
||||||
|
return {
|
||||||
|
onComponentClick: handlers["component."]?.fn,
|
||||||
|
onChartItemClick: handlers["chart_item."]?.fn
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8P_CA_CHART_VALUE_TYPES, P8P_CA_CHART_ACTION_AREAS, getChartCustomTypeValue, getChartHandlers };
|
||||||
@ -13,32 +13,46 @@ import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //
|
|||||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||||
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
||||||
|
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||||
|
import { P8P_CA_CHART_VALUE_TYPES, P8P_CA_CHART_ACTION_AREAS } from "./action"; //Общие ресурсы действий графика
|
||||||
|
import { P8PActions } from "../../../../components/editors/p8p_actions"; //Действия
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//График (редактор настроек)
|
//График (редактор настроек)
|
||||||
const ChartEditor = ({ id, dataSource = null, valueProviders = {}, onSettingsChange = null } = {}) => {
|
const ChartEditor = ({ id, dataSource = null, valueProviders = {}, actions = P8P_CAS_INITIAL, onSettingsChange = null } = {}) => {
|
||||||
//Собственное состояние - текущие настройки
|
//Собственное состояние - текущие настройки
|
||||||
const [settings, setSettings] = useState(null);
|
const [settings, setSettings] = useState(null);
|
||||||
|
|
||||||
//При изменении компонента
|
|
||||||
useEffect(() => {
|
|
||||||
settings?.id != id && setSettings({ id, dataSource });
|
|
||||||
}, [settings, id, dataSource]);
|
|
||||||
|
|
||||||
//При сохранении изменений элемента
|
//При сохранении изменений элемента
|
||||||
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
|
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
|
||||||
|
|
||||||
|
//При изменении действий
|
||||||
|
const handleActionsChange = actions => setSettings(pv => ({ ...pv, actions }));
|
||||||
|
|
||||||
//При сохранении настроек
|
//При сохранении настроек
|
||||||
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||||
|
|
||||||
|
//При изменении компонента
|
||||||
|
useEffect(() => {
|
||||||
|
settings?.id != id && setSettings({ id, dataSource, actions });
|
||||||
|
}, [settings, id, dataSource, actions]);
|
||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<P8PEditorBox title={"Параметры графика"} onSave={handleSave}>
|
<P8PEditorBox title={"Параметры графика"} onSave={handleSave}>
|
||||||
<P8PEditorSubHeader title={"Источник данных"} />
|
<P8PEditorSubHeader title={"Источник данных"} />
|
||||||
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
||||||
|
<P8PEditorSubHeader title={"Действия"} />
|
||||||
|
<P8PActions
|
||||||
|
actions={settings?.actions}
|
||||||
|
valueProviders={valueProviders}
|
||||||
|
areas={P8P_CA_CHART_ACTION_AREAS}
|
||||||
|
valueTypes={P8P_CA_CHART_VALUE_TYPES}
|
||||||
|
onChange={handleActionsChange}
|
||||||
|
/>
|
||||||
</P8PEditorBox>
|
</P8PEditorBox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -48,6 +62,7 @@ ChartEditor.propTypes = {
|
|||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||||
valueProviders: PropTypes.object,
|
valueProviders: PropTypes.object,
|
||||||
|
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||||
onSettingsChange: PropTypes.func
|
onSettingsChange: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -11,13 +11,16 @@ import React from "react"; //Классы React
|
|||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { Paper } from "@mui/material"; //Интерфейсные элементы
|
import { Paper } from "@mui/material"; //Интерфейсные элементы
|
||||||
import { P8PChart } from "../../../../components/p8p_chart"; //График
|
import { P8PChart } from "../../../../components/p8p_chart"; //График
|
||||||
import { useDataSource } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
import { useDataSource, useComponentHandlers } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
||||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||||
import {
|
import {
|
||||||
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
||||||
P8P_COMPONENT_INLINE_MESSAGE,
|
P8P_COMPONENT_INLINE_MESSAGE,
|
||||||
P8PComponentInlineMessage
|
P8PComponentInlineMessage
|
||||||
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
||||||
|
import { P8P_COMPONENT_SETTINGS_RESP_ARGS } from "../../../../components/editors/p8p_component_settings"; //Дополнительный функционал источников данных
|
||||||
|
import { P8P_CA_SHAPE } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||||
|
import { getChartCustomTypeValue, getChartHandlers } from "./action"; //Общие ресурсы действий графика
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -31,7 +34,14 @@ const COMPONENT_NAME = "График";
|
|||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
CHART: { width: "100%", height: "100%", alignItems: "center", justifyContent: "center", display: "flex" }
|
CHART: { width: "100%", height: "100%", alignItems: "center", justifyContent: "center", display: "flex" },
|
||||||
|
CONTAINER: clickable => ({
|
||||||
|
...(clickable
|
||||||
|
? {
|
||||||
|
cursor: "pointer"
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
@ -39,24 +49,31 @@ const STYLES = {
|
|||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//График (представление)
|
//График (представление)
|
||||||
const Chart = ({ dataSource = null, values = {} } = {}) => {
|
const Chart = ({ dataSource = null, values = {}, actions = [], onValuesChange = null } = {}) => {
|
||||||
//Собственное состояние - данные
|
//Собственное состояние - данные
|
||||||
const [data, error] = useDataSource({ dataSource, values });
|
const [chart, error, haveConfing, haveData] = useDataSource({ dataSource, values, componentRespArg: P8P_COMPONENT_SETTINGS_RESP_ARGS.CHART });
|
||||||
|
|
||||||
//Флаг настроенности графика
|
//Собственное состояние - обработчики компонента
|
||||||
const haveConfing = dataSource?.stored ? true : false;
|
const [handlers] = useComponentHandlers({ actions, onValuesChange, getCustomTypeValue: getChartCustomTypeValue });
|
||||||
|
|
||||||
//Флаг наличия данных
|
//Обработчики областей
|
||||||
const haveData = data?.init === true && !error ? true : false;
|
const { onComponentClick, onChartItemClick } = getChartHandlers(handlers);
|
||||||
|
|
||||||
//Данные графика
|
|
||||||
const chart = data?.XCHART || {};
|
|
||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<Paper className={"component-view__container component-view__container__empty"} elevation={6}>
|
<Paper
|
||||||
|
sx={STYLES.CONTAINER(onComponentClick)}
|
||||||
|
className={"component-view__container component-view__container__empty"}
|
||||||
|
elevation={6}
|
||||||
|
onClick={event => onComponentClick && onComponentClick({ event, values })}
|
||||||
|
>
|
||||||
{haveConfing && haveData ? (
|
{haveConfing && haveData ? (
|
||||||
<P8PChart style={STYLES.CHART} {...chart} />
|
<P8PChart
|
||||||
|
style={STYLES.CHART}
|
||||||
|
{...chart}
|
||||||
|
options={{ responsive: true, maintainAspectRatio: false }}
|
||||||
|
onClick={prms => onChartItemClick && onChartItemClick({ values, prms })}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<P8PComponentInlineMessage
|
<P8PComponentInlineMessage
|
||||||
icon={COMPONENT_ICON}
|
icon={COMPONENT_ICON}
|
||||||
@ -72,7 +89,9 @@ const Chart = ({ dataSource = null, values = {} } = {}) => {
|
|||||||
//Контроль свойств компонента - График (представление)
|
//Контроль свойств компонента - График (представление)
|
||||||
Chart.propTypes = {
|
Chart.propTypes = {
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||||
values: PropTypes.object
|
values: PropTypes.object,
|
||||||
|
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||||
|
onValuesChange: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
|
|||||||
@ -7,123 +7,51 @@
|
|||||||
//Константы
|
//Константы
|
||||||
//---------
|
//---------
|
||||||
|
|
||||||
const COMPONETNS = [
|
const COMPONENTS = [
|
||||||
{
|
{
|
||||||
name: "Форма",
|
name: "Форма",
|
||||||
path: "form",
|
path: "form",
|
||||||
settings2: {
|
settings: {
|
||||||
title: "Параметры формирования",
|
id: "",
|
||||||
autoApply: true,
|
title: "",
|
||||||
items: [
|
autoApply: false,
|
||||||
{
|
orientation: "v",
|
||||||
name: "AGENT",
|
items: []
|
||||||
caption: "Контрагент",
|
|
||||||
unitCode2: "AGNLIST",
|
|
||||||
unitName: "Контрагенты",
|
|
||||||
showMethod: "main",
|
|
||||||
showMethodName: "main",
|
|
||||||
parameter: "Мнемокод",
|
|
||||||
inputParameter: "in_AGNABBR",
|
|
||||||
outputParameter: "out_AGNABBR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "DOC_TYPE",
|
|
||||||
caption: "Тип документа",
|
|
||||||
unitCode2: "DOCTYPES",
|
|
||||||
unitName: "Типы документов",
|
|
||||||
showMethod: "main",
|
|
||||||
showMethodName: "main",
|
|
||||||
parameter: "Мнемокод",
|
|
||||||
inputParameter: "in_DOCCODE",
|
|
||||||
outputParameter: "out_DOCCODE"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "График",
|
name: "График",
|
||||||
path: "chart",
|
path: "chart",
|
||||||
settings2: {
|
settings: {
|
||||||
dataSource: {
|
id: "",
|
||||||
type: "USER_PROC",
|
dataSource: {},
|
||||||
userProc: "ГрафТоп5ДогКонтрТип",
|
actions: []
|
||||||
stored: "UDO_P_P8P_AGNCONTR_CHART",
|
|
||||||
respArg: "COUT",
|
|
||||||
arguments: [
|
|
||||||
{ name: "SAGENT", caption: "Контрагент", dataType: "STR", req: false, value: "", valueSource: "AGENT" },
|
|
||||||
{ name: "SDOC_TYPE", caption: "Тип документа", dataType: "STR", req: false, value: "", valueSource: "DOC_TYPE" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Таблица",
|
name: "Таблица",
|
||||||
path: "table",
|
path: "table",
|
||||||
settings2: {
|
settings: {
|
||||||
dataSource: {
|
id: "",
|
||||||
type: "USER_PROC",
|
dataSource: {},
|
||||||
userProc: "ТаблицаДогКонтрТип",
|
actions: [],
|
||||||
stored: "UDO_P_P8P_AGNCONTR_TABLE",
|
conditions: []
|
||||||
respArg: "COUT",
|
|
||||||
arguments: [
|
|
||||||
{ name: "SAGENT", caption: "Контрагент", dataType: "STR", req: false, value: "", valueSource: "AGENT" },
|
|
||||||
{ name: "SDOC_TYPE", caption: "Тип документа", dataType: "STR", req: false, value: "", valueSource: "DOC_TYPE" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Индикатор",
|
name: "Индикатор",
|
||||||
path: "indicator",
|
path: "indicator",
|
||||||
settings2: {
|
|
||||||
dataSource: {
|
|
||||||
type: "USER_PROC",
|
|
||||||
userProc: "ИндКолДогКонтрТип",
|
|
||||||
stored: "UDO_P_P8P_AGNCONTR_IND",
|
|
||||||
respArg: "COUT",
|
|
||||||
arguments: [
|
|
||||||
{ name: "SAGENT", caption: "Контрагент", dataType: "STR", req: false, value: "", valueSource: "AGENT" },
|
|
||||||
{ name: "SDOC_TYPE", caption: "Тип документа", dataType: "STR", req: false, value: "", valueSource: "DOC_TYPE" },
|
|
||||||
{
|
|
||||||
name: "NIND_TYPE",
|
|
||||||
caption: "Тип индикатора (0 - все, 1 - неутвержденные)",
|
|
||||||
dataType: "NUMB",
|
|
||||||
req: true,
|
|
||||||
value: "0",
|
|
||||||
valueSource: ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} /*,
|
|
||||||
{
|
|
||||||
name: "Индикатор2",
|
|
||||||
path: "indicator",
|
|
||||||
settings: {
|
settings: {
|
||||||
dataSource: {
|
id: "",
|
||||||
type: "USER_PROC",
|
dataSource: {},
|
||||||
userProc: "ИндКолДогКонтрТип",
|
actions: [],
|
||||||
stored: "UDO_P_P8P_AGNCONTR_IND",
|
conditions: []
|
||||||
respArg: "COUT",
|
|
||||||
arguments: [
|
|
||||||
{ name: "SAGENT", caption: "Контрагент", dataType: "STR", req: false, value: "", valueSource: "AGENT" },
|
|
||||||
{ name: "SDOC_TYPE", caption: "Тип документа", dataType: "STR", req: false, value: "", valueSource: "DOC_TYPE" },
|
|
||||||
{
|
|
||||||
name: "NIND_TYPE",
|
|
||||||
caption: "Тип индикатора (0 - все, 1 - неутвержденные)",
|
|
||||||
dataType: "NUMB",
|
|
||||||
req: true,
|
|
||||||
value: "1",
|
|
||||||
valueSource: ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
export { COMPONETNS };
|
export { COMPONENTS };
|
||||||
|
|||||||
@ -7,7 +7,140 @@
|
|||||||
//Подключение библиотек
|
//Подключение библиотек
|
||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
import { useState, useEffect } from "react"; //Классы React
|
import { useState, useEffect, useContext, useCallback, useLayoutEffect } from "react"; //Классы React
|
||||||
|
import { BackEndСtx } from "../../../context/backend"; //Контекст взаимодействия с сервером
|
||||||
|
import { MessagingСtx } from "../../../context/messaging"; //Контекст сообщений
|
||||||
|
import { object2Base64XML, genUID, xml2JSON } from "../../../core/utils"; //Вспомогательные функции
|
||||||
|
import { exportXMLFile } from "../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||||
|
import { P8P_COMPONENT_SETTINGS_PATHS } from "../../../components/editors/p8p_component_settings"; //Дополнительные настройки источников
|
||||||
|
import { getActionsVariables } from "../../../components/editors/p8p_component_action/util"; //Вспомогательный функционал действий компонентов
|
||||||
|
import { P8P_CA_TYPE } from "../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Начальное состояние размера макета
|
||||||
|
const INITIAL_BREAKPOINT = "lg";
|
||||||
|
|
||||||
|
//Начальное состояние макета
|
||||||
|
const INITIAL_LAYOUTS = {
|
||||||
|
[INITIAL_BREAKPOINT]: []
|
||||||
|
};
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы расчета высоты строки ResponsiveGridLayout
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Структура параметров для расчета высоты строки ResponsiveGridLayout
|
||||||
|
const GRID_LAYOUT_PARAMS = {
|
||||||
|
//Высота главного меню (px)
|
||||||
|
APP_BAR_HEIGHT: 64,
|
||||||
|
//Дополнительный отступ (px)
|
||||||
|
SAFE_OFFSET: 20,
|
||||||
|
//Максимальное количество строк (число)
|
||||||
|
MAX_ROWS: 68,
|
||||||
|
//Высота отступа (px)
|
||||||
|
MARGIN: 10
|
||||||
|
};
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Проверка на массив загружаемых данных панели
|
||||||
|
const isArrayPanelDesc = (name, jPath) =>
|
||||||
|
["items", "arguments", "conditions", "actions", "dependencies", "inputParams"].includes(name) || /(.*)XLAYOUTS\.[^.]*$/.test(jPath);
|
||||||
|
|
||||||
|
//Обработка значений тэгов загружаемых данных панели
|
||||||
|
const tagValueProcessorPanelDesc = (name, val, jPath) =>
|
||||||
|
["condValue", "resValue", "description"].includes(name)
|
||||||
|
? undefined
|
||||||
|
: /(.*)dataSource.arguments.value$/.test(jPath) || /(.*)XVALUE_PROVIDERS(.*)$/.test(jPath)
|
||||||
|
? undefined
|
||||||
|
: val;
|
||||||
|
|
||||||
|
//Конвертация серверного описания компонентов в данные для редактора панелей
|
||||||
|
const convertServerData2Components = components => {
|
||||||
|
//Корректировка информации о действия (Для типа "setVariable" значение ключа "params" - обязательно массив, в иных случаях - объект)
|
||||||
|
const correctionActions = actions => {
|
||||||
|
return actions.reduce(
|
||||||
|
(prevActions, action) => [
|
||||||
|
...prevActions,
|
||||||
|
{
|
||||||
|
...(action.type === P8P_CA_TYPE.setVariable.code && !Array.isArray(action.params)
|
||||||
|
? { ...action, params: [{ ...action.params }] }
|
||||||
|
: { ...action })
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Форматируем устанавливая пустой объект для dataSource, если он пуст
|
||||||
|
return Object.keys(components).reduce(
|
||||||
|
(prev, cur) => ({
|
||||||
|
...prev,
|
||||||
|
[cur]: {
|
||||||
|
...components[cur],
|
||||||
|
settings: {
|
||||||
|
...components[cur].settings,
|
||||||
|
//dataSource - обязательно объект
|
||||||
|
...(components[cur].settings?.dataSource ? { dataSource: components[cur].settings.dataSource || {} } : {}),
|
||||||
|
//actions - требуют корректировки
|
||||||
|
...(components[cur].settings?.actions
|
||||||
|
? {
|
||||||
|
actions: correctionActions(components[cur].settings.actions)
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Конвертация серверного описания проводников значений в данные для редактора панелей
|
||||||
|
const convertServerData2ValueProviders = valueProviders => {
|
||||||
|
//Форматируем инициализируя dependencies, если требуется
|
||||||
|
return Object.keys(valueProviders).reduce(
|
||||||
|
(prev, cur) => ({ ...prev, [cur]: { ...valueProviders[cur], dependencies: valueProviders[cur].dependencies || [] } }),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Конвертация серверного описания панели в данные для редактора панелей
|
||||||
|
const serverPanelData2PanelDesc = (components, valueProviders, layouts, breakpoint) => {
|
||||||
|
//Возвращаем информацию о панеле с учетом конвертаций
|
||||||
|
return {
|
||||||
|
components: convertServerData2Components(components),
|
||||||
|
valueProviders: convertServerData2ValueProviders(valueProviders),
|
||||||
|
layouts: layouts[breakpoint] ? { [breakpoint]: [...layouts[breakpoint]] } : INITIAL_LAYOUTS,
|
||||||
|
breakpoint: breakpoint
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
//Считывание общего списка зависимостей от проводника значений
|
||||||
|
const getValueProvidersLinks = (componentPath, settings) => {
|
||||||
|
//Если это индикатор/график/таблица
|
||||||
|
if ([P8P_COMPONENT_SETTINGS_PATHS.INDICATOR, P8P_COMPONENT_SETTINGS_PATHS.CHART, P8P_COMPONENT_SETTINGS_PATHS.TABLE].includes(componentPath)) {
|
||||||
|
//Собираем зависимости из настройки источника
|
||||||
|
let argumentsSources = Array.isArray(settings?.dataSource?.arguments)
|
||||||
|
? settings.dataSource.arguments.reduce((prev, cur) => (cur.valueSource ? [...prev, cur.valueSource] : [...prev]), [])
|
||||||
|
: [];
|
||||||
|
//Собираем зависимости из параметров действий
|
||||||
|
let actionsSources = Array.isArray(settings?.actions) ? getActionsVariables(settings.actions) : [];
|
||||||
|
//Возвращаем зависимости компонента
|
||||||
|
return [...new Set([...argumentsSources, ...actionsSources])];
|
||||||
|
}
|
||||||
|
//Если это форма
|
||||||
|
if (P8P_COMPONENT_SETTINGS_PATHS.FORM === componentPath) {
|
||||||
|
//Собираем зависимости из элементов формы
|
||||||
|
let items = Array.isArray(settings?.items) ? settings.items.map(item => item.name) : [];
|
||||||
|
//Возвращаем зависимости компонента
|
||||||
|
return [...new Set(items)];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
@ -37,8 +170,470 @@ const useComponentModule = ({ path = null, module = "view" } = {}) => {
|
|||||||
return [componentModule, init];
|
return [componentModule, init];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Работа с панелью
|
||||||
|
const usePanel = () => {
|
||||||
|
//Собственное состояние - рабочая панель
|
||||||
|
const [panel, setPanel] = useState();
|
||||||
|
//Собственное состояние - имя рабочей панели
|
||||||
|
const [panelName, setPanelName] = useState(null);
|
||||||
|
//Собственное состояние - наличие изменений рабочей панели
|
||||||
|
const [isPanelChanged, setIsPanelChanged] = useState(false);
|
||||||
|
//Собственное состояние - возможность редактирования
|
||||||
|
const [isEditAvaliable, setIsEditAvaliable] = useState(true);
|
||||||
|
//Собственное состояние - режим редактирования
|
||||||
|
const [editMode, setEditMode] = useState(true);
|
||||||
|
|
||||||
|
//Подключение к контексту взаимодействия с сервером
|
||||||
|
const { executeStored } = useContext(BackEndСtx);
|
||||||
|
|
||||||
|
//При необходимости загрузки информации о панели
|
||||||
|
const loadPanel = async panel => {
|
||||||
|
//Считываем информацию с сервера
|
||||||
|
const res = await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_PE.PANEL_ATTRS_GET_BY_CODE",
|
||||||
|
args: {
|
||||||
|
SCODE: panel
|
||||||
|
},
|
||||||
|
respArg: "COUT",
|
||||||
|
loader: true
|
||||||
|
});
|
||||||
|
//Устанавливаем рабочую панель
|
||||||
|
setPanel(res.XPANEL_ATTRS.rn);
|
||||||
|
//Устанавливаем наименование рабочей панели
|
||||||
|
setPanelName(res.XPANEL_ATTRS.name);
|
||||||
|
//Сбрасываем наличие изменений рабочей панели
|
||||||
|
setIsPanelChanged(false);
|
||||||
|
//Загружаемую панель запрещено редактировать
|
||||||
|
setIsEditAvaliable(false);
|
||||||
|
setEditMode(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При выборе панели
|
||||||
|
const selectPanel = (panel, panelName, isPanelEditAvaliable) => {
|
||||||
|
//Обновляем информацию о панели
|
||||||
|
setPanel(panel);
|
||||||
|
setPanelName(panelName);
|
||||||
|
setIsEditAvaliable(isPanelEditAvaliable);
|
||||||
|
setEditMode(isPanelEditAvaliable);
|
||||||
|
setIsPanelChanged(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При закрытии панели
|
||||||
|
const closePanel = () => {
|
||||||
|
setPanel(null);
|
||||||
|
setPanelName(null);
|
||||||
|
setIsPanelChanged(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При установке признака изменений панели
|
||||||
|
const setPanelChanged = isChanged => setIsPanelChanged(isChanged);
|
||||||
|
|
||||||
|
//При установке признака режима редактирования
|
||||||
|
const changeEditMode = isEditMode => setEditMode(isEditMode);
|
||||||
|
|
||||||
|
//Возвращаем интерфейс хука
|
||||||
|
return [panel, panelName, editMode, isEditAvaliable, isPanelChanged, loadPanel, selectPanel, closePanel, changeEditMode, setPanelChanged];
|
||||||
|
};
|
||||||
|
|
||||||
|
//Работа с менеджером панелей
|
||||||
|
const usePanelManager = () => {
|
||||||
|
//Собственное состояние - флаг необходимости обновления
|
||||||
|
const [refresh, setRefresh] = useState(true);
|
||||||
|
|
||||||
|
//Собственное состояние - данные
|
||||||
|
const [data, setData] = useState(null);
|
||||||
|
|
||||||
|
//Подключение к контексту взаимодействия с сервером
|
||||||
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
|
//Добавление панели
|
||||||
|
const insertPanel = useCallback(
|
||||||
|
async (code, name) => {
|
||||||
|
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_INSERT", args: { SCODE: code, SNAME: name }, loader: false });
|
||||||
|
setRefresh(true);
|
||||||
|
},
|
||||||
|
[executeStored]
|
||||||
|
);
|
||||||
|
|
||||||
|
//Изменение панели
|
||||||
|
const updatePanel = useCallback(
|
||||||
|
async (query, code, name) => {
|
||||||
|
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_UPDATE", args: { NRN: query, SCODE: code, SNAME: name }, loader: false });
|
||||||
|
setRefresh(true);
|
||||||
|
},
|
||||||
|
[executeStored]
|
||||||
|
);
|
||||||
|
|
||||||
|
//Удаление панели
|
||||||
|
const deletePanel = useCallback(
|
||||||
|
async query => {
|
||||||
|
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_DELETE", args: { NRN: query }, loader: false });
|
||||||
|
setRefresh(true);
|
||||||
|
},
|
||||||
|
[executeStored]
|
||||||
|
);
|
||||||
|
|
||||||
|
//Установка флага готовности панели
|
||||||
|
const setPanelReady = useCallback(
|
||||||
|
async (query, ready) => {
|
||||||
|
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_READY_SET", args: { NRN: query, NREADY: ready }, loader: false });
|
||||||
|
setRefresh(true);
|
||||||
|
},
|
||||||
|
[executeStored]
|
||||||
|
);
|
||||||
|
|
||||||
|
//Установка флага публичности панели
|
||||||
|
const setPanelPbl = useCallback(
|
||||||
|
async (query, pbl) => {
|
||||||
|
await executeStored({ stored: "PKG_P8PANELS_PE.PANEL_PBL_SET", args: { NRN: query, NPBL: pbl }, loader: false });
|
||||||
|
setRefresh(true);
|
||||||
|
},
|
||||||
|
[executeStored]
|
||||||
|
);
|
||||||
|
|
||||||
|
//Импорт новой панели
|
||||||
|
const importPanel = useCallback(
|
||||||
|
async fileData => {
|
||||||
|
await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_PE.PANEL_IMPORT",
|
||||||
|
args: {
|
||||||
|
CPANEL: {
|
||||||
|
//Форматируем данные документа в base64
|
||||||
|
VALUE: btoa(unescape(encodeURIComponent(fileData))),
|
||||||
|
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loader: true
|
||||||
|
});
|
||||||
|
setRefresh(true);
|
||||||
|
},
|
||||||
|
[SERV_DATA_TYPE_CLOB, executeStored]
|
||||||
|
);
|
||||||
|
|
||||||
|
//При необходимости получить/обновить данные
|
||||||
|
useEffect(() => {
|
||||||
|
//Загрузка данных с сервера
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
const data = await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_PE.PANEL_LIST",
|
||||||
|
respArg: "COUT",
|
||||||
|
isArray: name => ["XPANEL"].includes(name),
|
||||||
|
attributeValueProcessor: (name, val) => (["code", "name"].includes(name) ? undefined : val),
|
||||||
|
loader: true
|
||||||
|
});
|
||||||
|
setData(data?.XPANELS?.XPANEL || []);
|
||||||
|
} finally {
|
||||||
|
setRefresh(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//Если надо обновить
|
||||||
|
if (refresh)
|
||||||
|
//Получим данные
|
||||||
|
loadData();
|
||||||
|
}, [refresh, executeStored]);
|
||||||
|
|
||||||
|
//Возвращаем интерфейс хука
|
||||||
|
return [data, insertPanel, updatePanel, deletePanel, setPanelReady, setPanelPbl, importPanel];
|
||||||
|
};
|
||||||
|
|
||||||
|
//Работа с содержимым панели
|
||||||
|
const usePanelDesc = panel => {
|
||||||
|
//Собственное состояние - флаг инициализированности
|
||||||
|
const [isInit, setInit] = useState(false);
|
||||||
|
|
||||||
|
//Собственное состояние - флаг необходимости обновления
|
||||||
|
const [refresh, setRefresh] = useState(true);
|
||||||
|
|
||||||
|
//Собственное состояние - данные
|
||||||
|
const [data, setData] = useState({
|
||||||
|
components: {},
|
||||||
|
valueProviders: {},
|
||||||
|
layouts: INITIAL_LAYOUTS,
|
||||||
|
breakpoint: INITIAL_BREAKPOINT
|
||||||
|
});
|
||||||
|
|
||||||
|
//Подключение к контексту взаимодействия с сервером
|
||||||
|
const { executeStored, SERV_DATA_TYPE_CLOB } = useContext(BackEndСtx);
|
||||||
|
|
||||||
|
//Подключение к контексту сообщений
|
||||||
|
const { showMsgErr } = useContext(MessagingСtx);
|
||||||
|
|
||||||
|
//Считывание базовой информации о панели
|
||||||
|
const getPanelInfo = useCallback(async () => {
|
||||||
|
//Считываем информацию с сервера
|
||||||
|
const res = await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_PE.PANEL_ATTRS_GET",
|
||||||
|
args: {
|
||||||
|
NRN: panel
|
||||||
|
},
|
||||||
|
respArg: "COUT",
|
||||||
|
loader: true
|
||||||
|
});
|
||||||
|
//Забираем все без RN
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const { rn, ...panelAttrs } = res.XPANEL_ATTRS;
|
||||||
|
//Возвращаем результат
|
||||||
|
return panelAttrs;
|
||||||
|
}, [executeStored, panel]);
|
||||||
|
|
||||||
|
//Формирования данных панели для выгрузки
|
||||||
|
const makeXMLPanelDesc = useCallback(
|
||||||
|
async (includePanelInfo = false, isBase64 = true) => {
|
||||||
|
//Считываем информацию о выгружаемой панели
|
||||||
|
const panelInfo = includePanelInfo ? await getPanelInfo() : {};
|
||||||
|
//Сформируем данные в формат XML
|
||||||
|
const xmlData = object2Base64XML(
|
||||||
|
{
|
||||||
|
XPANEL: {
|
||||||
|
...(includePanelInfo ? { XPANEL_INFO: panelInfo } : {}),
|
||||||
|
XCOMPONENTS: data.components,
|
||||||
|
XVALUE_PROVIDERS: data.valueProviders,
|
||||||
|
XLAYOUTS: data.layouts,
|
||||||
|
XOPTIONS: {
|
||||||
|
breakpoint: data.breakpoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ suppressEmptyNode: false }
|
||||||
|
);
|
||||||
|
//Возвращаем данные в формате XML (формат исходит от признака)
|
||||||
|
return isBase64 ? xmlData : decodeURIComponent(escape(atob(xmlData)));
|
||||||
|
},
|
||||||
|
[data.breakpoint, data.components, data.layouts, data.valueProviders, getPanelInfo]
|
||||||
|
);
|
||||||
|
|
||||||
|
//Добавление компонента в макет
|
||||||
|
const addComponent = component => {
|
||||||
|
//Генерируем ID
|
||||||
|
const id = genUID(component.path);
|
||||||
|
//Добавляем компонент и его макет
|
||||||
|
setData(pv => ({
|
||||||
|
...pv,
|
||||||
|
components: { ...pv.components, [id]: { ...component, settings: { ...component.settings, id: id } } },
|
||||||
|
layouts: { ...pv.layouts, [data.breakpoint]: [...pv.layouts[data.breakpoint], { i: id, x: 0, y: 0, w: 4, h: 10 }] }
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
//Удаление компонента из макета
|
||||||
|
const deleteComponent = id => {
|
||||||
|
//Удаляем все старые зависимости от компонента
|
||||||
|
const newValueProviders = Object.keys(data.valueProviders).reduce(
|
||||||
|
(prev, cur) => ({
|
||||||
|
...prev,
|
||||||
|
[cur]: { ...data.valueProviders[cur], dependencies: data.valueProviders[cur].dependencies.filter(el => el !== id) }
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
//Обновляем данные
|
||||||
|
setData(pv => ({
|
||||||
|
...pv,
|
||||||
|
layouts: { ...pv.layouts, [data.breakpoint]: data.layouts[data.breakpoint].filter(item => item.i !== id) },
|
||||||
|
components: { ...pv.components, [id]: { ...pv.components[id], deleted: true } },
|
||||||
|
valueProviders: { ...newValueProviders }
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
//Изменение размера холста
|
||||||
|
const breakpointChange = breakpoint => setData(pv => ({ ...pv, breakpoint: breakpoint }));
|
||||||
|
|
||||||
|
//Изменение состояния макета
|
||||||
|
const layoutsChange = layouts => setData(pv => ({ ...pv, layouts: layouts }));
|
||||||
|
|
||||||
|
//Изменение значений в компоненте
|
||||||
|
const changeValueProviders = values => {
|
||||||
|
//Считываем проводники
|
||||||
|
const newValueProviders = { ...data.valueProviders };
|
||||||
|
//Переносим новые значения в проводники
|
||||||
|
Object.keys(values).map(el => (newValueProviders[el].value = values[el]));
|
||||||
|
//Обновляем проводники
|
||||||
|
setData(pv => ({ ...pv, valueProviders: { ...newValueProviders } }));
|
||||||
|
};
|
||||||
|
|
||||||
|
//Изменение настроек компонента
|
||||||
|
const changeComponentSettings = (id = null, settings = {}, onChanged = null) => {
|
||||||
|
//Считываем новые зависимости компонента
|
||||||
|
const providedValues = getValueProvidersLinks(data.components[id].path, settings);
|
||||||
|
//Удаляем все старые зависимости от компонента
|
||||||
|
const newValueProviders = Object.keys(data.valueProviders).reduce(
|
||||||
|
(prev, cur) => ({
|
||||||
|
...prev,
|
||||||
|
[cur]: { ...data.valueProviders[cur], dependencies: data.valueProviders[cur].dependencies.filter(el => el !== id) }
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
//Добавляем новые зависимости
|
||||||
|
providedValues.map(providerName => newValueProviders[providerName].dependencies.push(id));
|
||||||
|
//Обновляем данные
|
||||||
|
setData(pv => ({
|
||||||
|
...pv,
|
||||||
|
components: { ...pv.components, [id]: { ...pv.components[id], settings: { ...settings } } },
|
||||||
|
valueProviders: { ...newValueProviders }
|
||||||
|
}));
|
||||||
|
//Выполняем действия после изменения
|
||||||
|
onChanged && onChanged();
|
||||||
|
};
|
||||||
|
|
||||||
|
//Изменение настроек панели
|
||||||
|
const changePanelSettings = valueProviders => {
|
||||||
|
//Обновляем данные
|
||||||
|
setData(pv => ({
|
||||||
|
...pv,
|
||||||
|
valueProviders: { ...valueProviders }
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
//Сохранение описания панели
|
||||||
|
const savePanelDesc = useCallback(
|
||||||
|
async (callBack = null) => {
|
||||||
|
try {
|
||||||
|
await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_PE.PANEL_DESC_SET",
|
||||||
|
args: {
|
||||||
|
NRN: panel,
|
||||||
|
CPANEL: {
|
||||||
|
VALUE: await makeXMLPanelDesc(),
|
||||||
|
SDATA_TYPE: SERV_DATA_TYPE_CLOB
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loader: true
|
||||||
|
});
|
||||||
|
callBack && callBack(false);
|
||||||
|
} catch (e) {
|
||||||
|
callBack && callBack(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[SERV_DATA_TYPE_CLOB, executeStored, makeXMLPanelDesc, panel]
|
||||||
|
);
|
||||||
|
|
||||||
|
//Загрузка описания панели из файла
|
||||||
|
const importPanelDesc = async (fileData, callBack = null) => {
|
||||||
|
//Формируем данные панели
|
||||||
|
const panelData = await xml2JSON({
|
||||||
|
xmlDoc: fileData,
|
||||||
|
isArray: isArrayPanelDesc,
|
||||||
|
tagValueProcessor: tagValueProcessorPanelDesc
|
||||||
|
});
|
||||||
|
//Если файл содержит тэг XPANEL
|
||||||
|
if (panelData.XPANEL) {
|
||||||
|
setData(
|
||||||
|
serverPanelData2PanelDesc(
|
||||||
|
panelData.XPANEL?.XCOMPONENTS || {},
|
||||||
|
panelData.XPANEL?.XVALUE_PROVIDERS || {},
|
||||||
|
panelData.XPANEL?.XLAYOUTS || {},
|
||||||
|
panelData.XPANEL?.XOPTIONS?.breakpoint || INITIAL_BREAKPOINT
|
||||||
|
)
|
||||||
|
);
|
||||||
|
callBack && callBack(true);
|
||||||
|
} else {
|
||||||
|
showMsgErr("Загружаемые данные не соответствуют формату настройки панели.");
|
||||||
|
callBack && callBack(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Выгрузка панели в файл
|
||||||
|
const exportPanelDesc = async panelName => {
|
||||||
|
//Формируем XML-представление панели
|
||||||
|
const xmlPanelDesc = await makeXMLPanelDesc(true, false);
|
||||||
|
//Выгружаем в файл
|
||||||
|
exportXMLFile(xmlPanelDesc, panelName);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При необходимости получить/обновить данные
|
||||||
|
useEffect(() => {
|
||||||
|
//Загрузка данных с сервера
|
||||||
|
const loadData = async () => {
|
||||||
|
try {
|
||||||
|
const data = await executeStored({
|
||||||
|
stored: "PKG_P8PANELS_PE.PANEL_DESC_GET",
|
||||||
|
args: { NRN: panel },
|
||||||
|
respArg: "COUT",
|
||||||
|
isArray: isArrayPanelDesc,
|
||||||
|
tagValueProcessor: tagValueProcessorPanelDesc,
|
||||||
|
loader: true
|
||||||
|
});
|
||||||
|
setData(
|
||||||
|
serverPanelData2PanelDesc(
|
||||||
|
data?.XCOMPONENTS || {},
|
||||||
|
data?.XVALUE_PROVIDERS || {},
|
||||||
|
data?.XLAYOUTS || {},
|
||||||
|
data?.XOPTIONS?.breakpoint || INITIAL_BREAKPOINT
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setInit(true);
|
||||||
|
} finally {
|
||||||
|
setRefresh(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//Если надо обновить
|
||||||
|
if (refresh)
|
||||||
|
if (panel)
|
||||||
|
//Если есть для чего получать данные
|
||||||
|
loadData();
|
||||||
|
//Нет идентификатора запроса - нет данных
|
||||||
|
else
|
||||||
|
setData({
|
||||||
|
components: {},
|
||||||
|
valueProviders: {},
|
||||||
|
layouts: INITIAL_LAYOUTS,
|
||||||
|
breakpoint: INITIAL_BREAKPOINT
|
||||||
|
});
|
||||||
|
}, [refresh, panel, executeStored]);
|
||||||
|
|
||||||
|
//При изменении входных свойств - поднимаем флаг обновления
|
||||||
|
useEffect(() => {
|
||||||
|
setInit(false);
|
||||||
|
setRefresh(true);
|
||||||
|
}, [panel]);
|
||||||
|
|
||||||
|
//Возвращаем интерфейс хука
|
||||||
|
return [
|
||||||
|
data,
|
||||||
|
isInit,
|
||||||
|
addComponent,
|
||||||
|
deleteComponent,
|
||||||
|
breakpointChange,
|
||||||
|
layoutsChange,
|
||||||
|
changeValueProviders,
|
||||||
|
changeComponentSettings,
|
||||||
|
changePanelSettings,
|
||||||
|
savePanelDesc,
|
||||||
|
importPanelDesc,
|
||||||
|
exportPanelDesc
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
//Работа с соотношением размеров
|
||||||
|
const useWindowResize = () => {
|
||||||
|
//Состояние высоты строки ResponsiveGridLayout
|
||||||
|
const [rowHeight, setRowHeight] = useState(1);
|
||||||
|
//При изменении размера
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
//Расчет высоты строки
|
||||||
|
const updateRowHeight = () => {
|
||||||
|
//Определяем доступную область
|
||||||
|
const availableHeight = window.innerHeight - GRID_LAYOUT_PARAMS.APP_BAR_HEIGHT - GRID_LAYOUT_PARAMS.SAFE_OFFSET;
|
||||||
|
//Промежутки между рядами
|
||||||
|
const totalMargins = (GRID_LAYOUT_PARAMS.MAX_ROWS - 1) * GRID_LAYOUT_PARAMS.MARGIN;
|
||||||
|
//Рассчитываем высоту строки
|
||||||
|
const result = (availableHeight - totalMargins) / GRID_LAYOUT_PARAMS.MAX_ROWS;
|
||||||
|
//Устанавливаем
|
||||||
|
setRowHeight(result);
|
||||||
|
};
|
||||||
|
//Запускаем при открытии панели
|
||||||
|
updateRowHeight();
|
||||||
|
//Устанавливаем обработчик на событие
|
||||||
|
window.addEventListener("resize", updateRowHeight);
|
||||||
|
//Удаляем обработчик на событие
|
||||||
|
return () => window.removeEventListener("resize", updateRowHeight);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
//Возвращаем интерфейс хука
|
||||||
|
return [rowHeight];
|
||||||
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
//Интерфейс модуля
|
//Интерфейс модуля
|
||||||
//----------------
|
//----------------
|
||||||
|
|
||||||
export { useComponentModule };
|
export { useComponentModule, usePanel, usePanelManager, usePanelDesc, useWindowResize };
|
||||||
|
|||||||
@ -47,3 +47,6 @@ export const ORIENTATION = {
|
|||||||
H: "H",
|
H: "H",
|
||||||
V: "v"
|
V: "v"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Доступность сохранения настроек элемента
|
||||||
|
export const isItemOkDisabled = item => (!item.name ? true : false);
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Icon,
|
Icon,
|
||||||
Select,
|
Select,
|
||||||
|
Menu,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
FormControl,
|
FormControl,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
@ -31,7 +32,8 @@ import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //
|
|||||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||||
import { P8PConfigDialog } from "../../../../components/editors/p8p_config_dialog"; //Диалог настройки
|
import { P8PConfigDialog } from "../../../../components/editors/p8p_config_dialog"; //Диалог настройки
|
||||||
import { STYLES as COMMON_STYLES } from "../../../../components/editors/p8p_editors_common"; //Общие ресурсы редакторов
|
import { STYLES as COMMON_STYLES } from "../../../../components/editors/p8p_editors_common"; //Общие ресурсы редакторов
|
||||||
import { ITEM_SHAPE, ITEM_INITIAL, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы
|
import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы
|
||||||
|
import { ITEM_SHAPE, ITEM_INITIAL, ITEMS_INITIAL, ORIENTATION, isItemOkDisabled } from "./common"; //Общие ресурсы и константы формы
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -47,10 +49,13 @@ const STYLES = {
|
|||||||
//------------------------------------
|
//------------------------------------
|
||||||
|
|
||||||
//Редактор элемента
|
//Редактор элемента
|
||||||
const ItemEditor = ({ item = null, onOk = null, onCancel = null } = {}) => {
|
const ItemEditor = ({ item = null, valueProviders = {}, onOk = null, onCancel = null } = {}) => {
|
||||||
//Собственное состояние - параметры элемента формы
|
//Собственное состояние - параметры элемента формы
|
||||||
const [state, setState] = useState({ ...ITEM_INITIAL, ...item });
|
const [state, setState] = useState({ ...ITEM_INITIAL, ...item });
|
||||||
|
|
||||||
|
//Собственное состояние - элемент привязки меню выбора источника
|
||||||
|
const [valueProvidersMenuAnchorEl, setValueProvidersMenuAnchorEl] = useState(null);
|
||||||
|
|
||||||
//Подключение к контексту приложения
|
//Подключение к контексту приложения
|
||||||
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
const { pOnlineShowDictionary } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
@ -123,11 +128,74 @@ const ItemEditor = ({ item = null, onOk = null, onCancel = null } = {}) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Открытие/сокрытие меню выбора источника
|
||||||
|
const toggleValueProvidersMenu = target => setValueProvidersMenuAnchorEl(target instanceof Element ? target : null);
|
||||||
|
|
||||||
|
//При очистке значения/связывания параметра
|
||||||
|
const handleParamClearClick = () => setState(pv => ({ ...pv, name: "" }));
|
||||||
|
|
||||||
|
//При отображении меню связывания параметра с поставщиком данных
|
||||||
|
const handleParamLinkMenuClick = e => setValueProvidersMenuAnchorEl(e.currentTarget);
|
||||||
|
|
||||||
|
//При выборе элемента меню связывания аргумента с поставщиком данных
|
||||||
|
const handleParamLinkClick = valueSource => {
|
||||||
|
//Устанавливаем выбранный параметр
|
||||||
|
setState(pv => ({ ...pv, name: valueSource }));
|
||||||
|
//Закрываем меню
|
||||||
|
toggleValueProvidersMenu();
|
||||||
|
};
|
||||||
|
|
||||||
|
//Список значений
|
||||||
|
const values = Object.keys(valueProviders);
|
||||||
|
|
||||||
|
//Наличие значений
|
||||||
|
const isValues = values && values.length > 0 ? true : false;
|
||||||
|
|
||||||
|
//Меню привязки к поставщикам значений
|
||||||
|
const valueProvidersMenu = isValues && (
|
||||||
|
<Menu anchorEl={valueProvidersMenuAnchorEl} open={Boolean(valueProvidersMenuAnchorEl)} onClose={toggleValueProvidersMenu}>
|
||||||
|
{values.map((value, i) => (
|
||||||
|
<MenuItem key={i} onClick={() => handleParamLinkClick(value)}>
|
||||||
|
{value}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<P8PConfigDialog title={`${item ? "Изменение" : "Добавление"} элемента`} onOk={handleOk} onCancel={handleCancel}>
|
<P8PConfigDialog
|
||||||
|
title={`${item ? TITLES.UPDATE : TITLES.INSERT} элемента`}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
okDisabled={isItemOkDisabled(state)}
|
||||||
|
>
|
||||||
<Stack direction={"column"} spacing={1}>
|
<Stack direction={"column"} spacing={1}>
|
||||||
<TextField type={"text"} variant={"standard"} value={state.name} label={"Имя"} id={"name"} onChange={handleChange} />
|
{valueProvidersMenu}
|
||||||
|
<TextField
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={state.name}
|
||||||
|
label={"Имя"}
|
||||||
|
id={"name"}
|
||||||
|
required={true}
|
||||||
|
onChange={handleChange}
|
||||||
|
InputProps={{
|
||||||
|
readOnly: true,
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={handleParamClearClick}>
|
||||||
|
<Icon>clear</Icon>
|
||||||
|
</IconButton>
|
||||||
|
{isValues && (
|
||||||
|
<IconButton onClick={handleParamLinkMenuClick}>
|
||||||
|
<Icon>settings_ethernet</Icon>
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<TextField type={"text"} variant={"standard"} value={state.caption} label={"Приглашение"} id={"caption"} onChange={handleChange} />
|
<TextField type={"text"} variant={"standard"} value={state.caption} label={"Приглашение"} id={"caption"} onChange={handleChange} />
|
||||||
<TextField
|
<TextField
|
||||||
type={"text"}
|
type={"text"}
|
||||||
@ -182,6 +250,7 @@ const ItemEditor = ({ item = null, onOk = null, onCancel = null } = {}) => {
|
|||||||
//Контроль свойств - редактор элемента
|
//Контроль свойств - редактор элемента
|
||||||
ItemEditor.propTypes = {
|
ItemEditor.propTypes = {
|
||||||
item: ITEM_SHAPE,
|
item: ITEM_SHAPE,
|
||||||
|
valueProviders: PropTypes.object,
|
||||||
onOk: PropTypes.func,
|
onOk: PropTypes.func,
|
||||||
onCancel: PropTypes.func
|
onCancel: PropTypes.func
|
||||||
};
|
};
|
||||||
@ -191,13 +260,18 @@ ItemEditor.propTypes = {
|
|||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Форма (редактор настроек)
|
//Форма (редактор настроек)
|
||||||
const FormEditor = ({ id, title = "", orientation = ORIENTATION.V, autoApply = false, items = ITEMS_INITIAL, onSettingsChange = null } = {}) => {
|
const FormEditor = ({
|
||||||
|
id,
|
||||||
|
title = "",
|
||||||
|
orientation = ORIENTATION.V,
|
||||||
|
autoApply = false,
|
||||||
|
items = ITEMS_INITIAL,
|
||||||
|
valueProviders = {},
|
||||||
|
onSettingsChange = null
|
||||||
|
} = {}) => {
|
||||||
//Собственное состояние - текущие настройки
|
//Собственное состояние - текущие настройки
|
||||||
const [settings, setSettings] = useState(null);
|
const [settings, setSettings] = useState(null);
|
||||||
|
|
||||||
//Собственное состояние - предоставляемые в панель значения
|
|
||||||
const [providedValues, setProvidedValues] = useState([]);
|
|
||||||
|
|
||||||
//Собственное состояние - редактор элементов формы
|
//Собственное состояние - редактор элементов формы
|
||||||
const [itemEditor, setItemEditor] = useState({ display: false, index: null });
|
const [itemEditor, setItemEditor] = useState({ display: false, index: null });
|
||||||
|
|
||||||
@ -229,18 +303,13 @@ const FormEditor = ({ id, title = "", orientation = ORIENTATION.V, autoApply = f
|
|||||||
const handleItemCancel = () => setItemEditor({ display: false, index: null });
|
const handleItemCancel = () => setItemEditor({ display: false, index: null });
|
||||||
|
|
||||||
//При сохранении настроек
|
//При сохранении настроек
|
||||||
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, providedValues, closeEditor });
|
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||||
|
|
||||||
//При изменении компонента
|
//При изменении компонента
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
settings?.id != id && setSettings({ id, title, orientation, autoApply, items });
|
settings?.id != id && setSettings({ id, title, orientation, autoApply, items });
|
||||||
}, [settings, id, title, orientation, autoApply, items]);
|
}, [settings, id, title, orientation, autoApply, items]);
|
||||||
|
|
||||||
//При изменении состава элементов формы
|
|
||||||
useEffect(() => {
|
|
||||||
Array.isArray(settings?.items) && setProvidedValues(settings.items.map(item => item.name));
|
|
||||||
}, [settings?.items]);
|
|
||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
settings && (
|
settings && (
|
||||||
@ -248,6 +317,7 @@ const FormEditor = ({ id, title = "", orientation = ORIENTATION.V, autoApply = f
|
|||||||
{itemEditor.display && (
|
{itemEditor.display && (
|
||||||
<ItemEditor
|
<ItemEditor
|
||||||
item={itemEditor.index !== null ? { ...settings.items[itemEditor.index] } : null}
|
item={itemEditor.index !== null ? { ...settings.items[itemEditor.index] } : null}
|
||||||
|
valueProviders={valueProviders}
|
||||||
onCancel={handleItemCancel}
|
onCancel={handleItemCancel}
|
||||||
onOk={handleItemSave}
|
onOk={handleItemSave}
|
||||||
/>
|
/>
|
||||||
@ -299,6 +369,7 @@ FormEditor.propTypes = {
|
|||||||
orientation: PropTypes.oneOf(Object.values(ORIENTATION)),
|
orientation: PropTypes.oneOf(Object.values(ORIENTATION)),
|
||||||
autoApply: PropTypes.bool,
|
autoApply: PropTypes.bool,
|
||||||
items: PropTypes.arrayOf(ITEM_SHAPE),
|
items: PropTypes.arrayOf(ITEM_SHAPE),
|
||||||
|
valueProviders: PropTypes.object,
|
||||||
onSettingsChange: PropTypes.func
|
onSettingsChange: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -13,11 +13,17 @@ import { Paper, Stack, Typography, Icon, TextField, IconButton, InputAdornment }
|
|||||||
import { ApplicationСtx } from "../../../../context/application"; //Контекст приложения
|
import { ApplicationСtx } from "../../../../context/application"; //Контекст приложения
|
||||||
import { P8P_COMPONENT_INLINE_MESSAGE, P8PComponentInlineMessage } from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
import { P8P_COMPONENT_INLINE_MESSAGE, P8PComponentInlineMessage } from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
||||||
import { ITEM_SHAPE, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы
|
import { ITEM_SHAPE, ITEMS_INITIAL, ORIENTATION } from "./common"; //Общие ресурсы и константы формы
|
||||||
|
import { APP_STYLES } from "../../../../../app.styles";
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
//---------
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CONTAINER: { overflow: "auto", ...APP_STYLES.SCROLL }
|
||||||
|
};
|
||||||
|
|
||||||
//Иконка компонента
|
//Иконка компонента
|
||||||
const COMPONENT_ICON = "fact_check";
|
const COMPONENT_ICON = "fact_check";
|
||||||
|
|
||||||
@ -116,7 +122,7 @@ const Form = ({ title = null, orientation = ORIENTATION.V, autoApply = false, it
|
|||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<Paper className={`component-view__container ${!haveConfing && "component-view__container__empty"}`} elevation={6}>
|
<Paper className={`component-view__container ${!haveConfing && "component-view__container__empty"}`} sx={STYLES.CONTAINER} elevation={6}>
|
||||||
{haveConfing ? (
|
{haveConfing ? (
|
||||||
<Stack direction={"column"}>
|
<Stack direction={"column"}>
|
||||||
<Stack direction={"row"} justifyContent={"space-between"} alignItems={"center"}>
|
<Stack direction={"row"} justifyContent={"space-between"} alignItems={"center"}>
|
||||||
|
|||||||
39
app/panels/panels_editor/components/indicator/action.js
Normal file
39
app/panels/panels_editor/components/indicator/action.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Индикатор (общие ресурсы действия)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Доступные области действий индикатора
|
||||||
|
const P8P_CA_INDICATOR_ACTION_AREAS = [
|
||||||
|
{ name: "Компонент", area: "component", hasElement: false },
|
||||||
|
{ name: "Заголовок", area: "inner_caption", hasElement: false },
|
||||||
|
{ name: "Значение", area: "inner_value", hasElement: false }
|
||||||
|
];
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Считывание обработчиков индикатора
|
||||||
|
const getIndicatorHandlers = handlers => {
|
||||||
|
//Возвращаем объект хэндлеров (имеют формат "[Область].[Сегмент]", где [Сегмент] - необязательный)
|
||||||
|
return {
|
||||||
|
onComponentClick: handlers["component."]?.fn,
|
||||||
|
onCaptionClick: handlers["inner_caption."]?.fn,
|
||||||
|
onValueClick: handlers["inner_value."]?.fn
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8P_CA_INDICATOR_ACTION_AREAS, getIndicatorHandlers };
|
||||||
33
app/panels/panels_editor/components/indicator/conditions.js
Normal file
33
app/panels/panels_editor/components/indicator/conditions.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Индикатор (общие ресурсы условия)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Доступные поля условий индикатора
|
||||||
|
const P8P_CC_INDICATOR_COND_FIELDS = [
|
||||||
|
{ name: "Состояние", value: "state", hasElement: false },
|
||||||
|
{ name: "Значение", value: "value", hasElement: false },
|
||||||
|
{ name: "Подсказка", value: "hint", hasElement: false }
|
||||||
|
];
|
||||||
|
|
||||||
|
//Доступные поля результата условия индикатора
|
||||||
|
const P8P_CC_INDICATOR_RES_FIELDS = [
|
||||||
|
{ name: "Подпись", value: "caption", hasElement: false, icon: "rtt" },
|
||||||
|
{ name: "Значение", value: "value", hasElement: false, icon: "pin" },
|
||||||
|
{ name: "Цвет заливки", value: "backgroundColor", hasElement: false, icon: "color_lens" },
|
||||||
|
{ name: "Цвет шрифта", value: "color", hasElement: false, icon: "format_color_text" }
|
||||||
|
];
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8P_CC_INDICATOR_COND_FIELDS, P8P_CC_INDICATOR_RES_FIELDS };
|
||||||
@ -13,32 +13,69 @@ import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //
|
|||||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||||
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
||||||
|
import { P8PActions } from "../../../../components/editors/p8p_actions"; //Действия
|
||||||
|
import { P8PConditions } from "../../../../components/editors/p8p_conditions"; //Условия
|
||||||
|
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||||
|
import { P8P_CCS_INITIAL, P8P_CC_SHAPE } from "../../../../components/editors/p8p_component_condition/common"; //Общие ресурсы компонента "Редактор условия"
|
||||||
|
import { P8P_CA_INDICATOR_ACTION_AREAS } from "./action"; //Общие ресурсы действий индикатора
|
||||||
|
import { P8P_CC_INDICATOR_COND_FIELDS, P8P_CC_INDICATOR_RES_FIELDS } from "./conditions"; //Общие ресурсы условий индикатора
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Индикатор (редактор настроек)
|
//Индикатор (редактор настроек)
|
||||||
const IndicatorEditor = ({ id, dataSource = null, valueProviders = {}, onSettingsChange = null } = {}) => {
|
const IndicatorEditor = ({
|
||||||
|
id,
|
||||||
|
dataSource = null,
|
||||||
|
valueProviders = {},
|
||||||
|
conditions = P8P_CCS_INITIAL,
|
||||||
|
actions = P8P_CAS_INITIAL,
|
||||||
|
onSettingsChange = null
|
||||||
|
} = {}) => {
|
||||||
//Собственное состояние - текущие настройки
|
//Собственное состояние - текущие настройки
|
||||||
const [settings, setSettings] = useState(null);
|
const [settings, setSettings] = useState(null);
|
||||||
|
|
||||||
//При изменении компонента
|
|
||||||
useEffect(() => {
|
|
||||||
settings?.id != id && setSettings({ id, dataSource });
|
|
||||||
}, [settings, id, dataSource]);
|
|
||||||
|
|
||||||
//При сохранении изменений элемента
|
//При сохранении изменений элемента
|
||||||
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
|
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
|
||||||
|
|
||||||
|
//При изменении действий
|
||||||
|
const handleActionsChange = actions => setSettings(pv => ({ ...pv, actions }));
|
||||||
|
|
||||||
|
//При изменении условий
|
||||||
|
const handleConditionsChange = conditions => setSettings(pv => ({ ...pv, conditions }));
|
||||||
|
|
||||||
//При сохранении настроек
|
//При сохранении настроек
|
||||||
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||||
|
|
||||||
|
//При изменении компонента
|
||||||
|
useEffect(() => {
|
||||||
|
settings?.id != id && setSettings({ id, dataSource, conditions, actions });
|
||||||
|
}, [settings, id, dataSource, conditions, actions]);
|
||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<P8PEditorBox title={"Параметры индикатора"} onSave={handleSave}>
|
<P8PEditorBox title={"Параметры индикатора"} onSave={handleSave}>
|
||||||
<P8PEditorSubHeader title={"Источник данных"} />
|
<P8PEditorSubHeader title={"Источник данных"} />
|
||||||
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
||||||
|
<P8PEditorSubHeader title={"Условия"} />
|
||||||
|
<P8PConditions
|
||||||
|
conditions={settings?.conditions}
|
||||||
|
condFields={P8P_CC_INDICATOR_COND_FIELDS}
|
||||||
|
resFields={P8P_CC_INDICATOR_RES_FIELDS}
|
||||||
|
onChange={handleConditionsChange}
|
||||||
|
/>
|
||||||
|
<P8PEditorSubHeader title={"Действия"} />
|
||||||
|
<P8PActions
|
||||||
|
actions={settings?.actions}
|
||||||
|
valueProviders={valueProviders}
|
||||||
|
areas={P8P_CA_INDICATOR_ACTION_AREAS}
|
||||||
|
onChange={handleActionsChange}
|
||||||
|
/>
|
||||||
</P8PEditorBox>
|
</P8PEditorBox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -48,6 +85,8 @@ IndicatorEditor.propTypes = {
|
|||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||||
valueProviders: PropTypes.object,
|
valueProviders: PropTypes.object,
|
||||||
|
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||||
|
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||||
onSettingsChange: PropTypes.func
|
onSettingsChange: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -11,13 +11,17 @@ import React from "react"; //Классы React
|
|||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { Paper } from "@mui/material"; //Интерфейсные элементы
|
import { Paper } from "@mui/material"; //Интерфейсные элементы
|
||||||
import { P8PIndicator } from "../../../../components/p8p_indicator"; //Компонент индикатора
|
import { P8PIndicator } from "../../../../components/p8p_indicator"; //Компонент индикатора
|
||||||
import { useDataSource } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
import { useDataSource, useConditions, useComponentHandlers } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
||||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||||
import {
|
import {
|
||||||
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
||||||
P8P_COMPONENT_INLINE_MESSAGE,
|
P8P_COMPONENT_INLINE_MESSAGE,
|
||||||
P8PComponentInlineMessage
|
P8PComponentInlineMessage
|
||||||
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
||||||
|
import { P8P_CC_SHAPE } from "../../../../components/editors/p8p_component_condition/common"; //Общие ресурсы условий
|
||||||
|
import { P8P_CA_SHAPE } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||||
|
import { P8P_COMPONENT_SETTINGS_RESP_ARGS } from "../../../../components/editors/p8p_component_settings"; //Дополнительный функционал источников данных
|
||||||
|
import { getIndicatorHandlers } from "./action"; //Общие ресурсы действий индикатора
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -31,7 +35,14 @@ const COMPONENT_NAME = "Индикатор";
|
|||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
CONTAINER: { height: "100%" }
|
CONTAINER: clickable => ({
|
||||||
|
height: "100%",
|
||||||
|
...(clickable
|
||||||
|
? {
|
||||||
|
cursor: "pointer"
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
@ -39,29 +50,39 @@ const STYLES = {
|
|||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Индикатор (представление)
|
//Индикатор (представление)
|
||||||
const Indicator = ({ dataSource = null, values = {} } = {}) => {
|
const Indicator = ({ dataSource = null, values = {}, conditions = [], actions = [], onValuesChange = null }) => {
|
||||||
//Собственное состояние - данные
|
//Собственное состояние - данные
|
||||||
const [data, error] = useDataSource({ dataSource, values });
|
const [indicator, error, haveConfing, haveData] = useDataSource({
|
||||||
|
dataSource,
|
||||||
|
values,
|
||||||
|
componentRespArg: P8P_COMPONENT_SETTINGS_RESP_ARGS.INDICATOR
|
||||||
|
});
|
||||||
|
|
||||||
//Флаг настроенности индикатора
|
//Собственное состояние - данные с учетом условий
|
||||||
const haveConfing = dataSource?.stored ? true : false;
|
const [indicatorOrigin] = useConditions({ componentData: indicator, conditions });
|
||||||
|
|
||||||
//Флаг наличия данных
|
//Собственное состояние - обработчики компонента
|
||||||
const haveData = data?.init === true && !error ? true : false;
|
const [handlers] = useComponentHandlers({ actions, onValuesChange });
|
||||||
|
|
||||||
//Данные индикатора
|
//Обработчики областей
|
||||||
const indicator = data?.XINDICATOR || {};
|
const { onComponentClick, onCaptionClick, onValueClick } = getIndicatorHandlers(handlers);
|
||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
{...(haveConfing && haveData
|
{...(haveConfing && haveData
|
||||||
? { sx: { ...STYLES.CONTAINER } }
|
? { sx: { ...STYLES.CONTAINER(onComponentClick) } }
|
||||||
: { className: "component-view__container component-view__container__empty" })}
|
: { className: "component-view__container component-view__container__empty" })}
|
||||||
elevation={6}
|
elevation={6}
|
||||||
|
onClick={event => onComponentClick && onComponentClick({ event, values })}
|
||||||
>
|
>
|
||||||
{haveConfing && haveData ? (
|
{haveConfing && haveData ? (
|
||||||
<P8PIndicator {...indicator} elevation={0} />
|
<P8PIndicator
|
||||||
|
{...indicatorOrigin}
|
||||||
|
onValueClick={onValueClick ? event => onValueClick({ event, values }) : null}
|
||||||
|
onCaptionClick={onCaptionClick ? event => onCaptionClick({ event, values }) : null}
|
||||||
|
elevation={0}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<P8PComponentInlineMessage
|
<P8PComponentInlineMessage
|
||||||
icon={COMPONENT_ICON}
|
icon={COMPONENT_ICON}
|
||||||
@ -77,7 +98,10 @@ const Indicator = ({ dataSource = null, values = {} } = {}) => {
|
|||||||
//Контроль свойств компонента - Индикатор (представление)
|
//Контроль свойств компонента - Индикатор (представление)
|
||||||
Indicator.propTypes = {
|
Indicator.propTypes = {
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||||
values: PropTypes.object
|
values: PropTypes.object,
|
||||||
|
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||||
|
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||||
|
onValuesChange: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
|
|||||||
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компонент: Диалог добавления/исправления панели
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { P8PDialog } from "../../../../components/p8p_dialog"; //Типовой диалог
|
||||||
|
import { TITLES } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Диалог добавления/исправления панели
|
||||||
|
const PanelIUDialog = ({ code = "", name = "", insert = true, onOk, onCancel }) => {
|
||||||
|
//Нажатие на кнопку "Ok"
|
||||||
|
const handleOk = values => onOk && onOk({ ...values });
|
||||||
|
|
||||||
|
//Нажатие на кнопку "Отмена"
|
||||||
|
const handleCancel = () => onCancel && onCancel();
|
||||||
|
|
||||||
|
//Генерация содержимого
|
||||||
|
return (
|
||||||
|
<P8PDialog
|
||||||
|
title={`${insert === true ? TITLES.INSERT : TITLES.UPDATE} панели`}
|
||||||
|
inputs={[
|
||||||
|
{ name: "code", value: code, label: "Мнемокод" },
|
||||||
|
{ name: "name", value: name, label: "Наименование" }
|
||||||
|
]}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Диалог добавления/исправления панели
|
||||||
|
PanelIUDialog.propTypes = {
|
||||||
|
code: PropTypes.string,
|
||||||
|
name: PropTypes.string,
|
||||||
|
insert: PropTypes.bool,
|
||||||
|
onOk: PropTypes.func,
|
||||||
|
onCancel: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { PanelIUDialog };
|
||||||
@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компонент: Список панелей
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Stack, List, ListItem, IconButton, Icon, ListItemButton, ListItemText, Typography } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||||
|
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||||
|
import { APP_STYLES } from "../../../../../app.styles"; //Типовые стили
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
LIST_PANELS: { height: "500px", width: "360px", bgcolor: "background.paper", overflowY: "auto", ...APP_STYLES.SCROLL },
|
||||||
|
SMALL_TOOL_ICON: {
|
||||||
|
fontSize: 20
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Структура данных о сущности панели
|
||||||
|
const PANELS_LIST_ITEM_SHAPE = PropTypes.shape({
|
||||||
|
rn: PropTypes.number.isRequired,
|
||||||
|
code: PropTypes.string.isRequired,
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
author: PropTypes.string.isRequired,
|
||||||
|
chDate: PropTypes.string.isRequired,
|
||||||
|
modify: PropTypes.oneOf([0, 1]).isRequired,
|
||||||
|
pbl: PropTypes.oneOf([0, 1]).isRequired,
|
||||||
|
ready: PropTypes.oneOf([0, 1]).isRequired
|
||||||
|
});
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Диалог открытия панели
|
||||||
|
const PanelsList = ({
|
||||||
|
panels = [],
|
||||||
|
isEditable = true,
|
||||||
|
current = null,
|
||||||
|
onSelect = null,
|
||||||
|
onPbl = null,
|
||||||
|
onReady = null,
|
||||||
|
onEdit = null,
|
||||||
|
onDelete = null
|
||||||
|
} = {}) => {
|
||||||
|
//При выборе элемента списка
|
||||||
|
const handleSelectClick = query => {
|
||||||
|
onSelect && onSelect(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на общедоступность
|
||||||
|
const handlePblClick = (e, query) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onPbl && onPbl(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на готовность
|
||||||
|
const handleReadyClick = (e, query) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onReady && onReady(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на исправлении
|
||||||
|
const handleEditClick = (e, query) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onEdit && onEdit(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на удаление
|
||||||
|
const handleDeleteClick = (e, query) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDelete && onDelete(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<List sx={STYLES.LIST_PANELS}>
|
||||||
|
{panels.map((panel, i) => {
|
||||||
|
const selected = panel.rn === current || panel.code === current;
|
||||||
|
const disabled = !panel.modify;
|
||||||
|
const pblTitle = `${panel.pbl === 1 ? "Общедоступный" : "Приватный"}${!disabled ? " - нажмите, чтобы изменить" : ""}`;
|
||||||
|
const pblIcon = panel.pbl === 1 ? "groups" : "lock_person";
|
||||||
|
const readyTitle = `${panel.ready === 1 ? "Готов" : "Не готов"}${!disabled ? " - нажмите, чтобы изменить" : ""}`;
|
||||||
|
const readyIcon = panel.ready === 1 ? "touch_app" : "do_not_touch";
|
||||||
|
return (
|
||||||
|
<ListItem key={i}>
|
||||||
|
<ListItemButton onClick={() => handleSelectClick(panel)} selected={selected}>
|
||||||
|
<ListItemText
|
||||||
|
primary={panel.name}
|
||||||
|
secondaryTypographyProps={{ component: "div" }}
|
||||||
|
secondary={
|
||||||
|
<Stack direction={"column"}>
|
||||||
|
<Typography variant={"caption"}>{`${panel.code}, ${panel.author}, ${panel.chDate}`}</Typography>
|
||||||
|
{isEditable ? (
|
||||||
|
<Stack direction={"row"}>
|
||||||
|
<div title={pblTitle}>
|
||||||
|
<IconButton disabled={disabled} onClick={e => handlePblClick(e, panel)}>
|
||||||
|
<Icon sx={STYLES.SMALL_TOOL_ICON}>{pblIcon}</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
<div title={readyTitle}>
|
||||||
|
<IconButton disabled={disabled} onClick={e => handleReadyClick(e, panel)}>
|
||||||
|
<Icon sx={STYLES.SMALL_TOOL_ICON}>{readyIcon}</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</div>
|
||||||
|
</Stack>
|
||||||
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{isEditable ? (
|
||||||
|
<Stack direction={"row"}>
|
||||||
|
<IconButton onClick={e => handleEditClick(e, panel)} disabled={disabled} title={BUTTONS.UPDATE}>
|
||||||
|
<Icon>edit</Icon>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={e => handleDeleteClick(e, panel)} disabled={disabled} title={BUTTONS.DELETE}>
|
||||||
|
<Icon>delete</Icon>
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
) : null}
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Список панелей
|
||||||
|
PanelsList.propTypes = {
|
||||||
|
panels: PropTypes.arrayOf(PANELS_LIST_ITEM_SHAPE),
|
||||||
|
current: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
isEditable: PropTypes.bool,
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
onPbl: PropTypes.func,
|
||||||
|
onReady: PropTypes.func,
|
||||||
|
onEdit: PropTypes.func,
|
||||||
|
onDelete: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { PanelsList };
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компонент: Менеджер панелей
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState, useContext } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Box, Button, Icon } from "@mui/material"; //Интерфейсные компоненты MUI
|
||||||
|
import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений
|
||||||
|
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы приложения
|
||||||
|
import { P8PConfigDialog } from "../../../../components/editors/p8p_config_dialog"; //Типовой диалог настройки
|
||||||
|
import { usePanelManager } from "../components_hooks"; //Пользовательские хуки
|
||||||
|
import { PanelsList } from "./panels_list"; //Список панелей
|
||||||
|
import { PanelIUDialog } from "./panel_iu_dialog"; //Диалог добавления/исправления панели
|
||||||
|
import { ImportPanelButton } from "../../layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Менеджер панелей
|
||||||
|
const PanelsManager = ({ current = null, isEditable = true, onPanelSelect = null, onCancel = null } = {}) => {
|
||||||
|
//Собственное состояние - изменяемая панель
|
||||||
|
const [modPanel, setModPanel] = useState(null);
|
||||||
|
|
||||||
|
//Работа со списком панелей
|
||||||
|
const [panels, insertPanel, updatePanel, deletePanel, setPanelReady, setPanelPbl, importPanel] = usePanelManager();
|
||||||
|
|
||||||
|
//Подключение к контексту сообщений
|
||||||
|
const { showMsgWarn } = useContext(MessagingСtx);
|
||||||
|
|
||||||
|
//При добавлении панели
|
||||||
|
const handlePanelAdd = () => setModPanel(true);
|
||||||
|
|
||||||
|
//При загрузке панели из файла
|
||||||
|
const handlePanelImport = fileData => {
|
||||||
|
importPanel(fileData);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При выборе панели
|
||||||
|
const handlePanelSelect = panel => onPanelSelect && onPanelSelect(panel);
|
||||||
|
|
||||||
|
//При установке признака публичности
|
||||||
|
const handlePanelPblSet = panel => setPanelPbl(panel.rn, panel.pbl === 1 ? 0 : 1);
|
||||||
|
|
||||||
|
//При установке признака готовности
|
||||||
|
const handlePanelReadySet = panel => setPanelReady(panel.rn, panel.ready === 1 ? 0 : 1);
|
||||||
|
|
||||||
|
//При исправлении панели
|
||||||
|
const handlePanelEdit = panel => setModPanel({ ...panel });
|
||||||
|
|
||||||
|
//При удалении панели
|
||||||
|
const handlePanelDelete = panel => showMsgWarn("Удалить панель?", () => deletePanel(panel.rn));
|
||||||
|
|
||||||
|
//При закрытии диалога добавления/исправления по "ОК"
|
||||||
|
const handleIUDialogOk = async values => {
|
||||||
|
if (modPanel === true) await insertPanel(values.code, values.name);
|
||||||
|
else await updatePanel(modPanel.rn, values.code, values.name);
|
||||||
|
setModPanel(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При закрытии диалога добавления/исправления по "Отмена"
|
||||||
|
const handleIUDialogCancel = () => setModPanel(null);
|
||||||
|
|
||||||
|
//При закрытии менеджера отменой
|
||||||
|
const handleCancel = () => onCancel && onCancel();
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<P8PConfigDialog title={"Панели"} onCancel={handleCancel}>
|
||||||
|
{modPanel && (
|
||||||
|
<PanelIUDialog
|
||||||
|
code={modPanel?.code}
|
||||||
|
name={modPanel?.name}
|
||||||
|
insert={modPanel === true}
|
||||||
|
onOk={handleIUDialogOk}
|
||||||
|
onCancel={handleIUDialogCancel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isEditable ? (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
|
||||||
|
<Button startIcon={<Icon>add</Icon>} onClick={handlePanelAdd}>
|
||||||
|
{BUTTONS.INSERT}
|
||||||
|
</Button>
|
||||||
|
<ImportPanelButton id={"input-file-loader-manager"} startIcon={"file_upload"} title={"Загрузить"} onClick={handlePanelImport} />
|
||||||
|
</Box>
|
||||||
|
) : null}
|
||||||
|
<PanelsList
|
||||||
|
panels={panels || []}
|
||||||
|
current={current}
|
||||||
|
isEditable={isEditable}
|
||||||
|
onSelect={handlePanelSelect}
|
||||||
|
onPbl={handlePanelPblSet}
|
||||||
|
onReady={handlePanelReadySet}
|
||||||
|
onEdit={handlePanelEdit}
|
||||||
|
onDelete={handlePanelDelete}
|
||||||
|
/>
|
||||||
|
</P8PConfigDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Менеджер панелей
|
||||||
|
PanelsManager.propTypes = {
|
||||||
|
current: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
isEditable: PropTypes.bool,
|
||||||
|
onPanelSelect: PropTypes.func,
|
||||||
|
onCancel: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { PanelsManager };
|
||||||
58
app/panels/panels_editor/components/table/action.js
Normal file
58
app/panels/panels_editor/components/table/action.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Таблица (общие ресурсы действия)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Собственные значения типа действия
|
||||||
|
const P8P_CA_TABLE_TYPE_VALUE = {
|
||||||
|
COLUMN_VALUE: "Значение графы"
|
||||||
|
};
|
||||||
|
|
||||||
|
//Типы значений действий таблицы
|
||||||
|
const P8P_CA_TABLE_VALUE_TYPES = [...Object.values(P8P_CA_TABLE_TYPE_VALUE)];
|
||||||
|
|
||||||
|
//Доступные области действий таблицы
|
||||||
|
const P8P_CA_TABLE_ACTION_AREAS = [
|
||||||
|
{ name: "Компонент", area: "component", hasElement: false },
|
||||||
|
{ name: "Графа таблицы", area: "column", hasElement: true },
|
||||||
|
{ name: "Строка таблицы", area: "row", hasElement: false }
|
||||||
|
];
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Получение значения собственного типа действия
|
||||||
|
const getDataGridCustomTypeValue = ({ type, value, prms }) => {
|
||||||
|
//Если это значение графы - возвращаем нужное значение, иначе - null
|
||||||
|
return type === P8P_CA_TABLE_TYPE_VALUE.COLUMN_VALUE ? prms.row[value] : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Считывание обработчиков таблицы
|
||||||
|
const getDataGridHandlers = (handlers, element = null) => {
|
||||||
|
//Возвращаем объект хэндлеров (имеют формат "[Область].[Сегмент]", где [Сегмент] - необязательный)
|
||||||
|
return {
|
||||||
|
onComponentClick: handlers["component."]?.fn,
|
||||||
|
onRowClick: handlers["row."]?.fn,
|
||||||
|
onColumnClick: element ? handlers[`column.${element}`]?.fn : null
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
//Проверка наличия обработчиков ячеек
|
||||||
|
const isHasCellRender = (handlers, conditions) => {
|
||||||
|
return Object.keys(handlers).some(handler => handler.startsWith("column.") || handler === "row.") || conditions.length !== 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8P_CA_TABLE_VALUE_TYPES, P8P_CA_TABLE_ACTION_AREAS, getDataGridCustomTypeValue, getDataGridHandlers, isHasCellRender };
|
||||||
38
app/panels/panels_editor/components/table/conditions.js
Normal file
38
app/panels/panels_editor/components/table/conditions.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Компоненты: Индикатор (общие ресурсы условия)
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
import { getConditionsValues } from "../../../../components/editors/p8p_component_condition/util"; //Вспомогательные ресурсы условий
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Доступные поля условий таблицы
|
||||||
|
const P8P_CC_TABLE_COND_FIELDS = [{ name: "Графа", value: "column", hasElement: true }];
|
||||||
|
|
||||||
|
//Доступные поля результата условия таблицы
|
||||||
|
const P8P_CC_TABLE_RES_FIELDS = [
|
||||||
|
{ name: "Цвет заливки", value: "backgroundColor", hasElement: true, icon: "color_lens" },
|
||||||
|
{ name: "Цвет шрифта", value: "color", hasElement: true, icon: "format_color_text" }
|
||||||
|
];
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Считывание стилей колонки по условиям
|
||||||
|
const getColumnStylesByConditions = (conditions, row, columnName) => {
|
||||||
|
//Считываем стили строки
|
||||||
|
return getConditionsValues(row, conditions, columnName);
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { P8P_CC_TABLE_COND_FIELDS, P8P_CC_TABLE_RES_FIELDS, getColumnStylesByConditions };
|
||||||
@ -13,32 +13,66 @@ import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //
|
|||||||
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||||
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
import { P8PDataSource } from "../../../../components/editors/p8p_data_source"; //Источник данных
|
||||||
|
import { P8PActions } from "../../../../components/editors/p8p_actions"; //Действия
|
||||||
|
import { P8PConditions } from "../../../../components/editors/p8p_conditions"; //Условия
|
||||||
|
import { P8P_CA_TABLE_VALUE_TYPES, P8P_CA_TABLE_ACTION_AREAS } from "./action"; //Общие ресурсы действий таблицы
|
||||||
|
import { P8P_CC_TABLE_COND_FIELDS, P8P_CC_TABLE_RES_FIELDS } from "./conditions"; //Общие ресурсы условий таблицы
|
||||||
|
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||||
|
import { P8P_CCS_INITIAL, P8P_CC_SHAPE } from "../../../../components/editors/p8p_component_condition/common"; //Общие ресурсы компонента "Редактор условия"
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Таблица (редактор настроек)
|
//Таблица (редактор настроек)
|
||||||
const TableEditor = ({ id, dataSource = null, valueProviders = {}, onSettingsChange = null } = {}) => {
|
const TableEditor = ({
|
||||||
|
id,
|
||||||
|
dataSource = null,
|
||||||
|
valueProviders = {},
|
||||||
|
conditions = P8P_CCS_INITIAL,
|
||||||
|
actions = P8P_CAS_INITIAL,
|
||||||
|
onSettingsChange = null
|
||||||
|
} = {}) => {
|
||||||
//Собственное состояние - текущие настройки
|
//Собственное состояние - текущие настройки
|
||||||
const [settings, setSettings] = useState(null);
|
const [settings, setSettings] = useState(null);
|
||||||
|
|
||||||
//При изменении компонента
|
|
||||||
useEffect(() => {
|
|
||||||
settings?.id != id && setSettings({ id, dataSource });
|
|
||||||
}, [settings, id, dataSource]);
|
|
||||||
|
|
||||||
//При сохранении изменений элемента
|
//При сохранении изменений элемента
|
||||||
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
|
const handleDataSourceChange = dataSource => setSettings(pv => ({ ...pv, dataSource: { ...dataSource } }));
|
||||||
|
|
||||||
|
//При изменении действий
|
||||||
|
const handleActionsChange = actions => setSettings(pv => ({ ...pv, actions }));
|
||||||
|
|
||||||
|
//При изменении условий
|
||||||
|
const handleConditionsChange = conditions => setSettings(pv => ({ ...pv, conditions }));
|
||||||
|
|
||||||
//При сохранении настроек
|
//При сохранении настроек
|
||||||
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
const handleSave = (closeEditor = false) => onSettingsChange && onSettingsChange({ id, settings, closeEditor });
|
||||||
|
|
||||||
|
//При изменении компонента
|
||||||
|
useEffect(() => {
|
||||||
|
settings?.id != id && setSettings({ id, dataSource, conditions, actions });
|
||||||
|
}, [settings, id, dataSource, conditions, actions]);
|
||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<P8PEditorBox title={"Параметры таблицы"} onSave={handleSave}>
|
<P8PEditorBox title={"Параметры таблицы"} onSave={handleSave}>
|
||||||
<P8PEditorSubHeader title={"Источник данных"} />
|
<P8PEditorSubHeader title={"Источник данных"} />
|
||||||
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
<P8PDataSource dataSource={settings?.dataSource} valueProviders={valueProviders} onChange={handleDataSourceChange} />
|
||||||
|
<P8PEditorSubHeader title={"Условия"} />
|
||||||
|
<P8PConditions
|
||||||
|
conditions={settings?.conditions}
|
||||||
|
condFields={P8P_CC_TABLE_COND_FIELDS}
|
||||||
|
resFields={P8P_CC_TABLE_RES_FIELDS}
|
||||||
|
onChange={handleConditionsChange}
|
||||||
|
/>
|
||||||
|
<P8PEditorSubHeader title={"Действия"} />
|
||||||
|
<P8PActions
|
||||||
|
actions={settings?.actions}
|
||||||
|
valueProviders={valueProviders}
|
||||||
|
areas={P8P_CA_TABLE_ACTION_AREAS}
|
||||||
|
valueTypes={P8P_CA_TABLE_VALUE_TYPES}
|
||||||
|
onChange={handleActionsChange}
|
||||||
|
/>
|
||||||
</P8PEditorBox>
|
</P8PEditorBox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -48,6 +82,8 @@ TableEditor.propTypes = {
|
|||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||||
valueProviders: PropTypes.object,
|
valueProviders: PropTypes.object,
|
||||||
|
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||||
|
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||||
onSettingsChange: PropTypes.func
|
onSettingsChange: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -9,17 +9,22 @@
|
|||||||
|
|
||||||
import React from "react"; //Классы React
|
import React from "react"; //Классы React
|
||||||
import PropTypes from "prop-types"; //Контроль свойств компонента
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
import { Paper } from "@mui/material"; //Интерфейсные элементы
|
import { Paper, Link } from "@mui/material"; //Интерфейсные элементы
|
||||||
import { APP_STYLES } from "../../../../../app.styles"; //Типовые стили
|
import { APP_STYLES } from "../../../../../app.styles"; //Типовые стили
|
||||||
import { P8PDataGrid } from "../../../../components/p8p_data_grid"; //Таблица данных
|
import { P8PDataGrid } from "../../../../components/p8p_data_grid"; //Таблица данных
|
||||||
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
import { P8P_DATA_GRID_CONFIG_PROPS } from "../../../../config_wrapper"; //Подключение компонентов к настройкам приложения
|
||||||
import { useDataSource } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
import { useDataSource, useComponentHandlers } from "../../../../components/editors/p8p_data_source_hooks"; //Хуки для данных
|
||||||
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
import { P8P_DATA_SOURCE_SHAPE } from "../../../../components/editors/p8p_data_source_common"; //Общие ресурсы источника данных
|
||||||
import {
|
import {
|
||||||
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
P8P_COMPONENT_INLINE_MESSAGE_TYPE,
|
||||||
P8P_COMPONENT_INLINE_MESSAGE,
|
P8P_COMPONENT_INLINE_MESSAGE,
|
||||||
P8PComponentInlineMessage
|
P8PComponentInlineMessage
|
||||||
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
} from "../../../../components/editors/p8p_component_inline_message"; //Информационное сообщение внутри компонента
|
||||||
|
import { P8P_COMPONENT_SETTINGS_RESP_ARGS } from "../../../../components/editors/p8p_component_settings"; //Дополнительный функционал источников данных
|
||||||
|
import { P8P_CC_SHAPE, P8P_CCS_INITIAL } from "../../../../components/editors/p8p_component_condition/common"; //Общие ресурсы условий
|
||||||
|
import { P8P_CA_SHAPE, P8P_CAS_INITIAL } from "../../../../components/editors/p8p_component_action/common"; //Общие ресурсы действий
|
||||||
|
import { getDataGridCustomTypeValue, getDataGridHandlers, isHasCellRender } from "./action"; //Общие ресурсы действий таблицы
|
||||||
|
import { getColumnStylesByConditions } from "./conditions";
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
@ -33,12 +38,54 @@ const COMPONENT_NAME = "Таблица";
|
|||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
CONTAINER: { display: "flex", height: "100%", overflow: "hidden" },
|
CONTAINER: clickable => ({
|
||||||
|
display: "flex",
|
||||||
|
height: "100%",
|
||||||
|
overflow: "hidden",
|
||||||
|
...(clickable
|
||||||
|
? {
|
||||||
|
cursor: "pointer"
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
}),
|
||||||
DATA_GRID: { width: "100%" },
|
DATA_GRID: { width: "100%" },
|
||||||
DATA_GRID_CONTAINER: {
|
DATA_GRID_CONTAINER: {
|
||||||
height: `calc(100%)`,
|
height: `calc(100%)`,
|
||||||
...APP_STYLES.SCROLL
|
...APP_STYLES.SCROLL
|
||||||
}
|
},
|
||||||
|
DATA_GRID_ROW_CLICABLE: { cursor: "pointer" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//---------------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//---------------------------------------------
|
||||||
|
|
||||||
|
//Форматирование значения ячейки
|
||||||
|
const dataCellRender = ({ row, columnDef, values, handlers, conditions }) => {
|
||||||
|
//Инициализируем обработчики строки
|
||||||
|
const { onRowClick, onColumnClick } = getDataGridHandlers(handlers, columnDef.name);
|
||||||
|
//Инициализируем стили по условию
|
||||||
|
const condStyles = getColumnStylesByConditions(conditions, row, columnDef.name);
|
||||||
|
//Накладываем нужные обработчики
|
||||||
|
return {
|
||||||
|
cellStyle: { ...(onRowClick ? { ...STYLES.DATA_GRID_ROW_CLICABLE } : {}), ...condStyles },
|
||||||
|
cellProps: {
|
||||||
|
...(onRowClick
|
||||||
|
? {
|
||||||
|
onClick: event => {
|
||||||
|
onRowClick({ event, values, prms: { row, columnDef } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
},
|
||||||
|
data: onColumnClick ? (
|
||||||
|
<Link component="button" variant="body2" underline="hover" onClick={event => onColumnClick({ event, values, prms: { row, columnDef } })}>
|
||||||
|
{row[columnDef.name]}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
row[columnDef.name]
|
||||||
|
)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
@ -46,26 +93,27 @@ const STYLES = {
|
|||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Таблица (представление)
|
//Таблица (представление)
|
||||||
const Table = ({ dataSource = null, values = {} } = {}) => {
|
const Table = ({ dataSource = null, values = {}, conditions = P8P_CCS_INITIAL, actions = P8P_CAS_INITIAL, onValuesChange = null } = {}) => {
|
||||||
//Собственное состояние - данные
|
//Собственное состояние - данные
|
||||||
const [data, error] = useDataSource({ dataSource, values });
|
const [dataGrid, error, haveConfing, haveData] = useDataSource({ dataSource, values, componentRespArg: P8P_COMPONENT_SETTINGS_RESP_ARGS.TABLE });
|
||||||
|
|
||||||
//Флаг настроенности таблицы
|
//Собственное состояние - обработчики компонента
|
||||||
const haveConfing = dataSource?.stored ? true : false;
|
const [handlers] = useComponentHandlers({ actions, onValuesChange, getCustomTypeValue: getDataGridCustomTypeValue });
|
||||||
|
|
||||||
//Флаг наличия данных
|
//Признак необходимости рендера ячеек
|
||||||
const haveData = data?.init === true && !error ? true : false;
|
const hasCellRender = isHasCellRender(handlers, conditions);
|
||||||
|
|
||||||
//Данные таблицы
|
//Обработчики областей
|
||||||
const dataGrid = data?.XDATA_GRID || {};
|
const { onComponentClick } = getDataGridHandlers(handlers);
|
||||||
|
|
||||||
//Формирование представления
|
//Формирование представления
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
{...(haveConfing && haveData
|
{...(haveConfing && haveData
|
||||||
? { sx: { ...STYLES.CONTAINER } }
|
? { sx: { ...STYLES.CONTAINER(onComponentClick) } }
|
||||||
: { className: "component-view__container component-view__container__empty" })}
|
: { className: "component-view__container component-view__container__empty" })}
|
||||||
elevation={6}
|
elevation={6}
|
||||||
|
onClick={event => onComponentClick && onComponentClick({ event, values })}
|
||||||
>
|
>
|
||||||
{haveConfing && haveData ? (
|
{haveConfing && haveData ? (
|
||||||
<P8PDataGrid
|
<P8PDataGrid
|
||||||
@ -73,6 +121,17 @@ const Table = ({ dataSource = null, values = {} } = {}) => {
|
|||||||
{...dataGrid}
|
{...dataGrid}
|
||||||
style={STYLES.DATA_GRID}
|
style={STYLES.DATA_GRID}
|
||||||
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER, elevation: 0 }}
|
containerComponentProps={{ sx: STYLES.DATA_GRID_CONTAINER, elevation: 0 }}
|
||||||
|
dataCellRender={
|
||||||
|
hasCellRender
|
||||||
|
? prms =>
|
||||||
|
dataCellRender({
|
||||||
|
...prms,
|
||||||
|
values,
|
||||||
|
handlers,
|
||||||
|
conditions
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<P8PComponentInlineMessage
|
<P8PComponentInlineMessage
|
||||||
@ -89,7 +148,10 @@ const Table = ({ dataSource = null, values = {} } = {}) => {
|
|||||||
//Контроль свойств компонента - Таблица (представление)
|
//Контроль свойств компонента - Таблица (представление)
|
||||||
Table.propTypes = {
|
Table.propTypes = {
|
||||||
dataSource: P8P_DATA_SOURCE_SHAPE,
|
dataSource: P8P_DATA_SOURCE_SHAPE,
|
||||||
values: PropTypes.object
|
values: PropTypes.object,
|
||||||
|
conditions: PropTypes.arrayOf(P8P_CC_SHAPE),
|
||||||
|
actions: PropTypes.arrayOf(P8P_CA_SHAPE),
|
||||||
|
onValuesChange: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
|
|||||||
@ -76,7 +76,8 @@ LayoutItem.propTypes = {
|
|||||||
onDeleteClick: PropTypes.func,
|
onDeleteClick: PropTypes.func,
|
||||||
item: PropTypes.object.isRequired,
|
item: PropTypes.object.isRequired,
|
||||||
editMode: PropTypes.bool,
|
editMode: PropTypes.bool,
|
||||||
selected: PropTypes.bool
|
selected: PropTypes.bool,
|
||||||
|
isDragging: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
//----------------
|
//----------------
|
||||||
|
|||||||
134
app/panels/panels_editor/layouts.js
Normal file
134
app/panels/panels_editor/layouts.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Дополнительная разметка и вёрстка клиентских элементов
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { InputLabel, Input, IconButton, Icon, Button } from "@mui/material"; //Интерфейсные компоненты
|
||||||
|
import { TEXTS } from "../../../app.text"; //Общие текстовые ресурсы приложения
|
||||||
|
import { P8PAppProgress } from "../../components/p8p_app_progress"; //Индикатор процесса
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
INPUT_FILE: { display: "none" },
|
||||||
|
INPUT_LABEL: { display: "contents" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Компонент "Загрузка из файла"
|
||||||
|
const ImportPanel = ({ id, buttonView, onClick, disabled = false }) => {
|
||||||
|
//Состояние загрузки файла
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
//При выборе файла
|
||||||
|
const handleChange = event => {
|
||||||
|
//Если файл выбран
|
||||||
|
if (event.target.files[0]) {
|
||||||
|
//Инициализируем считыватель файла
|
||||||
|
const reader = new FileReader();
|
||||||
|
//Обработчик начала считывания данных
|
||||||
|
reader.onloadstart = () => setLoading(true);
|
||||||
|
//Обработчик считывания данных
|
||||||
|
reader.onload = async e => {
|
||||||
|
//Обрабатываем текст файла
|
||||||
|
onClick(e.target.result);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
//Обработчик ошибки считывания данных
|
||||||
|
reader.onerror = () => setLoading(false);
|
||||||
|
//Загружаем данные из файла
|
||||||
|
reader.readAsText(event.target.files[0]);
|
||||||
|
}
|
||||||
|
//Очищаем значение
|
||||||
|
event.target.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Генерация содержимого
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{loading ? <P8PAppProgress open={true} text={TEXTS.LOADING} /> : null}
|
||||||
|
<InputLabel htmlFor={id} sx={STYLES.INPUT_LABEL}>
|
||||||
|
<Input
|
||||||
|
id={id}
|
||||||
|
type="file"
|
||||||
|
hidden
|
||||||
|
onChange={handleChange}
|
||||||
|
sx={STYLES.INPUT_FILE}
|
||||||
|
inputProps={{ accept: ".xml" }}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
{buttonView}
|
||||||
|
</InputLabel>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Компонент "Загрузка из файла"
|
||||||
|
ImportPanel.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
buttonView: PropTypes.object.isRequired,
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
disabled: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
//Генерация действия "Загрузить из файла" меню действий
|
||||||
|
export const toolbarImportRenderer = ({ icon, title, disabled, onClick }) => {
|
||||||
|
//Представление кнопки загрузки
|
||||||
|
let importButtonView = (
|
||||||
|
<IconButton title={title} disabled={disabled}>
|
||||||
|
<Icon>{icon}</Icon>
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
//Генерация содержимого
|
||||||
|
return <ImportPanel id={"input-file-loader-toolbar"} buttonView={importButtonView} onClick={onClick} disabled={disabled} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Компонент кнопки импорта панели из файла
|
||||||
|
export const ImportPanelButton = ({ id, startIcon, title, onClick }) => {
|
||||||
|
//Представление кнопки загрузки
|
||||||
|
let importButtonView = (
|
||||||
|
<Button startIcon={startIcon ? <Icon>{startIcon}</Icon> : null} component="span">
|
||||||
|
{title}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
//Генерация содержимого
|
||||||
|
return <ImportPanel id={id} buttonView={importButtonView} onClick={onClick} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - Компонент кнопки импорта панели из файла
|
||||||
|
ImportPanelButton.propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
startIcon: PropTypes.string,
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
onClick: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//Выгрузка XML файла
|
||||||
|
export const exportXMLFile = (data, fileName) => {
|
||||||
|
//Переводим в данные для файла
|
||||||
|
const blobData = new Blob([data], { type: "text/plain" });
|
||||||
|
//Формируем URL
|
||||||
|
const url = URL.createObjectURL(blobData);
|
||||||
|
//Делаем линк и устанавливаем параметры, после добавляем в DOM
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = `${fileName.replace(/[/\\?%*:|"<>.,;= ]/g, "_")}.xml`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
//Выполняем открытие ссылки
|
||||||
|
a.click();
|
||||||
|
//Очищаем от лишних данных
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
358
app/panels/panels_editor/panel_editor.js
Normal file
358
app/panels/panels_editor/panel_editor.js
Normal file
@ -0,0 +1,358 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Редактор панели
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState, useContext, useEffect } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Responsive, WidthProvider } from "react-grid-layout"; //Адаптивный макет
|
||||||
|
import { Box, Grid, Menu, MenuItem, Popover } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||||
|
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Рабочая область приложения
|
||||||
|
import { P8PEditorToolBar } from "../../components/editors/p8p_editor_toolbar"; //Панель инструментов редактора
|
||||||
|
import { LayoutItem } from "./layout_item"; //Элемент макета
|
||||||
|
import { ComponentView } from "./component_view"; //Представление компонента панели
|
||||||
|
import { ComponentEditor } from "./component_editor"; //Редактор свойств компонента панели
|
||||||
|
import { COMPONENTS } from "./components/components"; //Описание доступных компонентов
|
||||||
|
import { usePanelDesc, useWindowResize } from "./components/components_hooks"; //Вспомогательные хуки
|
||||||
|
import { toolbarImportRenderer } from "./layouts"; //Дополнительная разметка и вёрстка клиентских элементов
|
||||||
|
import "react-grid-layout/css/styles.css"; //Стили для адаптивного макета
|
||||||
|
import "react-resizable/css/styles.css"; //Стили для адаптивного макета
|
||||||
|
import "./panels_editor.css"; //Стили редактора панелей
|
||||||
|
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||||
|
import { PanelPropsEditor } from "./panel_props_editor"; //Редактор глобальных свойств панели
|
||||||
|
import { PanelVariableList } from "./panel_variable_list"; //Список переменных панели
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
CONTAINER: { display: "flex", overflow: "auto", ...APP_STYLES.SCROLL },
|
||||||
|
GRID_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})` },
|
||||||
|
GRID_ITEM_INSPECTOR: { backgroundColor: "#e9ecef", height: "100%", overflow: "auto", ...APP_STYLES.SCROLL },
|
||||||
|
POPOVER_CONTAINER: { [`& .MuiPaper-root`]: { maxHeight: "60%", width: "300px", ...APP_STYLES.SCROLL } }
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Обёрдка для динамического макета
|
||||||
|
const ResponsiveGridLayout = WidthProvider(Responsive);
|
||||||
|
|
||||||
|
//Редактор панели
|
||||||
|
const PanelEditor = ({
|
||||||
|
panel,
|
||||||
|
panelName,
|
||||||
|
editMode,
|
||||||
|
isPanelChanged,
|
||||||
|
isPanelVariablesOpened,
|
||||||
|
onPanelChanged,
|
||||||
|
onPanelClose,
|
||||||
|
onEditModeToggle,
|
||||||
|
onPanelsManagerOpen,
|
||||||
|
onPanelVariablesClose
|
||||||
|
}) => {
|
||||||
|
//Собственное состояние - ID перемещаемого макета
|
||||||
|
//Требуется для разделения onDrag и onClick на макете
|
||||||
|
const [draggingID, setDraggingID] = useState("");
|
||||||
|
|
||||||
|
//Состояние описания и функций панели
|
||||||
|
const [
|
||||||
|
panelDesc,
|
||||||
|
isInit,
|
||||||
|
addComponent,
|
||||||
|
deleteComponent,
|
||||||
|
breakpointChange,
|
||||||
|
layoutsChange,
|
||||||
|
changeValueProviders,
|
||||||
|
changeComponentSettings,
|
||||||
|
changePanelSettings,
|
||||||
|
savePanelDesc,
|
||||||
|
importPanelDesc,
|
||||||
|
exportPanelDesc
|
||||||
|
] = usePanelDesc(panel);
|
||||||
|
|
||||||
|
//Собственное состояние - признак инициализации макетов
|
||||||
|
const [isLayoutsInit, setIsLayoutsInit] = useState(true);
|
||||||
|
//Собственное состояние - редактирование компонента
|
||||||
|
const [editComponent, setEditComponent] = useState(null);
|
||||||
|
//Собственное состояние - элемент открывающий список
|
||||||
|
const [addMenuAnchorEl, setAddMenuAnchorEl] = useState(null);
|
||||||
|
//Собственное состояние - высота строки ResponsiveGridLayout
|
||||||
|
const [rowHeight] = useWindowResize();
|
||||||
|
|
||||||
|
//Подключение к контексту сообщений
|
||||||
|
const { showMsgWarn } = useContext(MessagingСtx);
|
||||||
|
|
||||||
|
//При перемещении макета
|
||||||
|
const handleOnDrag = layout => {
|
||||||
|
//Если ID перемещаемого макета не установлен - устанавливаем
|
||||||
|
if (!draggingID) setDraggingID(layout.i);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При завершении перемещения макета
|
||||||
|
const handleOnDragStop = () => setDraggingID("");
|
||||||
|
|
||||||
|
//Открытие редактора настроек компонента
|
||||||
|
const handleComponentSettingsEditorOpen = id => setEditComponent(id);
|
||||||
|
|
||||||
|
//Закрытие реактора настроек компонента
|
||||||
|
const handleComponentSettingsEditorClose = () => setEditComponent(null);
|
||||||
|
|
||||||
|
//При нажатии на настройки компонента
|
||||||
|
const handleComponentSettingsClick = id => (editComponent === id ? handleComponentSettingsEditorClose() : handleComponentSettingsEditorOpen(id));
|
||||||
|
|
||||||
|
//Открытие/сокрытие меню добавления
|
||||||
|
const handleAddMenuToggle = target => setAddMenuAnchorEl(target instanceof Element ? target : null);
|
||||||
|
|
||||||
|
//При изменении размера холста
|
||||||
|
const handleBreakpointChange = breakpoint => breakpointChange(breakpoint);
|
||||||
|
|
||||||
|
//При изменении состояния макета
|
||||||
|
const handleLayoutChange = (currentLayout, layouts) => {
|
||||||
|
//Изменяем только в случае, если панель выбрана, проиницализирована и это режим редактирования
|
||||||
|
if (panel && isInit) {
|
||||||
|
//ResponsiveGridLayout реагирует на каждое изменение, при первой загрузке с сервера не требуется перезагрузка макетов
|
||||||
|
if (isLayoutsInit) {
|
||||||
|
setIsLayoutsInit(false);
|
||||||
|
} else {
|
||||||
|
//Изменяем состояние макета
|
||||||
|
layoutsChange(layouts);
|
||||||
|
//Указываем, что панель изменилась (только при редактировании)
|
||||||
|
onPanelChanged(editMode ? true : false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//Добвление компонента в макет
|
||||||
|
const handlePanelComponentAdd = component => {
|
||||||
|
//Добавляем компонент и его макет
|
||||||
|
addComponent(component);
|
||||||
|
//Указываем, что панель изменилась
|
||||||
|
onPanelChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Удаление компонента из макета
|
||||||
|
const handlePanelComponentDelete = id => {
|
||||||
|
//Удаляем компонент
|
||||||
|
deleteComponent(id);
|
||||||
|
//Если удаляемый компонент редактируется - закрываем редактор
|
||||||
|
editComponent === id && handleComponentSettingsEditorClose();
|
||||||
|
//Указываем, что панель изменилась
|
||||||
|
onPanelChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на кнопку добавления
|
||||||
|
const handleAddClick = e => handleAddMenuToggle(e.currentTarget);
|
||||||
|
|
||||||
|
//При выборе элемента меню добавления
|
||||||
|
const handleAddMenuItemClick = component => {
|
||||||
|
handleAddMenuToggle();
|
||||||
|
handlePanelComponentAdd(component);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При изменении значений в компоненте
|
||||||
|
const handleComponentValuesChange = values => {
|
||||||
|
//Изменяем значения проводника
|
||||||
|
changeValueProviders(values);
|
||||||
|
//Указываем, что панель изменилась (только при редактировании)
|
||||||
|
onPanelChanged(editMode ? true : false);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При изменении настроек компонента
|
||||||
|
const handleComponentSettingsChange = ({ id = null, settings = {}, closeEditor = false } = {}) => {
|
||||||
|
if (id && panelDesc.components[id]) {
|
||||||
|
//Изменяем настройки компонента
|
||||||
|
changeComponentSettings(id, settings, () => {
|
||||||
|
if (closeEditor === true) handleComponentSettingsEditorClose();
|
||||||
|
//Указываем, что панель изменилась
|
||||||
|
onPanelChanged(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//При изменении настроек панели
|
||||||
|
const handlePanelSettingsChange = ({ valueProviders }) => {
|
||||||
|
//Изменяем настройки панели
|
||||||
|
changePanelSettings(valueProviders);
|
||||||
|
//Указываем, что панель изменилась
|
||||||
|
onPanelChanged(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
//При удалении компоненета
|
||||||
|
const handleComponentDeleteClick = id => handlePanelComponentDelete(id);
|
||||||
|
|
||||||
|
//Закрытие панели
|
||||||
|
const handlePanelClose = () => onPanelClose();
|
||||||
|
|
||||||
|
//При запуске панели
|
||||||
|
const handlePanelPlay = () => onEditModeToggle();
|
||||||
|
|
||||||
|
//При сохранении описания панели
|
||||||
|
const handlePanelDescSave = () => savePanelDesc(isChanged => onPanelChanged(isChanged));
|
||||||
|
|
||||||
|
//При импорте настройки панели из файла
|
||||||
|
const handleImportPanel = (fileData, callback) => importPanelDesc(fileData, callback);
|
||||||
|
|
||||||
|
//При выгрузке настройки панели в файл
|
||||||
|
const handleExportPanel = () => {
|
||||||
|
//Если панель выбрана и есть изменения
|
||||||
|
if (panel && isPanelChanged)
|
||||||
|
showMsgWarn(`Панель содержит несохраненные изменения. Выгрузить панель в текущем состоянии?`, () => exportPanelDesc(panelName));
|
||||||
|
else exportPanelDesc(panelName);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Текущие значения панели
|
||||||
|
const values = Object.keys(panelDesc.valueProviders).reduce((res, key) => ({ ...res, ...{ [key]: panelDesc.valueProviders[key].value } }), {});
|
||||||
|
|
||||||
|
//Меню добавления
|
||||||
|
const addMenu = (
|
||||||
|
<Menu anchorEl={addMenuAnchorEl} open={Boolean(addMenuAnchorEl)} onClose={handleAddMenuToggle}>
|
||||||
|
{COMPONENTS.map((comp, i) => (
|
||||||
|
<MenuItem key={i} onClick={() => handleAddMenuItemClick(comp)}>
|
||||||
|
{comp.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
//Панель инструментов
|
||||||
|
const toolBar = (
|
||||||
|
<P8PEditorToolBar
|
||||||
|
items={[
|
||||||
|
{ icon: "file_open", title: "Менеджер панелей", onClick: onPanelsManagerOpen },
|
||||||
|
{ icon: "save", title: "Сохранить", onClick: handlePanelDescSave, disabled: !isPanelChanged },
|
||||||
|
{ icon: "close", title: "Закрыть панель", onClick: handlePanelClose, disabled: !panel },
|
||||||
|
{ icon: "play_arrow", title: "Запустить", onClick: handlePanelPlay, disabled: !panel },
|
||||||
|
{ icon: "add", title: "Добавить элемент", onClick: handleAddClick, disabled: !panel },
|
||||||
|
{
|
||||||
|
icon: "file_upload",
|
||||||
|
title: "Загрузить из файла",
|
||||||
|
onClick: fileData => {
|
||||||
|
handleImportPanel(fileData, isLoaded => onPanelChanged(isLoaded));
|
||||||
|
},
|
||||||
|
customRenderer: toolbarImportRenderer,
|
||||||
|
disabled: !panel
|
||||||
|
},
|
||||||
|
{ icon: "file_download", title: "Выгрузить в файл", onClick: handleExportPanel, disabled: !panel },
|
||||||
|
{ icon: "settings", title: "Параметры панели", onClick: handleComponentSettingsEditorClose, disabled: !panel }
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
//При изменении панели
|
||||||
|
useEffect(() => {
|
||||||
|
//Первое изменение состояния макета - инициализация
|
||||||
|
setIsLayoutsInit(true);
|
||||||
|
//Очищаем редактируемый компонент
|
||||||
|
setEditComponent(null);
|
||||||
|
}, [panel]);
|
||||||
|
|
||||||
|
//Генерация содержимого
|
||||||
|
return (
|
||||||
|
<Box sx={STYLES.CONTAINER}>
|
||||||
|
{addMenu}
|
||||||
|
<Grid container sx={STYLES.GRID_CONTAINER} columns={25}>
|
||||||
|
<Grid item xs={editMode ? 20 : 25}>
|
||||||
|
<ResponsiveGridLayout
|
||||||
|
autoSize={true}
|
||||||
|
rowHeight={rowHeight}
|
||||||
|
className={"layout"}
|
||||||
|
layouts={panelDesc.layouts}
|
||||||
|
breakpoints={{ lg: 1200 }}
|
||||||
|
cols={{ lg: 12 }}
|
||||||
|
onBreakpointChange={handleBreakpointChange}
|
||||||
|
onLayoutChange={handleLayoutChange}
|
||||||
|
useCSSTransforms={true}
|
||||||
|
compactType={"vertical"}
|
||||||
|
isDraggable={editMode}
|
||||||
|
isResizable={editMode}
|
||||||
|
onDrag={(layouts, oldItem) => handleOnDrag(oldItem)}
|
||||||
|
onDragStop={handleOnDragStop}
|
||||||
|
>
|
||||||
|
{panelDesc.layouts[panelDesc.breakpoint].map(item => (
|
||||||
|
<LayoutItem
|
||||||
|
key={item.i}
|
||||||
|
onSettingsClick={handleComponentSettingsClick}
|
||||||
|
onDeleteClick={handleComponentDeleteClick}
|
||||||
|
item={item}
|
||||||
|
editMode={editMode}
|
||||||
|
selected={editMode && editComponent === item.i}
|
||||||
|
>
|
||||||
|
<ComponentView
|
||||||
|
id={item.i}
|
||||||
|
isDragging={item.i === draggingID}
|
||||||
|
path={panelDesc.components[item.i]?.path}
|
||||||
|
settings={panelDesc.components[item.i]?.settings}
|
||||||
|
values={values}
|
||||||
|
onValuesChange={handleComponentValuesChange}
|
||||||
|
/>
|
||||||
|
</LayoutItem>
|
||||||
|
))}
|
||||||
|
</ResponsiveGridLayout>
|
||||||
|
</Grid>
|
||||||
|
{editMode && (
|
||||||
|
<Grid item xs={5} sx={STYLES.GRID_ITEM_INSPECTOR}>
|
||||||
|
{toolBar}
|
||||||
|
{panel ? (
|
||||||
|
editComponent ? (
|
||||||
|
<ComponentEditor
|
||||||
|
id={editComponent}
|
||||||
|
path={panelDesc.components[editComponent].path}
|
||||||
|
settings={panelDesc.components[editComponent].settings}
|
||||||
|
valueProviders={panelDesc.valueProviders}
|
||||||
|
onSettingsChange={handleComponentSettingsChange}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<PanelPropsEditor
|
||||||
|
valueProviders={panelDesc.valueProviders}
|
||||||
|
onSettingsChange={handlePanelSettingsChange}
|
||||||
|
onDependencyClick={handleComponentSettingsClick}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
<Popover
|
||||||
|
id={"variables-popover"}
|
||||||
|
sx={STYLES.POPOVER_CONTAINER}
|
||||||
|
open={!editMode && isPanelVariablesOpened}
|
||||||
|
marginThreshold={10}
|
||||||
|
onClose={onPanelVariablesClose}
|
||||||
|
anchorReference={"anchorPosition"}
|
||||||
|
anchorPosition={{ top: 60, left: window.innerWidth }}
|
||||||
|
transitionDuration={{ appear: 0, enter: 200, exit: 200 }}
|
||||||
|
>
|
||||||
|
<PanelVariableList valueProviders={panelDesc.valueProviders} onValuesChange={handleComponentValuesChange} />
|
||||||
|
</Popover>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - Редактор панели
|
||||||
|
PanelEditor.propTypes = {
|
||||||
|
panel: PropTypes.number.isRequired,
|
||||||
|
panelName: PropTypes.string.isRequired,
|
||||||
|
editMode: PropTypes.bool.isRequired,
|
||||||
|
isPanelChanged: PropTypes.bool.isRequired,
|
||||||
|
isPanelVariablesOpened: PropTypes.bool.isRequired,
|
||||||
|
onPanelChanged: PropTypes.func.isRequired,
|
||||||
|
onPanelClose: PropTypes.func.isRequired,
|
||||||
|
onEditModeToggle: PropTypes.func.isRequired,
|
||||||
|
onPanelsManagerOpen: PropTypes.func.isRequired,
|
||||||
|
onPanelVariablesClose: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { PanelEditor };
|
||||||
230
app/panels/panels_editor/panel_props_editor.js
Normal file
230
app/panels/panels_editor/panel_props_editor.js
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Редактор глобальных свойств панели
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React, { useState, useContext } from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Chip, Stack, TextField, Button, Icon } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { P8PConfigDialog } from "../../components/editors/p8p_config_dialog"; //Диалог настройки
|
||||||
|
import { isElementNameCorrect } from "../../components/editors/p8p_editors_common"; //Общие ресурсы редакторов
|
||||||
|
import { P8PEditorSubHeader } from "../../components/editors/p8p_editor_sub_header"; //Заголовок раздела редактора
|
||||||
|
import { P8PEditorBox } from "../../components/editors/p8p_editor_box"; //Контейнер редактора
|
||||||
|
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||||
|
import { APP_STYLES } from "../../../app.styles"; //Типовые стили
|
||||||
|
import { STYLES as COMMON_STYLES } from "../../components/editors/p8p_editors_common"; //Общие ресурсы редакторов
|
||||||
|
import { P8PChipList } from "../../components/editors/p8p_chip_list"; //Дополнительные настройки редактора
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
STACK_DEPENENCIES: { gap: "5px", ...APP_STYLES.SCROLL, overflow: "auto", maxHeight: "160px", width: "260px" },
|
||||||
|
CHIP_DEPENDENCY: { ...COMMON_STYLES.CHIP(true, false), minHeight: "32px" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//Начальное состояние переменной
|
||||||
|
const VARIABLE_INITIAL = {
|
||||||
|
dependencies: [],
|
||||||
|
description: "",
|
||||||
|
value: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
//Структура переменной
|
||||||
|
export const VARIABLE_SHAPE = PropTypes.shape({
|
||||||
|
dependencies: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
description: PropTypes.string.isRequired,
|
||||||
|
value: PropTypes.string
|
||||||
|
});
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
//Редактор переменной
|
||||||
|
const VariableEditor = ({ variableName = "", variable = null, dependencies = [], onDependencyClick, onOk, onCancel } = {}) => {
|
||||||
|
//Собственное состояние - имя переменной
|
||||||
|
const [name, setName] = useState(variableName || "");
|
||||||
|
|
||||||
|
//Собственное состояние - параметры переменной
|
||||||
|
const [state, setState] = useState({ ...VARIABLE_INITIAL, ...variable });
|
||||||
|
|
||||||
|
//При закрытии редактора с сохранением
|
||||||
|
const handleOk = () => onOk(name, { ...state });
|
||||||
|
|
||||||
|
//При закрытии редактора с отменой
|
||||||
|
const handleCancel = () => onCancel();
|
||||||
|
|
||||||
|
//При изменении параметра переменной
|
||||||
|
const handleChange = e => {
|
||||||
|
//Если это поле наименования, то проверяем корректность
|
||||||
|
if (e.target.id === "name" && isElementNameCorrect(e.target.value)) setName(e.target.value);
|
||||||
|
//Устанавливаем значение
|
||||||
|
else setState(pv => ({ ...pv, [e.target.id]: e.target.value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
//Доступность сохранения настроек элемента
|
||||||
|
const okDisabled = !name || !state.description ? true : false;
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<P8PConfigDialog title={`${variable ? "Изменение" : "Добавление"} элемента`} onOk={handleOk} onCancel={handleCancel} okDisabled={okDisabled}>
|
||||||
|
<Stack direction={"column"} spacing={1}>
|
||||||
|
<TextField type={"text"} variant={"standard"} value={name} label={"Имя"} id={"name"} onChange={handleChange} required={true} />
|
||||||
|
<TextField
|
||||||
|
type={"text"}
|
||||||
|
variant={"standard"}
|
||||||
|
value={state.description}
|
||||||
|
label={"Описание"}
|
||||||
|
id={"description"}
|
||||||
|
onChange={handleChange}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
{dependencies.length !== 0 ? (
|
||||||
|
<>
|
||||||
|
<P8PEditorSubHeader title={"Зависимости"} />
|
||||||
|
<Stack sx={STYLES.STACK_DEPENENCIES} direction={"column"} p={1} useFlexGap={true}>
|
||||||
|
{dependencies.map((item, i) => (
|
||||||
|
<Chip
|
||||||
|
key={i}
|
||||||
|
label={item}
|
||||||
|
variant={"outlined"}
|
||||||
|
onClick={() => onDependencyClick && onDependencyClick(item)}
|
||||||
|
sx={STYLES.CHIP_DEPENDENCY}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</P8PConfigDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств - редактор переменной
|
||||||
|
VariableEditor.propTypes = {
|
||||||
|
variableName: PropTypes.string,
|
||||||
|
variable: VARIABLE_SHAPE,
|
||||||
|
dependencies: PropTypes.array,
|
||||||
|
onDependencyClick: PropTypes.func.isRequired,
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
onCancel: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Редактор глобальных свойств панели
|
||||||
|
const PanelPropsEditor = ({ valueProviders = {}, onSettingsChange, onDependencyClick } = {}) => {
|
||||||
|
//Собственное состояние - редактор переменных панели
|
||||||
|
const [variableEditor, setVariableEditor] = useState({ display: false, item: null });
|
||||||
|
|
||||||
|
//Подключение к контексту сообщений
|
||||||
|
const { showMsgErr } = useContext(MessagingСtx);
|
||||||
|
|
||||||
|
//При добавлении новой переменной
|
||||||
|
const handleVariableAdd = () => setVariableEditor({ display: true, item: null });
|
||||||
|
|
||||||
|
//При нажатии на переменную
|
||||||
|
const handleVariableClick = index => setVariableEditor({ display: true, item: variables[index] });
|
||||||
|
|
||||||
|
//При отмене сохранения изменений переменной
|
||||||
|
const handleVariableCancel = () => setVariableEditor({ display: false, index: null });
|
||||||
|
|
||||||
|
//При сохранении изменений переменной
|
||||||
|
const handleVariableSave = (variableName, variable) => {
|
||||||
|
//Текст ошибки
|
||||||
|
let msgError = "";
|
||||||
|
//Если это добавление или изменилось наименование - проверяем на дублирование
|
||||||
|
if ((!variableEditor.item || variableEditor.item !== variableName) && Object.prototype.hasOwnProperty.call(valueProviders, variableName)) {
|
||||||
|
msgError = `Дублирование наименования параметра "${variableName}".`;
|
||||||
|
}
|
||||||
|
//Если это исправление наименования параметра, но он используется
|
||||||
|
if (variableEditor.item && variableEditor.item !== variableName && valueProviders[variableEditor.item].dependencies.length !== 0) {
|
||||||
|
msgError = `Переменная имеет связи с компонентами. Изменение наименования запрещено.`;
|
||||||
|
}
|
||||||
|
//Если есть ошибка - выводим
|
||||||
|
if (msgError) {
|
||||||
|
showMsgErr(msgError);
|
||||||
|
} else {
|
||||||
|
//Копируем параметры
|
||||||
|
let newValueProviders = { ...valueProviders };
|
||||||
|
//Удаляем старое значение, если требуется
|
||||||
|
variableEditor.item && variableEditor.item !== variableName ? delete newValueProviders[variableEditor.item] : null;
|
||||||
|
//Добавляем новый параметр
|
||||||
|
newValueProviders = { ...newValueProviders, [variableName]: { ...variable } };
|
||||||
|
//Обновляем проводники панели
|
||||||
|
onSettingsChange({ valueProviders: { ...newValueProviders } });
|
||||||
|
//Закрываем редактирование
|
||||||
|
setVariableEditor({ display: false, index: null });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//При удалении переменной
|
||||||
|
const handleVariableDelete = index => {
|
||||||
|
const variable = variables[index];
|
||||||
|
//Если переменная не используется в компонентах - удаляем
|
||||||
|
if (valueProviders[variable].dependencies.length === 0) {
|
||||||
|
const newValueProviders = { ...valueProviders };
|
||||||
|
delete newValueProviders[variable];
|
||||||
|
onSettingsChange({ valueProviders: { ...newValueProviders } });
|
||||||
|
} else {
|
||||||
|
showMsgErr(`Переменная имеет связи с компонентами. Удаление запрещено.`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//При нажатии на зависимый компонент
|
||||||
|
const handleDependencyClick = id => {
|
||||||
|
//Закрываем редактирование
|
||||||
|
setVariableEditor({ display: false, index: null });
|
||||||
|
//Открываем настройку компонента
|
||||||
|
onDependencyClick(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Текущие переменные панели
|
||||||
|
const variables = Object.keys(valueProviders).reduce((res, key) => [...res, key], []);
|
||||||
|
|
||||||
|
//Определяем структуру переменных для отображения
|
||||||
|
const variableChips = variables.map(item => ({ text: item, title: item }));
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<P8PEditorBox title={"Параметры панели"}>
|
||||||
|
{variableEditor.display && (
|
||||||
|
<VariableEditor
|
||||||
|
variableName={variableEditor.item}
|
||||||
|
variable={variableEditor.item !== null ? { ...valueProviders[variableEditor.item] } : null}
|
||||||
|
dependencies={variableEditor.item ? valueProviders[variableEditor.item].dependencies : []}
|
||||||
|
onDependencyClick={handleDependencyClick}
|
||||||
|
onCancel={handleVariableCancel}
|
||||||
|
onOk={handleVariableSave}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<P8PEditorSubHeader title={"Переменные"} />
|
||||||
|
<P8PChipList items={variableChips} onClick={handleVariableClick} onDelete={handleVariableDelete} />
|
||||||
|
<Button startIcon={<Icon>add</Icon>} onClick={handleVariableAdd}>
|
||||||
|
Добавить переменную
|
||||||
|
</Button>
|
||||||
|
</P8PEditorBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - редактор глобальных свойств панели
|
||||||
|
PanelPropsEditor.propTypes = {
|
||||||
|
valueProviders: PropTypes.object,
|
||||||
|
onSettingsChange: PropTypes.func.isRequired,
|
||||||
|
onDependencyClick: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { PanelPropsEditor };
|
||||||
90
app/panels/panels_editor/panel_variable_list.js
Normal file
90
app/panels/panels_editor/panel_variable_list.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Редактор панелей
|
||||||
|
Список переменных панели
|
||||||
|
*/
|
||||||
|
|
||||||
|
//---------------------
|
||||||
|
//Подключение библиотек
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
import React from "react"; //Классы React
|
||||||
|
import PropTypes from "prop-types"; //Контроль свойств компонента
|
||||||
|
import { Typography, Stack } from "@mui/material"; //Интерфейсные элементы
|
||||||
|
import { P8PEditorBox } from "../../components/editors/p8p_editor_box"; //Контейнер редактора
|
||||||
|
import { P8PChipList } from "../../components/editors/p8p_chip_list"; //Дополнительные настройки редактора
|
||||||
|
import { hasValue } from "../../core/utils"; //Вспомогательные процедуры и функции
|
||||||
|
|
||||||
|
//---------
|
||||||
|
//Константы
|
||||||
|
//---------
|
||||||
|
|
||||||
|
//Стили
|
||||||
|
const STYLES = {
|
||||||
|
STACK_ITEM: { alignItems: "center" },
|
||||||
|
TYPOGRAPHY_INFO: { overflow: "hidden" }
|
||||||
|
};
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
//Вспомогательные функции и компоненты
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
const chipRender = item => {
|
||||||
|
//Определяем описание переменной
|
||||||
|
const description = item.description;
|
||||||
|
//Определяем значение переменной
|
||||||
|
const value = item.value ?? "";
|
||||||
|
//Определяем заголовок
|
||||||
|
const title = `${description}${value ? " - " : ""}${value}`;
|
||||||
|
return (
|
||||||
|
<Stack direction="row" spacing={1} sx={STYLES.STACK_ITEM}>
|
||||||
|
<Typography variant="body2" title={title} sx={STYLES.TYPOGRAPHY_INFO}>
|
||||||
|
<strong>{description}</strong>
|
||||||
|
{`${value ? " - " : ""}${value}`}
|
||||||
|
</Typography>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//-----------
|
||||||
|
//Тело модуля
|
||||||
|
//-----------
|
||||||
|
|
||||||
|
//Список переменных панели
|
||||||
|
const PanelVariableList = ({ valueProviders = {}, onValuesChange = null }) => {
|
||||||
|
//При очистке значения переменной
|
||||||
|
const handleVariableClear = index => onValuesChange && onValuesChange({ [values[index].key]: "" });
|
||||||
|
|
||||||
|
//Определяем структуру переменных
|
||||||
|
const values = Object.keys(valueProviders).reduce(
|
||||||
|
(prev, key) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
description: valueProviders[key].description,
|
||||||
|
value: valueProviders[key].value,
|
||||||
|
deleteIcon: "filter_alt_off",
|
||||||
|
disableDelete: !hasValue(valueProviders[key].value),
|
||||||
|
key
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
//Формирование представления
|
||||||
|
return (
|
||||||
|
<P8PEditorBox title={"Переменные"}>
|
||||||
|
<P8PChipList items={values} labelRender={chipRender} onDelete={onValuesChange ? index => handleVariableClear(index) : null} />
|
||||||
|
</P8PEditorBox>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Контроль свойств компонента - список переменных панели
|
||||||
|
PanelVariableList.propTypes = {
|
||||||
|
valueProviders: PropTypes.object,
|
||||||
|
onValuesChange: PropTypes.func
|
||||||
|
};
|
||||||
|
|
||||||
|
//----------------
|
||||||
|
//Интерфейс модуля
|
||||||
|
//----------------
|
||||||
|
|
||||||
|
export { PanelVariableList };
|
||||||
@ -7,242 +7,171 @@
|
|||||||
//Подключение библиотек
|
//Подключение библиотек
|
||||||
//---------------------
|
//---------------------
|
||||||
|
|
||||||
import React, { useEffect, useState, useContext } from "react"; //Классы React
|
import React, { useState, useContext, useEffect } from "react"; //Классы React
|
||||||
import { Responsive, WidthProvider } from "react-grid-layout"; //Адаптивный макет
|
import { Box, Icon, Fab } from "@mui/material"; //Интерфейсные элементы
|
||||||
import { Box, Grid, Menu, MenuItem, Icon, Fab } from "@mui/material"; //Интерфейсные элементы
|
|
||||||
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
import { ApplicationСtx } from "../../context/application"; //Контекст приложения
|
||||||
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Рабочая область приложения
|
import { MessagingСtx } from "../../context/messaging"; //Контекст сообщений
|
||||||
import { P8PEditorToolBar } from "../../components/editors/p8p_editor_toolbar"; //Панель инструментов редактора
|
import { PanelsManager } from "./components/panels_manager/panels_manager"; //Менеджер панелей
|
||||||
import { genGUID } from "../../core/utils"; //Общие вспомогательные функции
|
import { usePanel } from "./components/components_hooks"; //Вспомогательные хуки
|
||||||
import { LayoutItem } from "./layout_item"; //Элемент макета
|
import { NavigationCtx } from "../../context/navigation"; //Контекст навигации
|
||||||
import { ComponentView } from "./component_view"; //Представление компонента панели
|
import { PanelEditor } from "./panel_editor"; //Редактор панели
|
||||||
import { ComponentEditor } from "./component_editor"; //Редактор свойств компонента панели
|
|
||||||
import { COMPONETNS } from "./components/components"; //Описание доступных компонентов
|
|
||||||
import "react-grid-layout/css/styles.css"; //Стили для адаптивного макета
|
|
||||||
import "react-resizable/css/styles.css"; //Стили для адаптивного макета
|
|
||||||
import "./panels_editor.css"; //Стили редактора панелей
|
|
||||||
|
|
||||||
//---------
|
//---------
|
||||||
//Константы
|
//Константы
|
||||||
//---------
|
//---------
|
||||||
|
|
||||||
|
//Заголовок панели по умолчанию
|
||||||
|
const APP_BAR_TITLE_DEFAULT = "Редактор панелей";
|
||||||
|
|
||||||
//Стили
|
//Стили
|
||||||
const STYLES = {
|
const STYLES = {
|
||||||
CONTAINER: { display: "flex" },
|
BOX_PLAY_MODE: {
|
||||||
GRID_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})` },
|
position: "absolute",
|
||||||
GRID_ITEM_INSPECTOR: { backgroundColor: "#e9ecef" },
|
top: 12,
|
||||||
FAB_EDIT: { position: "absolute", top: 12, right: 12, zIndex: 2000 }
|
right: 15,
|
||||||
};
|
zIndex: 2000,
|
||||||
|
display: "flex",
|
||||||
//Заголовоки по умолчанию
|
height: "42px",
|
||||||
const PANEL_CAPTION_EDIT_MODE = "Редактор панелей";
|
width: "145px",
|
||||||
const PANEL_CAPTION_EXECUTE_MODE = "Исполнение панели";
|
justifyContent: "flex-end",
|
||||||
|
gap: "10px"
|
||||||
//Начальное состояние размера макета
|
}
|
||||||
const INITIAL_BREAKPOINT = "lg";
|
|
||||||
|
|
||||||
//Начальное состояние макета
|
|
||||||
const INITIAL_LAYOUTS = {
|
|
||||||
[INITIAL_BREAKPOINT]: []
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//-----------
|
//-----------
|
||||||
//Тело модуля
|
//Тело модуля
|
||||||
//-----------
|
//-----------
|
||||||
|
|
||||||
//Обёрдка для динамического макета
|
|
||||||
const ResponsiveGridLayout = WidthProvider(Responsive);
|
|
||||||
|
|
||||||
//Корневой компонент редактора панелей
|
//Корневой компонент редактора панелей
|
||||||
const PanelsEditor = () => {
|
const PanelsEditor = () => {
|
||||||
//Собственное состояние
|
//Собственное состояние - иниализация панели
|
||||||
const [components, setComponents] = useState({});
|
const [init, setInit] = useState(true);
|
||||||
const [valueProviders, setValueProviders] = useState({});
|
//Собственное состояние - иниализируемая панель
|
||||||
const [layouts, setLayouts] = useState(INITIAL_LAYOUTS);
|
const [panel, panelName, editMode, isEditAvaliable, isPanelChanged, loadPanel, selectPanel, closePanel, changeEditMode, setPanelChanged] =
|
||||||
const [breakpoint, setBreakpoint] = useState(INITIAL_BREAKPOINT);
|
usePanel();
|
||||||
const [editMode, setEditMode] = useState(true);
|
//Отображения менеджера панелей
|
||||||
const [editComponent, setEditComponent] = useState(null);
|
const [openPanelsManager, setOpenPanelsManager] = useState(false);
|
||||||
const [addMenuAnchorEl, setAddMenuAnchorEl] = useState(null);
|
//Отображение параметров панели
|
||||||
|
const [openPanelVariables, setOpenPanelVariables] = useState(false);
|
||||||
|
|
||||||
|
//Подключение к контексту навигации
|
||||||
|
const { getNavigationSearch } = useContext(NavigationCtx);
|
||||||
|
//Подключение к контексту сообщений
|
||||||
|
const { showMsgWarn } = useContext(MessagingСtx);
|
||||||
//Подключение к контексту приложения
|
//Подключение к контексту приложения
|
||||||
const { setAppBarTitle } = useContext(ApplicationСtx);
|
const { setAppBarTitle } = useContext(ApplicationСtx);
|
||||||
|
|
||||||
//Добвление компонента в макет
|
//При выборе панели
|
||||||
const addComponent = component => {
|
const handlePanelSelect = panel => {
|
||||||
const id = genGUID();
|
//Если панель выбрана и есть изменения
|
||||||
setLayouts(pv => ({ ...pv, [breakpoint]: [...pv[breakpoint], { i: id, x: 0, y: 0, w: 4, h: 10 }] }));
|
if (panel && isPanelChanged)
|
||||||
setComponents(pv => ({ ...pv, [id]: { ...component } }));
|
showMsgWarn(
|
||||||
};
|
`Панель содержит несохраненные изменения. Закрыть панель без изменений?`,
|
||||||
|
() => {
|
||||||
//Удаление компонента из макета
|
selectPanel(panel.rn, panel.name, panel.modify === 1 ? true : false);
|
||||||
const deleteComponent = id => {
|
setOpenPanelsManager(false);
|
||||||
setLayouts(pv => ({ ...pv, [breakpoint]: layouts[breakpoint].filter(item => item.i !== id) }));
|
},
|
||||||
setComponents(pv => ({ ...pv, [id]: { ...pv[id], deleted: true } }));
|
() => setOpenPanelsManager(false)
|
||||||
if (valueProviders[id]) {
|
);
|
||||||
const vPTmp = { ...valueProviders };
|
else {
|
||||||
delete vPTmp[id];
|
selectPanel(panel.rn, panel.name, panel.modify === 1 ? true : false);
|
||||||
setValueProviders(vPTmp);
|
setOpenPanelsManager(false);
|
||||||
}
|
|
||||||
editComponent === id && closeComponentSettingsEditor();
|
|
||||||
};
|
|
||||||
|
|
||||||
//Включение/выключение режима редиктирования
|
|
||||||
const toggleEditMode = () => {
|
|
||||||
if (!editMode) setAppBarTitle(PANEL_CAPTION_EDIT_MODE);
|
|
||||||
else setAppBarTitle(PANEL_CAPTION_EXECUTE_MODE);
|
|
||||||
setEditMode(!editMode);
|
|
||||||
};
|
|
||||||
|
|
||||||
//Открытие редактора настроек компонента
|
|
||||||
const openComponentSettingsEditor = id => setEditComponent(id);
|
|
||||||
|
|
||||||
//Закрытие реактора настроек компонента
|
|
||||||
const closeComponentSettingsEditor = () => setEditComponent(null);
|
|
||||||
|
|
||||||
//Открытие/сокрытие меню добавления
|
|
||||||
const toggleAddMenu = target => setAddMenuAnchorEl(target instanceof Element ? target : null);
|
|
||||||
|
|
||||||
//При изменении размера холста
|
|
||||||
const handleBreakpointChange = breakpoint => setBreakpoint(breakpoint);
|
|
||||||
|
|
||||||
//При изменении состояния макета
|
|
||||||
const handleLayoutChange = (currentLayout, layouts) => setLayouts(layouts);
|
|
||||||
|
|
||||||
//При нажатии на кнопку добалвения
|
|
||||||
const handleAddClick = e => toggleAddMenu(e.currentTarget);
|
|
||||||
|
|
||||||
//При выборе элемента меню добавления
|
|
||||||
const handleAddMenuItemClick = component => {
|
|
||||||
toggleAddMenu();
|
|
||||||
addComponent(component);
|
|
||||||
};
|
|
||||||
|
|
||||||
//При изменении значений в компоненте
|
|
||||||
const handleComponentValuesChange = (id, values) => setValueProviders(pv => ({ ...pv, [id]: { ...values } }));
|
|
||||||
|
|
||||||
//При нажатии на настройки компонента
|
|
||||||
const handleComponentSettingsClick = id => (editComponent === id ? closeComponentSettingsEditor() : openComponentSettingsEditor(id));
|
|
||||||
|
|
||||||
//При изменении настроек компонента
|
|
||||||
const handleComponentSettingsChange = ({ id = null, settings = {}, providedValues = [], closeEditor = false } = {}) => {
|
|
||||||
if (id && components[id]) {
|
|
||||||
const providedValuesInit = providedValues.reduce((res, providedValue) => ({ ...res, [providedValue]: undefined }), {});
|
|
||||||
if (valueProviders[id]) {
|
|
||||||
const vPTmp = { ...valueProviders[id] };
|
|
||||||
Object.keys(valueProviders[id]).forEach(key => !providedValues.includes(key) && delete vPTmp[key]);
|
|
||||||
setValueProviders(pv => ({ ...pv, [id]: { ...providedValuesInit, ...vPTmp } }));
|
|
||||||
} else setValueProviders(pv => ({ ...pv, [id]: providedValuesInit }));
|
|
||||||
setComponents(pv => ({ ...pv, [editComponent]: { ...pv[editComponent], settings: { ...settings } } }));
|
|
||||||
if (closeEditor === true) closeComponentSettingsEditor();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//При удалении компоненета
|
//Закрытие панели
|
||||||
const handleComponentDeleteClick = id => deleteComponent(id);
|
const handlePanelClose = () => {
|
||||||
|
//Если панель выбрана и есть изменения
|
||||||
|
if (panel && isPanelChanged)
|
||||||
|
showMsgWarn(`Панель содержит несохраненные изменения. Закрыть панель без изменений?`, () => {
|
||||||
|
closePanel();
|
||||||
|
setOpenPanelsManager(true);
|
||||||
|
});
|
||||||
|
else {
|
||||||
|
closePanel();
|
||||||
|
setOpenPanelsManager(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
//При подключении к странице
|
//Открытие менеджера панелей
|
||||||
|
const handleOpenPanelsManager = () => setOpenPanelsManager(true);
|
||||||
|
|
||||||
|
//Закрытие менеджера панелей
|
||||||
|
const handleCancelPanelsManager = () => setOpenPanelsManager(false);
|
||||||
|
|
||||||
|
//Открытие/закрытие параметров панели
|
||||||
|
const handleToggleOpenPanelVariables = () => setOpenPanelVariables(!openPanelVariables);
|
||||||
|
|
||||||
|
//Включение/выключение режима редактирования
|
||||||
|
const handleToggleEditMode = () => {
|
||||||
|
//Включаем/отключаем панель
|
||||||
|
changeEditMode(!editMode);
|
||||||
|
//Изначально отключаем отображение параметров панели
|
||||||
|
setOpenPanelVariables(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
//Дополнительные кнопки режима использования панели
|
||||||
|
const runTimeButtons = !editMode && (
|
||||||
|
<Box sx={STYLES.BOX_PLAY_MODE}>
|
||||||
|
{isEditAvaliable ? (
|
||||||
|
<Fab size={"small"} color={"grey.700"} title={"Редактировать"} onClick={handleToggleEditMode} disabled={openPanelsManager}>
|
||||||
|
<Icon>edit</Icon>
|
||||||
|
</Fab>
|
||||||
|
) : null}
|
||||||
|
<Fab size={"small"} color={"grey.700"} title={"Менеджер панелей"} onClick={handleOpenPanelsManager} disabled={openPanelsManager}>
|
||||||
|
<Icon>file_open</Icon>
|
||||||
|
</Fab>
|
||||||
|
<Fab size={"small"} color={"grey.700"} title={"Переменные панели"} onClick={handleToggleOpenPanelVariables} disabled={openPanelsManager}>
|
||||||
|
<Icon>mediation</Icon>
|
||||||
|
</Fab>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
//При открытии панели
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//addComponent(COMPONETNS[0]);
|
//Считаем параметры открытия панели
|
||||||
//addComponent(COMPONETNS[3]);
|
const panelPrms = getNavigationSearch();
|
||||||
//addComponent(COMPONETNS[4]);
|
//Если указан рег. номер панели - устанавливаем изначальный рег. номер панели
|
||||||
//addComponent(COMPONETNS[1]);
|
if (panelPrms.SCODE) loadPanel(panelPrms.SCODE);
|
||||||
//addComponent(COMPONETNS[2]);
|
//Если рег. номера панели нет - открываем менеджер панелей
|
||||||
|
else handleOpenPanelsManager();
|
||||||
|
//Указываем, что инициализация пройдена
|
||||||
|
setInit(false);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
//Текущие значения панели
|
//При изменении мнемокода панели
|
||||||
const values = Object.keys(valueProviders).reduce((res, key) => ({ ...res, ...valueProviders[key] }), {});
|
useEffect(() => {
|
||||||
|
//Устанавливаем заголовок в шапке приложения
|
||||||
//Меню добавления
|
setAppBarTitle(panelName ? `Панель [${panelName}]` : APP_BAR_TITLE_DEFAULT);
|
||||||
const addMenu = (
|
}, [panelName, setAppBarTitle]);
|
||||||
<Menu anchorEl={addMenuAnchorEl} open={Boolean(addMenuAnchorEl)} onClose={toggleAddMenu}>
|
|
||||||
{COMPONETNS.map((comp, i) => (
|
|
||||||
<MenuItem key={i} onClick={() => handleAddMenuItemClick(comp)}>
|
|
||||||
{comp.name}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
|
|
||||||
//Кнопка редактирования
|
|
||||||
const editButton = !editMode && (
|
|
||||||
<Fab sx={STYLES.FAB_EDIT} size={"small"} color={"grey.700"} title={"Редактировать"} onClick={toggleEditMode}>
|
|
||||||
<Icon>edit</Icon>
|
|
||||||
</Fab>
|
|
||||||
);
|
|
||||||
|
|
||||||
//Панель инструмментов
|
|
||||||
const toolBar = (
|
|
||||||
<P8PEditorToolBar
|
|
||||||
items={[
|
|
||||||
{ icon: "play_arrow", title: "Запустить", onClick: toggleEditMode },
|
|
||||||
{
|
|
||||||
icon: "add",
|
|
||||||
title: "Добавить элемент",
|
|
||||||
onClick: handleAddClick
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
//Генерация содержимого
|
//Генерация содержимого
|
||||||
return (
|
return (
|
||||||
<Box sx={STYLES.CONTAINER}>
|
<>
|
||||||
{editButton}
|
{!init ? (
|
||||||
{addMenu}
|
<>
|
||||||
<Grid container sx={STYLES.GRID_CONTAINER} columns={25}>
|
{runTimeButtons}
|
||||||
<Grid item xs={editMode ? 20 : 25}>
|
{openPanelsManager && (
|
||||||
<ResponsiveGridLayout
|
<PanelsManager current={panel} isEditable={true} onPanelSelect={handlePanelSelect} onCancel={handleCancelPanelsManager} />
|
||||||
rowHeight={5}
|
)}
|
||||||
className={"layout"}
|
{panel && (
|
||||||
layouts={layouts}
|
<PanelEditor
|
||||||
breakpoints={{ lg: 1200 }}
|
panel={panel}
|
||||||
cols={{ lg: 12 }}
|
panelName={panelName}
|
||||||
onBreakpointChange={handleBreakpointChange}
|
editMode={editMode}
|
||||||
onLayoutChange={handleLayoutChange}
|
isPanelChanged={isPanelChanged}
|
||||||
useCSSTransforms={true}
|
isPanelVariablesOpened={openPanelVariables}
|
||||||
compactType={"vertical"}
|
onPanelChanged={setPanelChanged}
|
||||||
isDraggable={editMode}
|
onPanelClose={handlePanelClose}
|
||||||
isResizable={editMode}
|
onEditModeToggle={handleToggleEditMode}
|
||||||
>
|
onPanelsManagerOpen={handleOpenPanelsManager}
|
||||||
{layouts[breakpoint].map(item => (
|
onPanelVariablesClose={handleToggleOpenPanelVariables}
|
||||||
<LayoutItem
|
/>
|
||||||
key={item.i}
|
)}
|
||||||
onSettingsClick={handleComponentSettingsClick}
|
</>
|
||||||
onDeleteClick={handleComponentDeleteClick}
|
) : null}
|
||||||
item={item}
|
</>
|
||||||
editMode={editMode}
|
|
||||||
selected={editMode && editComponent === item.i}
|
|
||||||
>
|
|
||||||
<ComponentView
|
|
||||||
id={item.i}
|
|
||||||
path={components[item.i]?.path}
|
|
||||||
settings={components[item.i]?.settings}
|
|
||||||
values={values}
|
|
||||||
onValuesChange={handleComponentValuesChange}
|
|
||||||
/>
|
|
||||||
</LayoutItem>
|
|
||||||
))}
|
|
||||||
</ResponsiveGridLayout>
|
|
||||||
</Grid>
|
|
||||||
{editMode && (
|
|
||||||
<Grid item xs={5} sx={STYLES.GRID_ITEM_INSPECTOR}>
|
|
||||||
{toolBar}
|
|
||||||
{editComponent && (
|
|
||||||
<>
|
|
||||||
<ComponentEditor
|
|
||||||
id={editComponent}
|
|
||||||
path={components[editComponent].path}
|
|
||||||
settings={components[editComponent].settings}
|
|
||||||
valueProviders={valueProviders}
|
|
||||||
onSettingsChange={handleComponentSettingsChange}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
22
db/P8PNL_PE_PANEL.sql
Normal file
22
db/P8PNL_PE_PANEL.sql
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
Парус 8 - Панели мониторинга - Общие - Редактор панелей
|
||||||
|
Панели
|
||||||
|
*/
|
||||||
|
create table P8PNL_PE_PANEL
|
||||||
|
(
|
||||||
|
RN number(17) not null, -- Рег. номер записи
|
||||||
|
CODE varchar2(100) not null, -- Мнемокод
|
||||||
|
NAME varchar2(2000) not null, -- Наименование
|
||||||
|
AUTHOR varchar2(30) not null, -- Автор
|
||||||
|
CH_DATE date not null, -- Дата последнего изменения
|
||||||
|
READY number(1) default 0 not null, -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||||
|
PBL number(1) default 0 not null, -- Флаг публичности (0 - нет, 1 - да)
|
||||||
|
PANEL clob, -- Описание панели
|
||||||
|
constraint C_P8PNL_PE_PANEL_RN_PK primary key (RN),
|
||||||
|
constraint C_P8PNL_PE_PANEL_CODE_NB check (rtrim(CODE) is not null),
|
||||||
|
constraint C_P8PNL_PE_PANEL_NAME_NB check (rtrim(NAME) is not null),
|
||||||
|
constraint C_P8PNL_PE_PANEL_AUTHOR_FK foreign key (AUTHOR) references USERLIST (AUTHID) on delete cascade,
|
||||||
|
constraint C_P8PNL_PE_PANEL_READY_VAL check (READY in (0, 1)),
|
||||||
|
constraint C_P8PNL_PE_PANEL_PBL_VAL check (PBL in (0, 1)),
|
||||||
|
constraint C_P8PNL_PE_PANEL_UN unique (CODE)
|
||||||
|
);
|
||||||
@ -6,6 +6,85 @@ create or replace package PKG_P8PANELS_PE as
|
|||||||
SCODE in varchar2, -- Мнемокод пользовательской процедуры
|
SCODE in varchar2, -- Мнемокод пользовательской процедуры
|
||||||
COUT out clob -- Сериализованный список аргументов
|
COUT out clob -- Сериализованный список аргументов
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/* Получение списка панелей */
|
||||||
|
procedure PANEL_LIST
|
||||||
|
(
|
||||||
|
COUT out clob -- Сериализованный список панелей
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Считывание базовой информации о панели */
|
||||||
|
procedure PANEL_ATTRS_GET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
NCHECK_ACCESS in number := 1, -- Признак проверки прав доступа (0 - нет, 1 - да)
|
||||||
|
COUT out clob -- Базовой информация о панели
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Считывание базовой информации о панели (по мнемокоду) */
|
||||||
|
procedure PANEL_ATTRS_GET_BY_CODE
|
||||||
|
(
|
||||||
|
SCODE in varchar2, -- Мнемокод панели
|
||||||
|
NCHECK_ACCESS in number := 1, -- Признак проверки прав доступа (0 - нет, 1 - да)
|
||||||
|
COUT out clob -- Базовой информация о панели
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Добавление панели */
|
||||||
|
procedure PANEL_INSERT
|
||||||
|
(
|
||||||
|
SCODE in varchar2, -- Мнемокод
|
||||||
|
SNAME in varchar2, -- Наименование
|
||||||
|
NRN out number -- Рег. номер добавленной панели
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Исправление панели */
|
||||||
|
procedure PANEL_UPDATE
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
SCODE in varchar2, -- Мнемокод
|
||||||
|
SNAME in varchar2 -- Наименование
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Удаление панели */
|
||||||
|
procedure PANEL_DELETE
|
||||||
|
(
|
||||||
|
NRN in number -- Рег. номер панели
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Установка признака "готовности" панели */
|
||||||
|
procedure PANEL_READY_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Установка признака "публичности" панели */
|
||||||
|
procedure PANEL_PBL_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный)
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Импорт панели из файла */
|
||||||
|
procedure PANEL_IMPORT
|
||||||
|
(
|
||||||
|
CPANEL in clob, -- Описание импортируемой панели
|
||||||
|
NRN out number -- Рег. номер импортированной панели
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Получение описания панели */
|
||||||
|
procedure PANEL_DESC_GET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
COUT out clob -- Сериализованное описание панели
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Сохранение описания панели */
|
||||||
|
procedure PANEL_DESC_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
CPANEL in clob -- Описание панели
|
||||||
|
);
|
||||||
|
|
||||||
end PKG_P8PANELS_PE;
|
end PKG_P8PANELS_PE;
|
||||||
/
|
/
|
||||||
@ -18,110 +97,169 @@ create or replace package body PKG_P8PANELS_PE as
|
|||||||
COUT out clob -- Сериализованный список аргументов
|
COUT out clob -- Сериализованный список аргументов
|
||||||
)
|
)
|
||||||
is
|
is
|
||||||
SRESP_ARG PKG_STD.TSTRING; -- Имя выходного визуализируемого параметра
|
|
||||||
begin
|
begin
|
||||||
/* Обращаемся к процедуре */
|
/* Базовое описание пользовательской процедуры */
|
||||||
for C in (select T.RN NRN,
|
PKG_P8PANELS_PE_BASE.USERPROCS_DESC(SCODE => SCODE, COUT => COUT);
|
||||||
T.PROCNAME SPROC_NAME,
|
|
||||||
T.PROCTYPE NPROC_TYPE
|
|
||||||
from USERPROCS T
|
|
||||||
where T.CODE = SCODE)
|
|
||||||
loop
|
|
||||||
/* Проверим возможность использования ПП в качестве источника данных */
|
|
||||||
if (C.NPROC_TYPE <> 0) then
|
|
||||||
P_EXCEPTION(0,
|
|
||||||
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: должна иметь тип "Хранимая процедура".',
|
|
||||||
SCODE);
|
|
||||||
end if;
|
|
||||||
if (C.SPROC_NAME is null) then
|
|
||||||
P_EXCEPTION(0,
|
|
||||||
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: не указана хранимая процедура.',
|
|
||||||
SCODE);
|
|
||||||
end if;
|
|
||||||
/* Начинаем формирование XML */
|
|
||||||
PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_);
|
|
||||||
/* Открываем корень */
|
|
||||||
PKG_XFAST.DOWN_NODE(SNAME => 'XDATA');
|
|
||||||
/* Открываем описание процедуры */
|
|
||||||
PKG_XFAST.DOWN_NODE(SNAME => 'XUSERPROC');
|
|
||||||
/* Обходим параметры */
|
|
||||||
for P in (select T.PARAMTYPE NTYPE,
|
|
||||||
T.PARAMNAME SNAME,
|
|
||||||
T.NAME SCAPTION,
|
|
||||||
T.DATATYPE NDATA_TYPE,
|
|
||||||
case T.DATATYPE
|
|
||||||
when 0 then
|
|
||||||
'STR'
|
|
||||||
when 1 then
|
|
||||||
'NUMB'
|
|
||||||
when 2 then
|
|
||||||
'DATE'
|
|
||||||
else
|
|
||||||
null
|
|
||||||
end SDATA_TYPE,
|
|
||||||
T.MANDATORY NREQ,
|
|
||||||
T.VISUALIZE NVISUALIZE
|
|
||||||
from USERPROCSPARAMS T
|
|
||||||
where T.PRN = C.NRN
|
|
||||||
order by T.POSITION)
|
|
||||||
loop
|
|
||||||
/* В результирующий список забираем только входные поддерживаемого типа */
|
|
||||||
if ((P.NTYPE = 0) and (P.SDATA_TYPE is not null)) then
|
|
||||||
/* Открываем описание аргумента */
|
|
||||||
PKG_XFAST.DOWN_NODE(SNAME => 'arguments');
|
|
||||||
/* Описываем аргумент */
|
|
||||||
PKG_XFAST.ATTR(SNAME => 'name', SVALUE => P.SNAME);
|
|
||||||
PKG_XFAST.ATTR(SNAME => 'caption', SVALUE => P.SCAPTION);
|
|
||||||
PKG_XFAST.ATTR(SNAME => 'dataType', SVALUE => P.SDATA_TYPE);
|
|
||||||
PKG_XFAST.ATTR(SNAME => 'req',
|
|
||||||
BVALUE => case P.NREQ
|
|
||||||
when 1 then
|
|
||||||
true
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end);
|
|
||||||
/* Закрываем описание аргумента */
|
|
||||||
PKG_XFAST.UP();
|
|
||||||
end if;
|
|
||||||
/* Если встретился визуализируемый параметр типа CLOB */
|
|
||||||
if ((P.NVISUALIZE = 1) and (P.NDATA_TYPE = 4)) then
|
|
||||||
if (SRESP_ARG is null) then
|
|
||||||
SRESP_ARG := P.SNAME;
|
|
||||||
else
|
|
||||||
/* Это уже второй такой - ошибка */
|
|
||||||
P_EXCEPTION(0,
|
|
||||||
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: имеет более одного выходного параметра типа "Текстовые данные" с признаком "Визуализировать после выполнения".',
|
|
||||||
SCODE);
|
|
||||||
end if;
|
|
||||||
end if;
|
|
||||||
end loop;
|
|
||||||
/* Сформируем описание хранимой процедуры */
|
|
||||||
PKG_XFAST.DOWN_NODE(SNAME => 'stored');
|
|
||||||
PKG_XFAST.ATTR(SNAME => 'name', SVALUE => C.SPROC_NAME);
|
|
||||||
PKG_XFAST.ATTR(SNAME => 'respArg', SVALUE => SRESP_ARG);
|
|
||||||
PKG_XFAST.UP();
|
|
||||||
/* Закрываем описание процедуры */
|
|
||||||
PKG_XFAST.UP();
|
|
||||||
/* Закрываем описание корня */
|
|
||||||
PKG_XFAST.UP();
|
|
||||||
/* Сериализуем */
|
|
||||||
COUT := PKG_XFAST.SERIALIZE_TO_CLOB();
|
|
||||||
/* Завершаем формирование XML */
|
|
||||||
PKG_XFAST.EPILOGUE();
|
|
||||||
end loop;
|
|
||||||
/* Если ничего не нашли */
|
|
||||||
if (COUT is null) then
|
|
||||||
P_EXCEPTION(0,
|
|
||||||
'Пользовательская процедура "%s" не определена.',
|
|
||||||
COALESCE(SCODE, '<НЕ УКАЗАНА>'));
|
|
||||||
end if;
|
|
||||||
/* Проверим возможность использования ПП в качестве источника данных - должен быть выходной визуализируемый параметр типа CLOB */
|
|
||||||
if (SRESP_ARG is null) then
|
|
||||||
P_EXCEPTION(0,
|
|
||||||
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: должна иметь выходной параметр типа "Текстовые данные" с признаком "Визуализировать после выполнения".',
|
|
||||||
SCODE);
|
|
||||||
end if;
|
|
||||||
end USERPROCS_DESC;
|
end USERPROCS_DESC;
|
||||||
|
|
||||||
|
/* Получение списка панелей */
|
||||||
|
procedure PANEL_LIST
|
||||||
|
(
|
||||||
|
COUT out clob -- Сериализованный список панелей
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Базово сформируем список */
|
||||||
|
COUT := PKG_P8PANELS_PE_BASE.PANEL_LIST_GET(SUSER => UTILIZER());
|
||||||
|
end PANEL_LIST;
|
||||||
|
|
||||||
|
/* Считывание базовой информации о панели */
|
||||||
|
procedure PANEL_ATTRS_GET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
NCHECK_ACCESS in number := 1, -- Признак проверки прав доступа (0 - нет, 1 - да)
|
||||||
|
COUT out clob -- Базовой информация о панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Если права необходимо проверить */
|
||||||
|
if (NCHECK_ACCESS = 1) then
|
||||||
|
/* Проверим права доступа */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_VIEW(NRN => NRN, SUSER => UTILIZER());
|
||||||
|
end if;
|
||||||
|
/* Считываем базовую информацию о панели */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_ATTRS_GET(NRN => NRN, COUT => COUT);
|
||||||
|
end PANEL_ATTRS_GET;
|
||||||
|
|
||||||
|
/* Считывание базовой информации о панели (по мнемокоду) */
|
||||||
|
procedure PANEL_ATTRS_GET_BY_CODE
|
||||||
|
(
|
||||||
|
SCODE in varchar2, -- Мнемокод панели
|
||||||
|
NCHECK_ACCESS in number := 1, -- Признак проверки прав доступа (0 - нет, 1 - да)
|
||||||
|
COUT out clob -- Базовой информация о панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
NRN PKG_STD.TREF; -- Рег. номер панели
|
||||||
|
begin
|
||||||
|
/* Считываем рег. номер панели */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_FIND_BY_CODE(NFLAG_SMART => 0, NFLAG_OPTION => 0, SCODE => SCODE, NRN => NRN);
|
||||||
|
/* Если права необходимо проверить */
|
||||||
|
if (NCHECK_ACCESS = 1) then
|
||||||
|
/* Проверим права доступа */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_VIEW(NRN => NRN, SUSER => UTILIZER());
|
||||||
|
end if;
|
||||||
|
/* Считываем базовую информацию о панели */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_ATTRS_GET(NRN => NRN, COUT => COUT);
|
||||||
|
end PANEL_ATTRS_GET_BY_CODE;
|
||||||
|
|
||||||
|
/* Добавление панели */
|
||||||
|
procedure PANEL_INSERT
|
||||||
|
(
|
||||||
|
SCODE in varchar2, -- Мнемокод
|
||||||
|
SNAME in varchar2, -- Наименование
|
||||||
|
NRN out number -- Рег. номер добавленной панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Базовое добавление */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_INSERT(SCODE => SCODE, SNAME => SNAME, NRN => NRN);
|
||||||
|
end PANEL_INSERT;
|
||||||
|
|
||||||
|
/* Исправление панели */
|
||||||
|
procedure PANEL_UPDATE
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
SCODE in varchar2, -- Мнемокод
|
||||||
|
SNAME in varchar2 -- Наименование
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Проверим права доступа */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||||
|
/* Базовое исправление */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_UPDATE(NRN => NRN, SCODE => SCODE, SNAME => SNAME);
|
||||||
|
end PANEL_UPDATE;
|
||||||
|
|
||||||
|
/* Удаление панели */
|
||||||
|
procedure PANEL_DELETE
|
||||||
|
(
|
||||||
|
NRN in number -- Рег. номер панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Проверим права доступа */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||||
|
/* Базовое удаление */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_DELETE(NRN => NRN);
|
||||||
|
end PANEL_DELETE;
|
||||||
|
|
||||||
|
/* Установка признака "готовности" панели */
|
||||||
|
procedure PANEL_READY_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Проверим права доступа */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||||
|
/* Базовая установка признака готовности */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_READY_SET(NRN => NRN, NREADY => NREADY);
|
||||||
|
end PANEL_READY_SET;
|
||||||
|
|
||||||
|
/* Установка признака "публичности" панели */
|
||||||
|
procedure PANEL_PBL_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный)
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Проверим права доступа */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||||
|
/* Базовая установка признака публичности */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_PBL_SET(NRN => NRN, NPBL => NPBL);
|
||||||
|
end PANEL_PBL_SET;
|
||||||
|
|
||||||
|
/* Импорт панели из файла */
|
||||||
|
procedure PANEL_IMPORT
|
||||||
|
(
|
||||||
|
CPANEL in clob, -- Описание импортируемой панели
|
||||||
|
NRN out number -- Рег. номер импортированной панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Базовое импортирование панели */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_IMPORT(CPANEL => CPANEL, NRN => NRN);
|
||||||
|
end PANEL_IMPORT;
|
||||||
|
|
||||||
|
/* Получение описания панели */
|
||||||
|
procedure PANEL_DESC_GET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
COUT out clob -- Сериализованное описание панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Проверим права доступа */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_VIEW(NRN => NRN, SUSER => UTILIZER());
|
||||||
|
/* Получим описание панели */
|
||||||
|
COUT := PKG_P8PANELS_PE_BASE.PANEL_DESC_GET(NRN => NRN);
|
||||||
|
end PANEL_DESC_GET;
|
||||||
|
|
||||||
|
/* Сохранение описания панели */
|
||||||
|
procedure PANEL_DESC_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
CPANEL in clob -- Описание панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Проверим права доступа */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_ACCESS_MODIFY(NRN => NRN, SUSER => UTILIZER());
|
||||||
|
/* Сохраняем описание панели */
|
||||||
|
PKG_P8PANELS_PE_BASE.PANEL_DESC_SET(NRN => NRN, CPANEL => CPANEL);
|
||||||
|
end PANEL_DESC_SET;
|
||||||
|
|
||||||
end PKG_P8PANELS_PE;
|
end PKG_P8PANELS_PE;
|
||||||
/
|
/
|
||||||
|
|||||||
702
db/PKG_P8PANELS_PE_BASE.pck
Normal file
702
db/PKG_P8PANELS_PE_BASE.pck
Normal file
@ -0,0 +1,702 @@
|
|||||||
|
create or replace package PKG_P8PANELS_PE_BASE as
|
||||||
|
|
||||||
|
/* Описание пользовательской процедуры */
|
||||||
|
procedure USERPROCS_DESC
|
||||||
|
(
|
||||||
|
SCODE in varchar2, -- Мнемокод пользовательской процедуры
|
||||||
|
COUT out clob -- Сериализованный список аргументов
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Поиск рег. номера панели по мнемокоду */
|
||||||
|
procedure PANEL_FIND_BY_CODE
|
||||||
|
(
|
||||||
|
NFLAG_SMART in number, -- Признак генерации исключения (0 - да, 1 - нет)
|
||||||
|
NFLAG_OPTION in number, -- Признак генерации исключения для пустого sCODE (0 - да, 1 - нет)
|
||||||
|
SCODE in varchar2, -- Мнемокод панели
|
||||||
|
NRN out number -- Рег. номер панели
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Получение признака возможности изменения панели */
|
||||||
|
function PANEL_ACCESS_SIGN_MODIFY
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
SUSER in varchar2 -- Имя пользователя
|
||||||
|
) return number; -- Признак возможности изменения панели (0 - нет, 1 - да)
|
||||||
|
|
||||||
|
|
||||||
|
/* Проверка возможности изменения панели */
|
||||||
|
procedure PANEL_ACCESS_MODIFY
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
SUSER in varchar2 -- Имя пользователя
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Получение признака возможности просмотра панели */
|
||||||
|
function PANEL_ACCESS_SIGN_VIEW
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
SUSER in varchar2 -- Имя пользователя
|
||||||
|
) return number; -- Признак возможности просмотра панели (0 - нет, 1 - да)
|
||||||
|
|
||||||
|
/* Проверка возможности просмотра панели */
|
||||||
|
procedure PANEL_ACCESS_VIEW
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
SUSER in varchar2 -- Имя пользователя
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Считывание базовой информации о панели */
|
||||||
|
procedure PANEL_ATTRS_GET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
COUT out clob -- Базовой информация о панели
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Добавление панели */
|
||||||
|
procedure PANEL_INSERT
|
||||||
|
(
|
||||||
|
SCODE in varchar2, -- Мнемокод
|
||||||
|
SNAME in varchar2, -- Наименование
|
||||||
|
NREADY in number := 0, -- Признак готовности к использованию
|
||||||
|
NPBL in number := 0, -- Публичность
|
||||||
|
CPANEL in clob := null, -- Описание панели
|
||||||
|
NRN out number -- Рег. номер добавленного запроса
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Исправление панели */
|
||||||
|
procedure PANEL_UPDATE
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
SCODE in varchar2, -- Мнемокод
|
||||||
|
SNAME in varchar2 -- Наименование
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Удаление панели */
|
||||||
|
procedure PANEL_DELETE
|
||||||
|
(
|
||||||
|
NRN in number -- Рег. номер панели
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Установка признака "готовности" панели */
|
||||||
|
procedure PANEL_READY_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Установка признака "публичности" панели */
|
||||||
|
procedure PANEL_PBL_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный)
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Загрузка описания панели */
|
||||||
|
function PANEL_DESC_GET
|
||||||
|
(
|
||||||
|
NRN in number -- Рег. номер панели
|
||||||
|
) return clob; -- XML-описание панели
|
||||||
|
|
||||||
|
/* Сохранение описания панели */
|
||||||
|
procedure PANEL_DESC_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
CPANEL in clob -- Описание панели
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Импорт панели из файла */
|
||||||
|
procedure PANEL_IMPORT
|
||||||
|
(
|
||||||
|
CPANEL in clob, -- Описание импортируемой панели
|
||||||
|
NRN out number -- Рег. номер импортированной панели
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Формирование списка панелей */
|
||||||
|
function PANEL_LIST_GET
|
||||||
|
(
|
||||||
|
SUSER in varchar2 -- Имя пользователя
|
||||||
|
) return clob; -- Список панелей
|
||||||
|
|
||||||
|
end PKG_P8PANELS_PE_BASE;
|
||||||
|
/
|
||||||
|
create or replace package body PKG_P8PANELS_PE_BASE as
|
||||||
|
|
||||||
|
/* Константы - Теги для сериализации */
|
||||||
|
STAG_DATA constant PKG_STD.TSTRING := 'XDATA'; -- Данные
|
||||||
|
STAG_PANELS constant PKG_STD.TSTRING := 'XPANELS'; -- Панели
|
||||||
|
STAG_PANEL constant PKG_STD.TSTRING := 'XPANEL'; -- Панель
|
||||||
|
STAG_PANEL_INFO constant PKG_STD.TSTRING := 'XPANEL_INFO'; -- Информация о панели
|
||||||
|
STAG_PANEL_ATTRS constant PKG_STD.TSTRING := 'XPANEL_ATTRS'; -- Атрибуты панели
|
||||||
|
|
||||||
|
/* Константы - Атрибуты для сериализации */
|
||||||
|
SATTR_RN constant PKG_STD.TSTRING := 'rn'; -- Регистрационный номер
|
||||||
|
SATTR_CODE constant PKG_STD.TSTRING := 'code'; -- Код
|
||||||
|
SATTR_NAME constant PKG_STD.TSTRING := 'name'; -- Имя
|
||||||
|
SATTR_AUTHOR constant PKG_STD.TSTRING := 'author'; -- Автор
|
||||||
|
SATTR_CH_DATE constant PKG_STD.TSTRING := 'chDate'; -- Дата изменения
|
||||||
|
SATTR_READY constant PKG_STD.TSTRING := 'ready'; -- Готовность к использованию
|
||||||
|
SATTR_PBL constant PKG_STD.TSTRING := 'pbl'; -- Публичность
|
||||||
|
SATTR_MODIFY constant PKG_STD.TSTRING := 'modify'; -- Изменяемость
|
||||||
|
|
||||||
|
/* Описание пользовательской процедуры */
|
||||||
|
procedure USERPROCS_DESC
|
||||||
|
(
|
||||||
|
SCODE in varchar2, -- Мнемокод пользовательской процедуры
|
||||||
|
COUT out clob -- Сериализованный список аргументов
|
||||||
|
)
|
||||||
|
is
|
||||||
|
SRESP_ARG PKG_STD.TSTRING; -- Имя выходного визуализируемого параметра
|
||||||
|
begin
|
||||||
|
/* Обращаемся к процедуре */
|
||||||
|
for C in (select T.RN NRN,
|
||||||
|
T.PROCNAME SPROC_NAME,
|
||||||
|
T.PROCTYPE NPROC_TYPE
|
||||||
|
from USERPROCS T
|
||||||
|
where T.CODE = SCODE)
|
||||||
|
loop
|
||||||
|
/* Проверим возможность использования ПП в качестве источника данных */
|
||||||
|
if (C.NPROC_TYPE <> 0) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: должна иметь тип "Хранимая процедура".',
|
||||||
|
SCODE);
|
||||||
|
end if;
|
||||||
|
if (C.SPROC_NAME is null) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: не указана хранимая процедура.',
|
||||||
|
SCODE);
|
||||||
|
end if;
|
||||||
|
/* Начинаем формирование XML */
|
||||||
|
PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_);
|
||||||
|
/* Открываем корень */
|
||||||
|
PKG_XFAST.DOWN_NODE(SNAME => 'XDATA');
|
||||||
|
/* Открываем описание процедуры */
|
||||||
|
PKG_XFAST.DOWN_NODE(SNAME => 'XUSERPROC');
|
||||||
|
/* Обходим параметры */
|
||||||
|
for P in (select T.PARAMTYPE NTYPE,
|
||||||
|
T.PARAMNAME SNAME,
|
||||||
|
T.NAME SCAPTION,
|
||||||
|
T.DATATYPE NDATA_TYPE,
|
||||||
|
case T.DATATYPE
|
||||||
|
when 0 then
|
||||||
|
'STR'
|
||||||
|
when 1 then
|
||||||
|
'NUMB'
|
||||||
|
when 2 then
|
||||||
|
'DATE'
|
||||||
|
else
|
||||||
|
null
|
||||||
|
end SDATA_TYPE,
|
||||||
|
T.MANDATORY NREQ,
|
||||||
|
T.VISUALIZE NVISUALIZE
|
||||||
|
from USERPROCSPARAMS T
|
||||||
|
where T.PRN = C.NRN
|
||||||
|
order by T.POSITION)
|
||||||
|
loop
|
||||||
|
/* В результирующий список забираем только входные поддерживаемого типа */
|
||||||
|
if ((P.NTYPE = 0) and (P.SDATA_TYPE is not null)) then
|
||||||
|
/* Открываем описание аргумента */
|
||||||
|
PKG_XFAST.DOWN_NODE(SNAME => 'arguments');
|
||||||
|
/* Описываем аргумент */
|
||||||
|
PKG_XFAST.ATTR(SNAME => 'name', SVALUE => P.SNAME);
|
||||||
|
PKG_XFAST.ATTR(SNAME => 'caption', SVALUE => P.SCAPTION);
|
||||||
|
PKG_XFAST.ATTR(SNAME => 'dataType', SVALUE => P.SDATA_TYPE);
|
||||||
|
PKG_XFAST.ATTR(SNAME => 'req',
|
||||||
|
BVALUE => case P.NREQ
|
||||||
|
when 1 then
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end);
|
||||||
|
/* Закрываем описание аргумента */
|
||||||
|
PKG_XFAST.UP();
|
||||||
|
end if;
|
||||||
|
/* Если встретился визуализируемый параметр типа CLOB */
|
||||||
|
if ((P.NVISUALIZE = 1) and (P.NDATA_TYPE = 4)) then
|
||||||
|
if (SRESP_ARG is null) then
|
||||||
|
SRESP_ARG := P.SNAME;
|
||||||
|
else
|
||||||
|
/* Это уже второй такой - ошибка */
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: имеет более одного выходного параметра типа "Текстовые данные" с признаком "Визуализировать после выполнения".',
|
||||||
|
SCODE);
|
||||||
|
end if;
|
||||||
|
end if;
|
||||||
|
end loop;
|
||||||
|
/* Сформируем описание хранимой процедуры */
|
||||||
|
PKG_XFAST.DOWN_NODE(SNAME => 'stored');
|
||||||
|
PKG_XFAST.ATTR(SNAME => 'name', SVALUE => C.SPROC_NAME);
|
||||||
|
PKG_XFAST.ATTR(SNAME => 'respArg', SVALUE => SRESP_ARG);
|
||||||
|
PKG_XFAST.UP();
|
||||||
|
/* Закрываем описание процедуры */
|
||||||
|
PKG_XFAST.UP();
|
||||||
|
/* Закрываем описание корня */
|
||||||
|
PKG_XFAST.UP();
|
||||||
|
/* Сериализуем */
|
||||||
|
COUT := PKG_XFAST.SERIALIZE_TO_CLOB();
|
||||||
|
/* Завершаем формирование XML */
|
||||||
|
PKG_XFAST.EPILOGUE();
|
||||||
|
end loop;
|
||||||
|
/* Если ничего не нашли */
|
||||||
|
if (COUT is null) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Пользовательская процедура "%s" не определена.',
|
||||||
|
COALESCE(SCODE, '<НЕ УКАЗАНА>'));
|
||||||
|
end if;
|
||||||
|
/* Проверим возможность использования ПП в качестве источника данных - должен быть выходной визуализируемый параметр типа CLOB */
|
||||||
|
if (SRESP_ARG is null) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Пользовательская процедура "%s" не может быть использована в качестве источника данных: должна иметь выходной параметр типа "Текстовые данные" с признаком "Визуализировать после выполнения".',
|
||||||
|
SCODE);
|
||||||
|
end if;
|
||||||
|
end USERPROCS_DESC;
|
||||||
|
|
||||||
|
/* Поиск рег. номера панели по мнемокоду */
|
||||||
|
procedure PANEL_FIND_BY_CODE
|
||||||
|
(
|
||||||
|
NFLAG_SMART in number, -- Признак генерации исключения (0 - да, 1 - нет)
|
||||||
|
NFLAG_OPTION in number, -- Признак генерации исключения для пустого sCODE (0 - да, 1 - нет)
|
||||||
|
SCODE in varchar2, -- Мнемокод панели
|
||||||
|
NRN out number -- Рег. номер панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Если мнемокод не задан */
|
||||||
|
if (RTRIM(SCODE) is null) then
|
||||||
|
/* Генерация исключения */
|
||||||
|
if (NFLAG_OPTION = 0) then
|
||||||
|
P_EXCEPTION(NFLAG_SMART, 'Не задан мнемокод панели.');
|
||||||
|
end if;
|
||||||
|
/* Мнемокод задан */
|
||||||
|
else
|
||||||
|
/* Поиск записи */
|
||||||
|
begin
|
||||||
|
select RN into NRN from P8PNL_PE_PANEL where CODE = SCODE;
|
||||||
|
exception
|
||||||
|
when NO_DATA_FOUND then
|
||||||
|
P_EXCEPTION(NFLAG_SMART, 'Панель "%s" не определена.', SCODE);
|
||||||
|
end;
|
||||||
|
end if;
|
||||||
|
end PANEL_FIND_BY_CODE;
|
||||||
|
|
||||||
|
/* Считывание записи панели */
|
||||||
|
function PANEL_GET
|
||||||
|
(
|
||||||
|
NRN in number -- Рег. номер панели
|
||||||
|
) return P8PNL_PE_PANEL%rowtype -- Запись панели
|
||||||
|
is
|
||||||
|
RRES P8PNL_PE_PANEL%rowtype; -- Буфер для результата
|
||||||
|
begin
|
||||||
|
/* Считываем запись */
|
||||||
|
select T.* into RRES from P8PNL_PE_PANEL T where T.RN = NRN;
|
||||||
|
/* Возвращаем запись */
|
||||||
|
return RRES;
|
||||||
|
exception
|
||||||
|
when NO_DATA_FOUND then
|
||||||
|
PKG_MSG.RECORD_NOT_FOUND(NFLAG_SMART => 0, NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||||
|
end PANEL_GET;
|
||||||
|
|
||||||
|
/* Получение признака возможности изменения панели */
|
||||||
|
function PANEL_ACCESS_SIGN_MODIFY
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
SUSER in varchar2 -- Имя пользователя
|
||||||
|
) return number -- Признак возможности изменения панели (0 - нет, 1 - да)
|
||||||
|
is
|
||||||
|
RP P8PNL_PE_PANEL%rowtype; -- Проверяемая запись панели
|
||||||
|
begin
|
||||||
|
/* Читаем панель */
|
||||||
|
RP := PANEL_GET(NRN => NRN);
|
||||||
|
/* Менять можно только свою панель */
|
||||||
|
if (RP.AUTHOR = SUSER) then
|
||||||
|
return 1;
|
||||||
|
end if;
|
||||||
|
/* Проверки не пройдены - менять нельзя */
|
||||||
|
return 0;
|
||||||
|
end PANEL_ACCESS_SIGN_MODIFY;
|
||||||
|
|
||||||
|
/* Проверка возможности изменения панели */
|
||||||
|
procedure PANEL_ACCESS_MODIFY
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
SUSER in varchar2 -- Имя пользователя
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Получим признак возможности изменения */
|
||||||
|
if (PANEL_ACCESS_SIGN_MODIFY(NRN => NRN, SUSER => SUSER) = 0) then
|
||||||
|
/* Менять нельзя */
|
||||||
|
P_EXCEPTION(0, 'У Вас нет прав доступа для изменения панели.');
|
||||||
|
end if;
|
||||||
|
end PANEL_ACCESS_MODIFY;
|
||||||
|
|
||||||
|
/* Получение признака возможности просмотра панели */
|
||||||
|
function PANEL_ACCESS_SIGN_VIEW
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
SUSER in varchar2 -- Имя пользователя
|
||||||
|
) return number -- Признак возможности просмотра панели (0 - нет, 1 - да)
|
||||||
|
is
|
||||||
|
RP P8PNL_PE_PANEL%rowtype; -- Проверяемая запись панели
|
||||||
|
begin
|
||||||
|
/* Читаем панель */
|
||||||
|
RP := PANEL_GET(NRN => NRN);
|
||||||
|
/* Смотреть можно только свою или публичную и готовую панель */
|
||||||
|
if (((RP.PBL = 1) and (RP.READY = 1)) or (RP.AUTHOR = SUSER)) then
|
||||||
|
return 1;
|
||||||
|
end if;
|
||||||
|
/* Проверки не пройдены - нельзя смотреть */
|
||||||
|
return 0;
|
||||||
|
end PANEL_ACCESS_SIGN_VIEW;
|
||||||
|
|
||||||
|
/* Проверка возможности просмотра панели */
|
||||||
|
procedure PANEL_ACCESS_VIEW
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
SUSER in varchar2 -- Имя пользователя
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Получим признак возможности просмотра */
|
||||||
|
if (PANEL_ACCESS_SIGN_VIEW(NRN => NRN, SUSER => SUSER) = 0) then
|
||||||
|
/* Смотреть нельзя */
|
||||||
|
P_EXCEPTION(0, 'У Вас нет прав доступа для просмотра панели.');
|
||||||
|
end if;
|
||||||
|
end PANEL_ACCESS_VIEW;
|
||||||
|
|
||||||
|
/* Проверка атрибутов панели */
|
||||||
|
procedure PANEL_CHECK
|
||||||
|
(
|
||||||
|
SCODE in varchar2, -- Мнемокод
|
||||||
|
SNAME in varchar2 -- Наименование
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Мнемокод должен быть задан */
|
||||||
|
if (SCODE is null) then
|
||||||
|
P_EXCEPTION(0, 'Не задан мнемокод панели.');
|
||||||
|
end if;
|
||||||
|
/* Наименование должно быть задано */
|
||||||
|
if (SNAME is null) then
|
||||||
|
P_EXCEPTION(0, 'Не задано наименование панели.');
|
||||||
|
end if;
|
||||||
|
end PANEL_CHECK;
|
||||||
|
|
||||||
|
/* Синхронизация даты изменения панели с текущим временем */
|
||||||
|
procedure PANEL_CH_DATE_SYNC
|
||||||
|
(
|
||||||
|
NRN in number -- Рег. номер панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Установим текущую дату изменения */
|
||||||
|
update P8PNL_PE_PANEL T set T.CH_DATE = sysdate where T.RN = NRN;
|
||||||
|
/* Контроль изменения данных */
|
||||||
|
if (sql%notfound) then
|
||||||
|
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||||
|
end if;
|
||||||
|
end PANEL_CH_DATE_SYNC;
|
||||||
|
|
||||||
|
/* Считывание базовой информации о панели */
|
||||||
|
procedure PANEL_ATTRS_GET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
COUT out clob -- Базовой информация о панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
RP P8PNL_PE_PANEL%rowtype; -- Считываемая запись панели
|
||||||
|
begin
|
||||||
|
/* Читаем панель */
|
||||||
|
RP := PANEL_GET(NRN => NRN);
|
||||||
|
/* Начинаем формирование XML */
|
||||||
|
PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_);
|
||||||
|
/* Открываем корень */
|
||||||
|
PKG_XFAST.DOWN_NODE(SNAME => STAG_DATA);
|
||||||
|
/* Открываем информацию о панели */
|
||||||
|
PKG_XFAST.DOWN_NODE(SNAME => STAG_PANEL_ATTRS);
|
||||||
|
/* Добавляем описание панели */
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_RN, NVALUE => RP.RN);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_CODE, SVALUE => RP.CODE);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => RP.NAME);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_READY, NVALUE => RP.READY);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_PBL, NVALUE => RP.PBL);
|
||||||
|
/* Закрываем информацию о панели */
|
||||||
|
PKG_XFAST.UP();
|
||||||
|
/* Закрываем корень */
|
||||||
|
PKG_XFAST.UP();
|
||||||
|
/* Сериализуем */
|
||||||
|
COUT := PKG_XFAST.SERIALIZE_TO_CLOB();
|
||||||
|
/* Завершаем формирование XML */
|
||||||
|
PKG_XFAST.EPILOGUE();
|
||||||
|
exception
|
||||||
|
when others then
|
||||||
|
/* Завершаем формирование XML */
|
||||||
|
PKG_XFAST.EPILOGUE();
|
||||||
|
/* Вернем ошибку */
|
||||||
|
PKG_STATE.DIAGNOSTICS_STACKED();
|
||||||
|
P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
|
||||||
|
end PANEL_ATTRS_GET;
|
||||||
|
|
||||||
|
/* Добавление панели */
|
||||||
|
procedure PANEL_INSERT
|
||||||
|
(
|
||||||
|
SCODE in varchar2, -- Мнемокод
|
||||||
|
SNAME in varchar2, -- Наименование
|
||||||
|
NREADY in number := 0, -- Признак готовности к использованию
|
||||||
|
NPBL in number := 0, -- Публичность
|
||||||
|
CPANEL in clob := null, -- Описание панели
|
||||||
|
NRN out number -- Рег. номер добавленного запроса
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Проверим параметры */
|
||||||
|
PANEL_CHECK(SCODE => SCODE, SNAME => SNAME);
|
||||||
|
/* Формируем рег. номер */
|
||||||
|
NRN := GEN_ID();
|
||||||
|
/* Добавляем данные */
|
||||||
|
insert into P8PNL_PE_PANEL
|
||||||
|
(RN, CODE, name, AUTHOR, CH_DATE, READY, PBL, PANEL)
|
||||||
|
values
|
||||||
|
(NRN, SCODE, SNAME, UTILIZER(), sysdate, NREADY, NPBL, CPANEL);
|
||||||
|
end PANEL_INSERT;
|
||||||
|
|
||||||
|
/* Исправление панели */
|
||||||
|
procedure PANEL_UPDATE
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
SCODE in varchar2, -- Мнемокод
|
||||||
|
SNAME in varchar2 -- Наименование
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Проверим параметры */
|
||||||
|
PANEL_CHECK(SCODE => SCODE, SNAME => SNAME);
|
||||||
|
/* Изменяем данные */
|
||||||
|
update P8PNL_PE_PANEL T
|
||||||
|
set T.CODE = SCODE,
|
||||||
|
T.NAME = SNAME
|
||||||
|
where T.RN = NRN;
|
||||||
|
/* Контроль изменения данных */
|
||||||
|
if (sql%notfound) then
|
||||||
|
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||||
|
end if;
|
||||||
|
/* Обновим дату изменения панели */
|
||||||
|
PANEL_CH_DATE_SYNC(NRN => NRN);
|
||||||
|
end PANEL_UPDATE;
|
||||||
|
|
||||||
|
/* Удаление панели */
|
||||||
|
procedure PANEL_DELETE
|
||||||
|
(
|
||||||
|
NRN in number -- Рег. номер панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Удаляем запись */
|
||||||
|
delete from P8PNL_PE_PANEL T where T.RN = NRN;
|
||||||
|
/* Контроль изменения данных */
|
||||||
|
if (sql%notfound) then
|
||||||
|
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||||
|
end if;
|
||||||
|
end PANEL_DELETE;
|
||||||
|
|
||||||
|
/* Установка признака "готовности" панели */
|
||||||
|
procedure PANEL_READY_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
NREADY in number -- Флаг готовности к использованию (0 - нет, 1 - да)
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Проверим параметры */
|
||||||
|
if (NREADY is null) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Не задано значение признака готовности панели к использованию.');
|
||||||
|
end if;
|
||||||
|
if (NREADY not in (0, 1)) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Значение признака готовности панели к использованию задано некорректно (ожидалось 0 или 1).');
|
||||||
|
end if;
|
||||||
|
/* Установим флаг готовности к использованию */
|
||||||
|
update P8PNL_PE_PANEL T set T.READY = NREADY where T.RN = NRN;
|
||||||
|
/* Контроль изменения данных */
|
||||||
|
if (sql%notfound) then
|
||||||
|
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||||
|
end if;
|
||||||
|
/* Обновим дату изменения панели */
|
||||||
|
PANEL_CH_DATE_SYNC(NRN => NRN);
|
||||||
|
end PANEL_READY_SET;
|
||||||
|
|
||||||
|
/* Установка признака "публичности" панели */
|
||||||
|
procedure PANEL_PBL_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
NPBL in number -- Флаг публичности (0 - приватный, 1 - публичный)
|
||||||
|
)
|
||||||
|
is
|
||||||
|
begin
|
||||||
|
/* Проверим параметры */
|
||||||
|
if (NPBL is null) then
|
||||||
|
P_EXCEPTION(0, 'Не задано значение признака публичности запроса.');
|
||||||
|
end if;
|
||||||
|
if (NPBL not in (0, 1)) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'Значение признака публичноти запроса задано некорректно (ожидалось 0 или 1).');
|
||||||
|
end if;
|
||||||
|
/* Установим флаг публичности */
|
||||||
|
update P8PNL_PE_PANEL T set T.PBL = NPBL where T.RN = NRN;
|
||||||
|
/* Контроль изменения данных */
|
||||||
|
if (sql%notfound) then
|
||||||
|
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||||
|
end if;
|
||||||
|
/* Обновим дату изменения панели */
|
||||||
|
PANEL_CH_DATE_SYNC(NRN => NRN);
|
||||||
|
end PANEL_PBL_SET;
|
||||||
|
|
||||||
|
/* Загрузка описания панели */
|
||||||
|
function PANEL_DESC_GET
|
||||||
|
(
|
||||||
|
NRN in number -- Рег. номер панели
|
||||||
|
) return clob -- XML-описание панели
|
||||||
|
is
|
||||||
|
RP P8PNL_PE_PANEL%rowtype; -- Запись панели
|
||||||
|
begin
|
||||||
|
/* Читаем панель */
|
||||||
|
RP := PANEL_GET(NRN => NRN);
|
||||||
|
/* Возвращаем результат */
|
||||||
|
return RP.PANEL;
|
||||||
|
end PANEL_DESC_GET;
|
||||||
|
|
||||||
|
/* Сохранение описания панели */
|
||||||
|
procedure PANEL_DESC_SET
|
||||||
|
(
|
||||||
|
NRN in number, -- Рег. номер панели
|
||||||
|
CPANEL in clob -- Описание панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
CPANEL_DATA clob; -- Данные описания панели
|
||||||
|
begin
|
||||||
|
/* Считываем данные описания панели */
|
||||||
|
CPANEL_DATA := BLOB2CLOB(LBDATA => BASE64_DECODE(LCSRCE => CPANEL), SCHARSET => PKG_CHARSET.CHARSET_UTF_());
|
||||||
|
/* Установим данные панели */
|
||||||
|
update P8PNL_PE_PANEL T set T.PANEL = CPANEL_DATA where T.RN = NRN;
|
||||||
|
/* Контроль изменения данных */
|
||||||
|
if (sql%notfound) then
|
||||||
|
PKG_MSG.RECORD_NOT_FOUND(NDOCUMENT => NRN, SUNIT_TABLE => 'P8PNL_PE_PANEL');
|
||||||
|
end if;
|
||||||
|
/* Обновим дату изменения панели */
|
||||||
|
PANEL_CH_DATE_SYNC(NRN => NRN);
|
||||||
|
exception
|
||||||
|
when others then
|
||||||
|
/* Завершаем формирование XML */
|
||||||
|
PKG_XFAST.EPILOGUE();
|
||||||
|
/* Вернем ошибку */
|
||||||
|
PKG_STATE.DIAGNOSTICS_STACKED();
|
||||||
|
P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
|
||||||
|
end PANEL_DESC_SET;
|
||||||
|
|
||||||
|
/* Импорт панели */
|
||||||
|
procedure PANEL_IMPORT
|
||||||
|
(
|
||||||
|
CPANEL in clob, -- Описание импортируемой панели
|
||||||
|
NRN out number -- Рег. номер импортированной панели
|
||||||
|
)
|
||||||
|
is
|
||||||
|
RPARSEDOC PKG_XPATH.TDOCUMENT; -- Документ XML
|
||||||
|
RROOT PKG_XPATH.TNODE; -- Буфер узла документа
|
||||||
|
RP P8PNL_PE_PANEL%rowtype; -- Запись панели
|
||||||
|
CPANEL_DATA clob; -- Данные описания панели
|
||||||
|
begin
|
||||||
|
/* Считываем данные описания панели */
|
||||||
|
CPANEL_DATA := BLOB2CLOB(LBDATA => BASE64_DECODE(LCSRCE => CPANEL), SCHARSET => PKG_CHARSET.CHARSET_UTF_());
|
||||||
|
/* Преобразовываем CLOB */
|
||||||
|
RPARSEDOC := PKG_XPATH.PARSE_FROM_CLOB(LCXML => CPANEL_DATA);
|
||||||
|
/* Считываем корневой узел */
|
||||||
|
RROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => RPARSEDOC);
|
||||||
|
/* Считываем мнемокод */
|
||||||
|
RP.CODE := PKG_XPATH.VALUE(RNODE => RROOT, SPATTERN => STAG_PANEL || '/' || STAG_PANEL_INFO || '/' || SATTR_CODE);
|
||||||
|
/* Считываем наименование */
|
||||||
|
RP.NAME := PKG_XPATH.VALUE(RNODE => RROOT, SPATTERN => STAG_PANEL || '/' || STAG_PANEL_INFO || '/' || SATTR_NAME);
|
||||||
|
/* Если в описании панели не содержится основной информации о панели */
|
||||||
|
if ((RP.CODE is null) or (RP.NAME is null)) then
|
||||||
|
P_EXCEPTION(0,
|
||||||
|
'В описании импортируемой панели отсутствует информация о мнемокоде или наименовании.');
|
||||||
|
end if;
|
||||||
|
/* Считываем готовность к использованию */
|
||||||
|
RP.READY := PKG_XPATH.VALUE_NUM(RNODE => RROOT,
|
||||||
|
SPATTERN => STAG_PANEL || '/' || STAG_PANEL_INFO || '/' || SATTR_READY);
|
||||||
|
/* Считываем публичность */
|
||||||
|
RP.PBL := PKG_XPATH.VALUE_NUM(RNODE => RROOT, SPATTERN => STAG_PANEL || '/' || STAG_PANEL_INFO || '/' || SATTR_PBL);
|
||||||
|
/* Удаляем лишнюю информацию из описания панели и устанавливаем его */
|
||||||
|
RP.PANEL := REGEXP_REPLACE(CPANEL_DATA, '<' || STAG_PANEL_INFO || '>.*?</' || STAG_PANEL_INFO || '>', '');
|
||||||
|
/* Добавляем панель */
|
||||||
|
PANEL_INSERT(SCODE => RP.CODE,
|
||||||
|
SNAME => RP.NAME,
|
||||||
|
NREADY => COALESCE(RP.READY, 0),
|
||||||
|
NPBL => COALESCE(RP.PBL, 0),
|
||||||
|
CPANEL => RP.PANEL,
|
||||||
|
NRN => NRN);
|
||||||
|
end PANEL_IMPORT;
|
||||||
|
|
||||||
|
/* Формирование списка панелей */
|
||||||
|
function PANEL_LIST_GET
|
||||||
|
(
|
||||||
|
SUSER in varchar2 -- Имя пользователя
|
||||||
|
) return clob -- Список панелей
|
||||||
|
is
|
||||||
|
CRES clob; -- Буфер для сериализации
|
||||||
|
begin
|
||||||
|
/* Начинаем формирование XML */
|
||||||
|
PKG_XFAST.PROLOGUE(ITYPE => PKG_XFAST.CONTENT_, BALINE => true, BINDENT => true);
|
||||||
|
/* Открываем корень */
|
||||||
|
PKG_XFAST.DOWN_NODE(SNAME => STAG_DATA);
|
||||||
|
/* Открываем список запросов */
|
||||||
|
PKG_XFAST.DOWN_NODE(SNAME => STAG_PANELS);
|
||||||
|
/* Обходим запросы - данного пользователя и публичные */
|
||||||
|
for C in (select T.RN NRN,
|
||||||
|
T.CODE SCODE,
|
||||||
|
T.NAME SNAME,
|
||||||
|
UL.NAME SAUTHOR,
|
||||||
|
TO_CHAR(T.CH_DATE, 'dd.mm.yyyy hh24:mi:ss') SCH_DATE,
|
||||||
|
T.READY NREADY,
|
||||||
|
T.PBL NPBL,
|
||||||
|
PANEL_ACCESS_SIGN_MODIFY(T.RN, SUSER) NMODIFY
|
||||||
|
from P8PNL_PE_PANEL T,
|
||||||
|
USERLIST UL
|
||||||
|
where T.AUTHOR = UL.AUTHID
|
||||||
|
and PANEL_ACCESS_SIGN_VIEW(T.RN, SUSER) = 1
|
||||||
|
order by T.CODE)
|
||||||
|
loop
|
||||||
|
/* Открываем описание панели */
|
||||||
|
PKG_XFAST.DOWN_NODE(SNAME => STAG_PANEL);
|
||||||
|
/* Панель */
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_RN, NVALUE => C.NRN);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_CODE, SVALUE => C.SCODE);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => C.SNAME);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_AUTHOR, SVALUE => C.SAUTHOR);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_CH_DATE, SVALUE => C.SCH_DATE);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_READY, NVALUE => C.NREADY);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_PBL, NVALUE => C.NPBL);
|
||||||
|
PKG_XFAST.ATTR(SNAME => SATTR_MODIFY, NVALUE => C.NMODIFY);
|
||||||
|
/* Закрываем описание панели */
|
||||||
|
PKG_XFAST.UP();
|
||||||
|
end loop;
|
||||||
|
/* Закрываем список панелей */
|
||||||
|
PKG_XFAST.UP();
|
||||||
|
/* Закрываем корень */
|
||||||
|
PKG_XFAST.UP();
|
||||||
|
/* Сериализуем */
|
||||||
|
CRES := PKG_XFAST.SERIALIZE_TO_CLOB();
|
||||||
|
/* Завершаем формирование XML */
|
||||||
|
PKG_XFAST.EPILOGUE();
|
||||||
|
/* Возвращаем результат */
|
||||||
|
return CRES;
|
||||||
|
exception
|
||||||
|
when others then
|
||||||
|
/* Завершаем формирование XML */
|
||||||
|
PKG_XFAST.EPILOGUE();
|
||||||
|
/* Вернем ошибку */
|
||||||
|
PKG_STATE.DIAGNOSTICS_STACKED();
|
||||||
|
P_EXCEPTION(0, PKG_STATE.SQL_ERRM());
|
||||||
|
end PANEL_LIST_GET;
|
||||||
|
|
||||||
|
end PKG_P8PANELS_PE_BASE;
|
||||||
|
/
|
||||||
10
dist/p8-panels.js
vendored
10
dist/p8-panels.js
vendored
File diff suppressed because one or more lines are too long
@ -10,14 +10,14 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://git.citpb.ru/CITKParus/P8-Panels.git"
|
"url": "git+https://github.com/CITKParus/P8-Panels.git"
|
||||||
},
|
},
|
||||||
"author": "CITK Parus",
|
"author": "CITK Parus",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://git.citpb.ru/CITKParus/P8-Panels/issues"
|
"url": "https://github.com/CITKParus/P8-Panels/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://git.citpb.ru/CITKParus/P8-Panels#readme",
|
"homepage": "https://github.com/CITKParus/P8-Panels#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.22.10",
|
"@babel/core": "^7.22.10",
|
||||||
"@babel/preset-react": "^7.22.5",
|
"@babel/preset-react": "^7.22.5",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user