P8-Panels/app/panels/panels_editor/panel_editor.js

359 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
Парус 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 };