240 lines
10 KiB
JavaScript
240 lines
10 KiB
JavaScript
/*
|
|
Парус 8 - Панели мониторинга - Редактор панелей
|
|
Корневой компонент
|
|
*/
|
|
|
|
//TODO: Подчистка values после обновления имени элемента формы (и т.п.), удаления элемента формы
|
|
|
|
//---------------------
|
|
//Подключение библиотек
|
|
//---------------------
|
|
|
|
import React, { useEffect, useState } from "react"; //Классы React
|
|
import { Responsive, WidthProvider } from "react-grid-layout"; //Адаптивный макет
|
|
import { Box, Grid, Stack, Menu, MenuItem, IconButton, Icon, Fab } from "@mui/material"; //Интерфейсные элементы
|
|
import { APP_BAR_HEIGHT } from "../../components/p8p_app_workspace"; //Рабочая область приложения
|
|
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"; //Стили для адаптивного макета
|
|
|
|
//---------
|
|
//Константы
|
|
//---------
|
|
|
|
//Стили
|
|
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 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 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 = () => 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 = (
|
|
<Stack direction={"row"} p={1}>
|
|
<IconButton onClick={toggleEditMode} title={"Запустить"}>
|
|
<Icon>play_arrow</Icon>
|
|
</IconButton>
|
|
<IconButton onClick={handleAddClick} title={"Добавить элемент"}>
|
|
<Icon>add</Icon>
|
|
</IconButton>
|
|
</Stack>
|
|
);
|
|
|
|
//Генерация содержимого
|
|
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 };
|