/* Парус 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 = ( {COMPONENTS.map((comp, i) => ( handleAddMenuItemClick(comp)}> {comp.name} ))} ); //Панель инструментов const toolBar = ( { 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 ( {addMenu} handleOnDrag(oldItem)} onDragStop={handleOnDragStop} > {panelDesc.layouts[panelDesc.breakpoint].map(item => ( ))} {editMode && ( {toolBar} {panel ? ( editComponent ? ( ) : ( ) ) : null} )} ); }; //Контроль свойств компонента - Редактор панели 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 };