254 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			254 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /*
 | ||
|     Парус 8 - Панели мониторинга - Редактор панелей
 | ||
|     Корневой компонент
 | ||
| */
 | ||
| 
 | ||
| //---------------------
 | ||
| //Подключение библиотек
 | ||
| //---------------------
 | ||
| 
 | ||
| import React, { useEffect, useState, useContext } from "react"; //Классы React
 | ||
| import { Responsive, WidthProvider } from "react-grid-layout"; //Адаптивный макет
 | ||
| import { Box, Grid, Menu, MenuItem, Icon, Fab } from "@mui/material"; //Интерфейсные элементы
 | ||
| import { ApplicationСtx } from "../../context/application"; //Контекст приложения
 | ||
| import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Рабочая область приложения
 | ||
| import { P8PEditorToolBar } from "../../components/editors/p8p_editor_toolbar"; //Панель инструментов редактора
 | ||
| import { genGUID } from "../../core/utils"; //Общие вспомогательные функции
 | ||
| import { LayoutItem } from "./layout_item"; //Элемент макета
 | ||
| import { ComponentView } from "./component_view"; //Представление компонента панели
 | ||
| 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 STYLES = {
 | ||
|     CONTAINER: { display: "flex" },
 | ||
|     GRID_CONTAINER: { height: `calc(100vh - ${APP_BAR_HEIGHT})` },
 | ||
|     GRID_ITEM_INSPECTOR: { backgroundColor: "#e9ecef" },
 | ||
|     FAB_EDIT: { position: "absolute", top: 12, right: 12, zIndex: 2000 }
 | ||
| };
 | ||
| 
 | ||
| //Заголовоки по умолчанию
 | ||
| const PANEL_CAPTION_EDIT_MODE = "Редактор панелей";
 | ||
| const PANEL_CAPTION_EXECUTE_MODE = "Исполнение панели";
 | ||
| 
 | ||
| //Начальное состояние размера макета
 | ||
| const INITIAL_BREAKPOINT = "lg";
 | ||
| 
 | ||
| //Начальное состояние макета
 | ||
| const INITIAL_LAYOUTS = {
 | ||
|     [INITIAL_BREAKPOINT]: []
 | ||
| };
 | ||
| 
 | ||
| //-----------
 | ||
| //Тело модуля
 | ||
| //-----------
 | ||
| 
 | ||
| //Обёрдка для динамического макета
 | ||
| const ResponsiveGridLayout = WidthProvider(Responsive);
 | ||
| 
 | ||
| //Корневой компонент редактора панелей
 | ||
| const PanelsEditor = () => {
 | ||
|     //Собственное состояние
 | ||
|     const [components, setComponents] = useState({});
 | ||
|     const [valueProviders, setValueProviders] = useState({});
 | ||
|     const [layouts, setLayouts] = useState(INITIAL_LAYOUTS);
 | ||
|     const [breakpoint, setBreakpoint] = useState(INITIAL_BREAKPOINT);
 | ||
|     const [editMode, setEditMode] = useState(true);
 | ||
|     const [editComponent, setEditComponent] = useState(null);
 | ||
|     const [addMenuAnchorEl, setAddMenuAnchorEl] = useState(null);
 | ||
| 
 | ||
|     //Подключение к контексту приложения
 | ||
|     const { setAppBarTitle } = useContext(ApplicationСtx);
 | ||
| 
 | ||
|     //Добвление компонента в макет
 | ||
|     const addComponent = component => {
 | ||
|         const id = genGUID();
 | ||
|         setLayouts(pv => ({ ...pv, [breakpoint]: [...pv[breakpoint], { i: id, x: 0, y: 0, w: 4, h: 10 }] }));
 | ||
|         setComponents(pv => ({ ...pv, [id]: { ...component } }));
 | ||
|     };
 | ||
| 
 | ||
|     //Удаление компонента из макета
 | ||
|     const deleteComponent = id => {
 | ||
|         setLayouts(pv => ({ ...pv, [breakpoint]: layouts[breakpoint].filter(item => item.i !== id) }));
 | ||
|         setComponents(pv => ({ ...pv, [id]: { ...pv[id], deleted: true } }));
 | ||
|         if (valueProviders[id]) {
 | ||
|             const vPTmp = { ...valueProviders };
 | ||
|             delete vPTmp[id];
 | ||
|             setValueProviders(vPTmp);
 | ||
|         }
 | ||
|         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);
 | ||
| 
 | ||
|     //При подключении к странице
 | ||
|     useEffect(() => {
 | ||
|         //addComponent(COMPONETNS[0]);
 | ||
|         //addComponent(COMPONETNS[3]);
 | ||
|         //addComponent(COMPONETNS[4]);
 | ||
|         //addComponent(COMPONETNS[1]);
 | ||
|         //addComponent(COMPONETNS[2]);
 | ||
|         // eslint-disable-next-line react-hooks/exhaustive-deps
 | ||
|     }, []);
 | ||
| 
 | ||
|     //Текущие значения панели
 | ||
|     const values = Object.keys(valueProviders).reduce((res, key) => ({ ...res, ...valueProviders[key] }), {});
 | ||
| 
 | ||
|     //Меню добавления
 | ||
|     const addMenu = (
 | ||
|         <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 (
 | ||
|         <Box sx={STYLES.CONTAINER}>
 | ||
|             {editButton}
 | ||
|             {addMenu}
 | ||
|             <Grid container sx={STYLES.GRID_CONTAINER} columns={25}>
 | ||
|                 <Grid item xs={editMode ? 20 : 25}>
 | ||
|                     <ResponsiveGridLayout
 | ||
|                         rowHeight={5}
 | ||
|                         className={"layout"}
 | ||
|                         layouts={layouts}
 | ||
|                         breakpoints={{ lg: 1200 }}
 | ||
|                         cols={{ lg: 12 }}
 | ||
|                         onBreakpointChange={handleBreakpointChange}
 | ||
|                         onLayoutChange={handleLayoutChange}
 | ||
|                         useCSSTransforms={true}
 | ||
|                         compactType={"vertical"}
 | ||
|                         isDraggable={editMode}
 | ||
|                         isResizable={editMode}
 | ||
|                     >
 | ||
|                         {layouts[breakpoint].map(item => (
 | ||
|                             <LayoutItem
 | ||
|                                 key={item.i}
 | ||
|                                 onSettingsClick={handleComponentSettingsClick}
 | ||
|                                 onDeleteClick={handleComponentDeleteClick}
 | ||
|                                 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>
 | ||
|     );
 | ||
| };
 | ||
| 
 | ||
| //----------------
 | ||
| //Интерфейс модуля
 | ||
| //----------------
 | ||
| 
 | ||
| export { PanelsEditor };
 |