forked from CITKParus/P8-Panels
359 lines
17 KiB
JavaScript
359 lines
17 KiB
JavaScript
/*
|
||
Парус 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 };
|