ЦИТК-979 - Рефакторинг понятий Сущность, Атрибут, Отношение, Элемент диаграммы, Связь диаграммы

This commit is contained in:
Mikhail Chechnev 2025-09-03 15:16:12 +03:00
parent 86aa639ca0
commit 7515a9cce3
11 changed files with 159 additions and 130 deletions

View File

@ -51,9 +51,9 @@ const STYLES = {
const ICONS = { ...DATA_TYPE_ICON, DEFAULT: "category" }; const ICONS = { ...DATA_TYPE_ICON, DEFAULT: "category" };
//Структура данных об атрибуте сущности //Структура данных об атрибуте сущности
const ATTRIBUTE_DATA_SHAPE = PropTypes.shape({ const ATTRIBUTE_SHAPE = PropTypes.shape({
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
parentEntity: PropTypes.string, parentEntity: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
dataType: PropTypes.oneOf(Object.values(DATA_TYPE)), dataType: PropTypes.oneOf(Object.values(DATA_TYPE)),
@ -136,11 +136,11 @@ const Attribute = ({ data }) => {
//Контроль свойств компонента - Атрибут сущности //Контроль свойств компонента - Атрибут сущности
Attribute.propTypes = { Attribute.propTypes = {
data: ATTRIBUTE_DATA_SHAPE data: ATTRIBUTE_SHAPE
}; };
//---------------- //----------------
//Интерфейс модуля //Интерфейс модуля
//---------------- //----------------
export { Attribute, ATTRIBUTE_DATA_SHAPE, attrGetUse, attrGetShow }; export { Attribute, ATTRIBUTE_SHAPE, attrGetUse, attrGetShow };

View File

@ -10,16 +10,20 @@
import React from "react"; //Классы React import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import "./entity.css"; //Стили компомнента import "./entity.css"; //Стили компомнента
import { ATTRIBUTE_SHAPE } from "../attribute/attribute"; //Описание атрибута сущности
//--------- //---------
//Константы //Константы
//--------- //---------
//Структура данных о сущности запроса //Структура данных о сущности запроса
const ENTITY_DATA_SHAPE = PropTypes.shape({ const ENTITY_SHAPE = PropTypes.shape({
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
title: PropTypes.string.isRequired title: PropTypes.string.isRequired,
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
attrs: PropTypes.arrayOf(ATTRIBUTE_SHAPE).isRequired
}); });
//----------- //-----------
@ -40,12 +44,12 @@ const Entity = ({ data, selected = false }) => {
//Контроль свойств компонента - Сущность запроса //Контроль свойств компонента - Сущность запроса
Entity.propTypes = { Entity.propTypes = {
data: ENTITY_DATA_SHAPE, data: ENTITY_SHAPE.isRequired,
selected: PropTypes.bool.isRequired selected: PropTypes.bool
}; };
//---------------- //----------------
//Интерфейс модуля //Интерфейс модуля
//---------------- //----------------
export { Entity, ENTITY_DATA_SHAPE }; export { Entity, ENTITY_SHAPE };

View File

@ -11,8 +11,8 @@ import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер параметров редактора import { P8PEditorBox } from "../../../../components/editors/p8p_editor_box"; //Контейнер параметров редактора
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Подзаголовок группы параметров редактора import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Подзаголовок группы параметров редактора
import { ENTITY_DATA_SHAPE } from "../entity/entity"; //Описание сущности import { ENTITY_SHAPE } from "../entity/entity"; //Описание сущности
import { RELATION_DATA_SHAPE } from "../relation/relation"; //Описание связи import { RELATION_SHAPE } from "../relation/relation"; //Описание связи
import { ARGUMENT_SHAPE } from "../argument/argument"; //Описание аргумента запроса import { ARGUMENT_SHAPE } from "../argument/argument"; //Описание аргумента запроса
import { InspectorQueryArguments } from "../inspector_query_args/inspector_query_args"; //Управление аргументами запроса import { InspectorQueryArguments } from "../inspector_query_args/inspector_query_args"; //Управление аргументами запроса
import { InspectorQueryConditions } from "../inspector_query_cond/inspector_query_cond"; //Управление условиями отбора запроса import { InspectorQueryConditions } from "../inspector_query_cond/inspector_query_cond"; //Управление условиями отбора запроса
@ -24,7 +24,7 @@ import { InspectorQueryRelations } from "../inspector_query_rls/inspector_query_
//----------- //-----------
//Инспектор свойств //Инспектор свойств
const Inspector = ({ query, entity, relation, args = [], cond = null, onOptionsChanged = null }) => { const Inspector = ({ query, entity, relation, entities = [], args = [], cond = null, onOptionsChanged = null }) => {
//При изменении настроек запроса //При изменении настроек запроса
const handleOptionsChanged = () => onOptionsChanged && onOptionsChanged(); const handleOptionsChanged = () => onOptionsChanged && onOptionsChanged();
@ -34,7 +34,7 @@ const Inspector = ({ query, entity, relation, args = [], cond = null, onOptionsC
<P8PEditorSubHeader title={"Аргументы"} /> <P8PEditorSubHeader title={"Аргументы"} />
<InspectorQueryArguments query={query} args={args} onOptionsChanged={handleOptionsChanged} /> <InspectorQueryArguments query={query} args={args} onOptionsChanged={handleOptionsChanged} />
<P8PEditorSubHeader title={"Условия отбора"} /> <P8PEditorSubHeader title={"Условия отбора"} />
<InspectorQueryConditions query={query} cond={cond} onOptionsChanged={handleOptionsChanged} /> <InspectorQueryConditions query={query} cond={cond} entities={entities} args={args} onOptionsChanged={handleOptionsChanged} />
<P8PEditorSubHeader title={"Сущности"} /> <P8PEditorSubHeader title={"Сущности"} />
<InspectorQueryEntities query={query} entity={entity} onOptionsChanged={handleOptionsChanged} /> <InspectorQueryEntities query={query} entity={entity} onOptionsChanged={handleOptionsChanged} />
{relation && ( {relation && (
@ -50,8 +50,9 @@ const Inspector = ({ query, entity, relation, args = [], cond = null, onOptionsC
//Контроль свойств компонента - Инспектор свойств //Контроль свойств компонента - Инспектор свойств
Inspector.propTypes = { Inspector.propTypes = {
query: PropTypes.number.isRequired, query: PropTypes.number.isRequired,
entity: ENTITY_DATA_SHAPE, entity: ENTITY_SHAPE,
relation: RELATION_DATA_SHAPE, relation: RELATION_SHAPE,
entities: PropTypes.arrayOf(ENTITY_SHAPE),
args: PropTypes.arrayOf(ARGUMENT_SHAPE), args: PropTypes.arrayOf(ARGUMENT_SHAPE),
cond: PropTypes.string, cond: PropTypes.string,
onOptionsChanged: PropTypes.func onOptionsChanged: PropTypes.func

View File

@ -10,7 +10,7 @@
import React from "react"; //Классы React import React from "react"; //Классы React
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import { Stack, List, ListItem, IconButton, Icon, ListItemButton, ListItemText, ListItemIcon, Checkbox, Typography } from "@mui/material"; //Интерфейсные компоненты MUI import { Stack, List, ListItem, IconButton, Icon, ListItemButton, ListItemText, ListItemIcon, Checkbox, Typography } from "@mui/material"; //Интерфейсные компоненты MUI
import { ATTRIBUTE_DATA_SHAPE, attrGetUse, attrGetShow } from "../attribute/attribute"; //Атрибут сущности import { ATTRIBUTE_SHAPE, attrGetUse, attrGetShow } from "../attribute/attribute"; //Атрибут сущности
import { APP_STYLES } from "../../../../../app.styles"; //Общие стили приложения import { APP_STYLES } from "../../../../../app.styles"; //Общие стили приложения
//--------- //---------
@ -84,7 +84,7 @@ const AttrsList = ({ attrs = [], filter, onSelect = null, onShow = null } = {})
//Контроль свойств компонента - Список атрибутов сущности //Контроль свойств компонента - Список атрибутов сущности
AttrsList.propTypes = { AttrsList.propTypes = {
attrs: PropTypes.arrayOf(ATTRIBUTE_DATA_SHAPE), attrs: PropTypes.arrayOf(ATTRIBUTE_SHAPE),
filter: PropTypes.string, filter: PropTypes.string,
onSelect: PropTypes.func, onSelect: PropTypes.func,
onShow: PropTypes.func onShow: PropTypes.func

View File

@ -13,7 +13,7 @@ import { Icon, Button } from "@mui/material"; //Интерфейсные эле
import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений приложения import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений приложения
import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Подзаголовок группы параметров редактора import { P8PEditorSubHeader } from "../../../../components/editors/p8p_editor_sub_header"; //Подзаголовок группы параметров редактора
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы
import { ENTITY_DATA_SHAPE } from "../entity/entity"; //Описание сущности import { ENTITY_SHAPE } from "../entity/entity"; //Описание сущности
import { EntityAddDialog } from "./entity_add_dialog"; //Диалог добавления сущности import { EntityAddDialog } from "./entity_add_dialog"; //Диалог добавления сущности
import { EntityAttrsDialog } from "./entity_attrs_dialog"; //Диалог настройки атрибутов сущности import { EntityAttrsDialog } from "./entity_attrs_dialog"; //Диалог настройки атрибутов сущности
import { useQueryEntities } from "./hooks"; //Хуки для работы с сущностями на сервере import { useQueryEntities } from "./hooks"; //Хуки для работы с сущностями на сервере
@ -101,7 +101,7 @@ const InspectorQueryEntities = ({ query, entity, onOptionsChanged }) => {
//Контроль свойств компонента - Компонент инспектора - Сущности запроса //Контроль свойств компонента - Компонент инспектора - Сущности запроса
InspectorQueryEntities.propTypes = { InspectorQueryEntities.propTypes = {
query: PropTypes.number.isRequired, query: PropTypes.number.isRequired,
entity: ENTITY_DATA_SHAPE, entity: ENTITY_SHAPE,
onOptionsChanged: PropTypes.func onOptionsChanged: PropTypes.func
}; };

View File

@ -12,7 +12,7 @@ import PropTypes from "prop-types"; //Контроль свойств компо
import { Icon, Button } from "@mui/material"; //Интерфейсные элементы import { Icon, Button } from "@mui/material"; //Интерфейсные элементы
import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений приложения import { MessagingСtx } from "../../../../context/messaging"; //Контекст сообщений приложения
import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы import { BUTTONS } from "../../../../../app.text"; //Общие текстовые ресурсы
import { RELATION_DATA_SHAPE } from "../relation/relation"; //Описание связи import { RELATION_SHAPE } from "../relation/relation"; //Описание связи
import { useQueryRelations } from "./hooks"; //Хуки для работы со связями import { useQueryRelations } from "./hooks"; //Хуки для работы со связями
//----------- //-----------
@ -50,7 +50,7 @@ const InspectorQueryRelations = ({ query, relation, onOptionsChanged }) => {
//Контроль свойств компонента - Компонент инспектора - Связи запроса //Контроль свойств компонента - Компонент инспектора - Связи запроса
InspectorQueryRelations.propTypes = { InspectorQueryRelations.propTypes = {
query: PropTypes.number.isRequired, query: PropTypes.number.isRequired,
relation: RELATION_DATA_SHAPE.isRequired, relation: RELATION_SHAPE.isRequired,
onOptionsChanged: PropTypes.func onOptionsChanged: PropTypes.func
}; };

View File

@ -11,8 +11,8 @@ import React, { useState, useCallback, useEffect } from "react"; //Классы
import PropTypes from "prop-types"; //Контроль свойств компонента import PropTypes from "prop-types"; //Контроль свойств компонента
import ReactFlow, { addEdge, Controls, getOutgoers, applyNodeChanges, applyEdgeChanges } from "reactflow"; //Библиотека редактора диаграмм import ReactFlow, { addEdge, Controls, getOutgoers, applyNodeChanges, applyEdgeChanges } from "reactflow"; //Библиотека редактора диаграмм
import { NODE_TYPE } from "../../common"; //Общие ресурсы и константы редактора import { NODE_TYPE } from "../../common"; //Общие ресурсы и константы редактора
import { Entity, ENTITY_DATA_SHAPE } from "../entity/entity"; //Сущность запроса import { Entity, ENTITY_SHAPE } from "../entity/entity"; //Сущность запроса
import { Attribute, ATTRIBUTE_DATA_SHAPE } from "../attribute/attribute"; //Атрибут сущности import { Attribute, ATTRIBUTE_SHAPE } from "../attribute/attribute"; //Атрибут сущности
import "reactflow/dist/style.css"; //Типовые стили библиотеки редактора диаграмм import "reactflow/dist/style.css"; //Типовые стили библиотеки редактора диаграмм
import "./query_diagram.css"; //Стили компонента import "./query_diagram.css"; //Стили компонента
@ -37,18 +37,18 @@ const NODE_TYPES_COMPONENTS = {
[NODE_TYPE.ATTRIBUTE]: Attribute [NODE_TYPE.ATTRIBUTE]: Attribute
}; };
//Структура сущности запроса //Структура элемента диаграммы
const ENTITY_SHAPE = PropTypes.shape({ const NODE_SHAPE = PropTypes.shape({
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
type: PropTypes.oneOf([NODE_TYPE.ENTITY, NODE_TYPE.ATTRIBUTE]).isRequired, type: PropTypes.oneOf([NODE_TYPE.ENTITY, NODE_TYPE.ATTRIBUTE]).isRequired,
style: PropTypes.object, style: PropTypes.object,
position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }), position: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }),
draggable: PropTypes.bool.isRequired, draggable: PropTypes.bool.isRequired,
data: PropTypes.oneOfType([ENTITY_DATA_SHAPE, ATTRIBUTE_DATA_SHAPE]) data: PropTypes.oneOfType([ENTITY_SHAPE, ATTRIBUTE_SHAPE])
}); });
//Структура связи запроса //Структура связи диаграммы
const RELATION_SHAPE = PropTypes.shape({ id: PropTypes.string.isRequired, source: PropTypes.string.isRequired, target: PropTypes.string.isRequired }); const EDGE_SHAPE = PropTypes.shape({ id: PropTypes.string.isRequired, source: PropTypes.string.isRequired, target: PropTypes.string.isRequired });
//------------------------------------ //------------------------------------
//Вспомогательные функции и компоненты //Вспомогательные функции и компоненты
@ -88,8 +88,8 @@ const isValidConnection = (connection, nodes, edges) => {
//Диаграмма запроса //Диаграмма запроса
const QueryDiagram = ({ const QueryDiagram = ({
entities = [], nodes = [],
relations = [], edges = [],
onEntityClick, onEntityClick,
onEntityAttrClick, onEntityAttrClick,
onEntityPositionChange, onEntityPositionChange,
@ -100,10 +100,10 @@ const QueryDiagram = ({
onPaneClick onPaneClick
}) => { }) => {
//Собственное состояние - элементы //Собственное состояние - элементы
const [nodes, setNodes] = useState(entities); const [nodesCurrent, setNodesCurrent] = useState(nodes);
//Собственное состояние - связи //Собственное состояние - связи
const [edges, setEdges] = useState(relations); const [edgesCurrent, setEdgesCurrent] = useState(edges);
//Собственное состояние - перемещённый элемент //Собственное состояние - перемещённый элемент
const [movedNode, setMovedNode] = useState(null); const [movedNode, setMovedNode] = useState(null);
@ -115,7 +115,7 @@ const QueryDiagram = ({
const tmpChanges = changes.reduce((prevChanges, curChanges) => { const tmpChanges = changes.reduce((prevChanges, curChanges) => {
const tmp = { ...curChanges }; const tmp = { ...curChanges };
if (tmp.type == "select") { if (tmp.type == "select") {
const chEnt = entities.find(e => e.id === tmp.id); const chEnt = nodes.find(n => n.id === tmp.id);
if (chEnt && chEnt?.data?.parentEntity) { if (chEnt && chEnt?.data?.parentEntity) {
prevChanges.push({ ...curChanges, id: chEnt.data.parentEntity }); prevChanges.push({ ...curChanges, id: chEnt.data.parentEntity });
tmp.selected = false; tmp.selected = false;
@ -125,7 +125,7 @@ const QueryDiagram = ({
return prevChanges; return prevChanges;
}, []); }, []);
//Применим изменения в диаграмме //Применим изменения в диаграмме
setNodes(nodesSnapshot => applyNodeChanges(tmpChanges, nodesSnapshot)); setNodesCurrent(nodesSnapshot => applyNodeChanges(tmpChanges, nodesSnapshot));
//Если двигали сущность - запомним начало движения //Если двигали сущность - запомним начало движения
if (changes.length == 1 && changes[0].type == "position" && changes[0].dragging) if (changes.length == 1 && changes[0].type == "position" && changes[0].dragging)
setMovedNode({ id: changes[0].id, position: { ...changes[0].position } }); setMovedNode({ id: changes[0].id, position: { ...changes[0].position } });
@ -136,10 +136,10 @@ const QueryDiagram = ({
setMovedNode(null); setMovedNode(null);
} }
//Если удалили сущность - вызываем колбэк для удаления //Если удалили сущность - вызываем колбэк для удаления
if (changes[0].type == "remove" && entities.find(e => e.id == changes[0].id && e.type == NODE_TYPE.ENTITY) && onEntityRemove) if (changes[0].type == "remove" && nodes.find(n => n.id == changes[0].id && n.type == NODE_TYPE.ENTITY) && onEntityRemove)
onEntityRemove(changes[0].id); onEntityRemove(changes[0].id);
}, },
[movedNode, entities, onEntityClick, onEntityPositionChange, onEntityRemove] [movedNode, nodes, onEntityClick, onEntityPositionChange, onEntityRemove]
); );
//При выборе элемента диаграммы //При выборе элемента диаграммы
@ -153,7 +153,7 @@ const QueryDiagram = ({
//При связывании элементов на диаграмме //При связывании элементов на диаграмме
const handleConnect = connection => { const handleConnect = connection => {
setEdges(state => addEdge({ ...connection, id: `${connection.source}-${connection.target}` }, state)); setEdgesCurrent(state => addEdge({ ...connection, id: `${connection.source}-${connection.target}` }, state));
onRelationAdd && onRelationAdd(connection.source, connection.target); onRelationAdd && onRelationAdd(connection.source, connection.target);
}; };
@ -163,7 +163,7 @@ const QueryDiagram = ({
//При изменении связей на диаграмме //При изменении связей на диаграмме
const handleEdgesChange = useCallback( const handleEdgesChange = useCallback(
changes => { changes => {
setEdges(edgesSnapshot => applyEdgeChanges(changes, edgesSnapshot)); setEdgesCurrent(edgesSnapshot => applyEdgeChanges(changes, edgesSnapshot));
if (changes.length == 1 && changes[0].type == "remove" && onRelationRemove) onRelationRemove(changes[0].id); if (changes.length == 1 && changes[0].type == "remove" && onRelationRemove) onRelationRemove(changes[0].id);
}, },
[onRelationRemove] [onRelationRemove]
@ -173,22 +173,22 @@ const QueryDiagram = ({
const handlePaneClick = () => onPaneClick && onPaneClick(); const handlePaneClick = () => onPaneClick && onPaneClick();
//Валидация связи //Валидация связи
const validateConnection = connection => isValidConnection(connection, nodes, edges); const validateConnection = connection => isValidConnection(connection, nodesCurrent, edgesCurrent);
//Подсветка выбранной сущности //Подсветка выбранной сущности
//При изменении состава сущностей //При изменении состава сущностей
useEffect(() => setNodes(entities), [entities]); useEffect(() => setNodesCurrent(nodes), [nodes]);
//При изменении состава связей //При изменении состава связей
useEffect(() => setEdges(relations), [relations]); useEffect(() => setEdgesCurrent(edges), [edges]);
//Генерация содержимого //Генерация содержимого
return ( return (
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodesCurrent}
nodeTypes={NODE_TYPES_COMPONENTS} nodeTypes={NODE_TYPES_COMPONENTS}
edges={edges} edges={edgesCurrent}
onNodeClick={handleNodeClick} onNodeClick={handleNodeClick}
onNodesChange={handleNodesChange} onNodesChange={handleNodesChange}
onEdgeClick={handleEdgeClick} onEdgeClick={handleEdgeClick}
@ -210,8 +210,8 @@ const QueryDiagram = ({
//Контроль свойств компонента - Диаграмма запроса //Контроль свойств компонента - Диаграмма запроса
QueryDiagram.propTypes = { QueryDiagram.propTypes = {
entities: PropTypes.arrayOf(ENTITY_SHAPE), nodes: PropTypes.arrayOf(NODE_SHAPE),
relations: PropTypes.arrayOf(RELATION_SHAPE), edges: PropTypes.arrayOf(EDGE_SHAPE),
onEntityClick: PropTypes.func, onEntityClick: PropTypes.func,
onEntityAttrClick: PropTypes.func, onEntityAttrClick: PropTypes.func,
onEntityPositionChange: PropTypes.func, onEntityPositionChange: PropTypes.func,

View File

@ -14,7 +14,7 @@ import PropTypes from "prop-types"; //Контроль свойств компо
//--------- //---------
//Структура данных о связи сущностей запроса //Структура данных о связи сущностей запроса
const RELATION_DATA_SHAPE = PropTypes.shape({ const RELATION_SHAPE = PropTypes.shape({
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
source: PropTypes.string.isRequired, source: PropTypes.string.isRequired,
target: PropTypes.string.isRequired target: PropTypes.string.isRequired
@ -24,4 +24,4 @@ const RELATION_DATA_SHAPE = PropTypes.shape({
//Интерфейс модуля //Интерфейс модуля
//---------------- //----------------
export { RELATION_DATA_SHAPE }; export { RELATION_SHAPE };

View File

@ -11,76 +11,86 @@ import { useState, useContext, useEffect } from "react"; //Классы React
import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером import { BackEndСtx } from "../../context/backend"; //Контекст взаимодействия с сервером
import { NODE_TYPE } from "./common"; //Общие ресурсы и константы редактора import { NODE_TYPE } from "./common"; //Общие ресурсы и константы редактора
//---------
//Константы
//---------
//Ширина элемента диаграммы
const NODE_WIDTH = 250;
//Высота единицы состава группового элемента диаграммы
const GROUP_NODE_ITEM_HEIGHT = 50;
//Стили
const STYLES = {
ATTRIBUTE: isLast => ({ borderBottom: isLast ? "none" : "1px solid #dee2e6" })
};
//------------------------------------ //------------------------------------
//Вспомогательные функции и компоненты //Вспомогательные функции и компоненты
//------------------------------------ //------------------------------------
//Конвертация серверного описания сущностей запроса в элементы диаграммы //Конвертация серверного описания сущностей запроса в элементы диаграммы
const serverEntity2QueryDiagramNodes = entity => { const serverEntity2QueryDiagramNodes = entity => {
const groupWidth = 250; //Ссылка на атрибуты
const nameColumnHeight = 50; const attrs = entity.attrs || [];
const columns = entity?.XATTRS?.XATTR || []; //Количество атрибутов
const columnsCount = columns.length; const attrsCount = attrs.length;
const groupHeight = nameColumnHeight + columnsCount * 50; //Высота группового элемента диаграммы
const entityNodeHeight = GROUP_NODE_ITEM_HEIGHT + attrsCount * GROUP_NODE_ITEM_HEIGHT;
const groupNode = { //Элемент диаграммы для сущности (групповой элемент)
const entityNode = {
id: entity.id, id: entity.id,
type: NODE_TYPE.ENTITY, type: NODE_TYPE.ENTITY,
data: { ...entity }, data: { ...entity },
position: { x: entity.x, y: entity.y }, position: { x: entity.x, y: entity.y },
style: { style: {
width: groupWidth, width: NODE_WIDTH,
height: groupHeight height: entityNodeHeight
}, },
draggable: true draggable: true
}; };
//Элементы диаграммы для атрибутов сущности (состав группового элемента)
const columnNodes = columns.map((column, index, columns) => { const attrsNodes = attrs.map((attr, index, attrs) => ({
const x = 1; id: attr.id,
const y = 50 * (index + 1); type: NODE_TYPE.ATTRIBUTE,
const width = groupWidth - 2; data: { ...attr },
const height = 50; position: { x: 1, y: GROUP_NODE_ITEM_HEIGHT * (index + 1) },
parentId: entity.id,
const isLast = index === columns.length - 1; extent: "parent",
const defaultColumnStyles = { style: {
borderBottom: "1px solid #dee2e6" width: NODE_WIDTH - 2,
}; height: GROUP_NODE_ITEM_HEIGHT,
const lastColumnStyles = { ...STYLES.ATTRIBUTE(index === attrs.length - 1)
borderBottom: "none" },
}; draggable: false,
const otherStyles = isLast ? lastColumnStyles : defaultColumnStyles; selectable: true
}));
return { //Возвращаем элемент для сущности (групповой) и элементы для атрибутов (состав группового)
id: column.id, return [entityNode, ...attrsNodes];
type: NODE_TYPE.ATTRIBUTE,
data: {
...column,
included: false,
parentEntity: entity.id
},
position: { x, y },
parentId: entity.id,
extent: "parent",
style: {
width,
height,
...otherStyles
},
draggable: false,
selectable: true
};
});
return [groupNode, ...columnNodes];
}; };
//Конвертация серверного описания запроса в данные для редактора диаграмм //Конвертация серверного описания запроса в данные для редактора диаграмм
const serverQueryData2QueryDiagram = (entities, relations) => { const serverQueryData2QueryDiagram = (entities, relations) => {
const result = { entities: [], relations: [...relations] }; //Инициализация результата
entities.forEach(entity => { const result = { entities: [], relations: [], nodes: [], edges: [] };
const nodes = serverEntity2QueryDiagramNodes(entity); //Сущности (почти как есть на сервере, только массив XATTRS.XATTR перемещается в attrs)
result.entities = [...result.entities, ...nodes]; result.entities = entities.map(e => {
const tmp = { ...e };
tmp.attrs = tmp?.XATTRS?.XATTR?.map(a => ({ ...a })) || [];
delete tmp.XATTRS;
return tmp;
}); });
//Связи сущностей
result.relations = relations.map(r => ({ ...r }));
//Элементы для диаграммы
result.entities.forEach(entity => {
const nodes = serverEntity2QueryDiagramNodes(entity);
result.nodes = [...result.nodes, ...nodes];
});
//Грани для диаграммы
result.edges = relations.map(r => ({ ...r }));
//Вернем итоговый результат
return result; return result;
}; };

View File

@ -67,7 +67,7 @@ const QueryEditor = () => {
const selectEntity = ent => { const selectEntity = ent => {
setRelation(null); setRelation(null);
const queryEnt = queryDiagram.entities.find(e => e.id === ent); const queryEnt = queryDiagram.entities.find(e => e.id === ent);
if (queryEnt) setEntity({ ...queryEnt.data }); if (queryEnt) setEntity({ ...queryEnt });
}; };
//Выбор связи //Выбор связи
@ -137,6 +137,7 @@ const QueryEditor = () => {
setAppBarTitle(`Запрос [${name}]`); setAppBarTitle(`Запрос [${name}]`);
setQuery(rn); setQuery(rn);
setOpenQueriesManager(false); setOpenQueriesManager(false);
cleanupEnRlSelection();
}; };
//При изменении свойств запроса //При изменении свойств запроса
@ -163,7 +164,8 @@ const QueryEditor = () => {
<Grid item xs={20}> <Grid item xs={20}>
{queryDiagram && ( {queryDiagram && (
<QueryDiagram <QueryDiagram
{...queryDiagram} nodes={queryDiagram?.nodes}
edges={queryDiagram?.edges}
onEntityClick={handleEntityClick} onEntityClick={handleEntityClick}
onEntityAttrClick={handleEntityAttrClick} onEntityAttrClick={handleEntityAttrClick}
onEntityPositionChange={handleEntityPositionChange} onEntityPositionChange={handleEntityPositionChange}
@ -178,7 +180,14 @@ const QueryEditor = () => {
<Grid item xs={5} sx={STYLES.GRID_ITEM_INSPECTOR}> <Grid item xs={5} sx={STYLES.GRID_ITEM_INSPECTOR}>
{toolBar} {toolBar}
{query && ( {query && (
<Inspector {...queryOption} query={query} entity={entity} relation={relation} onOptionsChanged={handleQueryOptionsChanged} /> <Inspector
{...queryOption}
query={query}
entity={entity}
relation={relation}
entities={queryDiagram?.entities}
onOptionsChanged={handleQueryOptionsChanged}
/>
)} )}
</Grid> </Grid>
</Grid> </Grid>

View File

@ -23,6 +23,7 @@ create or replace package PKG_P8PANELS_QE_BASE as
type TATTR is record type TATTR is record
( (
SID PKG_STD.TSTRING, -- Уникальный идентификатор в запросе SID PKG_STD.TSTRING, -- Уникальный идентификатор в запросе
SPARENT_ENTITY PKG_STD.TSTRING, -- Идентификатор родительской сущности
SNAME PKG_STD.TSTRING, -- Имя SNAME PKG_STD.TSTRING, -- Имя
STITLE PKG_STD.TSTRING, -- Заголовок STITLE PKG_STD.TSTRING, -- Заголовок
NDATA_TYPE PKG_STD.TNUMBER, -- Тип данных (см. константы PKG_STD.DATA_TYPE_*) NDATA_TYPE PKG_STD.TNUMBER, -- Тип данных (см. константы PKG_STD.DATA_TYPE_*)
@ -310,27 +311,28 @@ create or replace package body PKG_P8PANELS_QE_BASE as
STAG_COND constant PKG_STD.TSTRING := 'XCOND'; -- Условия запроса STAG_COND constant PKG_STD.TSTRING := 'XCOND'; -- Условия запроса
/* Константы - Атрибуты для сериализации */ /* Константы - Атрибуты для сериализации */
SATTR_ID constant PKG_STD.TSTRING := 'id'; -- Идентификатор SATTR_ID constant PKG_STD.TSTRING := 'id'; -- Идентификатор
SATTR_RN constant PKG_STD.TSTRING := 'rn'; -- Регистрационный номер SATTR_RN constant PKG_STD.TSTRING := 'rn'; -- Регистрационный номер
SATTR_CODE constant PKG_STD.TSTRING := 'code'; -- Код SATTR_CODE constant PKG_STD.TSTRING := 'code'; -- Код
SATTR_NAME constant PKG_STD.TSTRING := 'name'; -- Имя SATTR_NAME constant PKG_STD.TSTRING := 'name'; -- Имя
SATTR_AUTHOR constant PKG_STD.TSTRING := 'author'; -- Автор SATTR_AUTHOR constant PKG_STD.TSTRING := 'author'; -- Автор
SATTR_CH_DATE constant PKG_STD.TSTRING := 'chDate'; -- Дата изменения SATTR_CH_DATE constant PKG_STD.TSTRING := 'chDate'; -- Дата изменения
SATTR_READY constant PKG_STD.TSTRING := 'ready'; -- Готовность к использованию SATTR_READY constant PKG_STD.TSTRING := 'ready'; -- Готовность к использованию
SATTR_PBL constant PKG_STD.TSTRING := 'pbl'; -- Публичность SATTR_PBL constant PKG_STD.TSTRING := 'pbl'; -- Публичность
SATTR_MODIFY constant PKG_STD.TSTRING := 'modify'; -- Изменяемость SATTR_MODIFY constant PKG_STD.TSTRING := 'modify'; -- Изменяемость
SATTR_TITLE constant PKG_STD.TSTRING := 'title'; -- Заголовок SATTR_TITLE constant PKG_STD.TSTRING := 'title'; -- Заголовок
SATTR_TYPE constant PKG_STD.TSTRING := 'type'; -- Тип SATTR_TYPE constant PKG_STD.TSTRING := 'type'; -- Тип
SATTR_DATA_TYPE constant PKG_STD.TSTRING := 'dataType'; -- Тип данных SATTR_DATA_TYPE constant PKG_STD.TSTRING := 'dataType'; -- Тип данных
SATTR_MANDATORY constant PKG_STD.TSTRING := 'mandatory'; -- Обязательность SATTR_MANDATORY constant PKG_STD.TSTRING := 'mandatory'; -- Обязательность
SATTR_X constant PKG_STD.TSTRING := 'x'; -- Координата по X SATTR_X constant PKG_STD.TSTRING := 'x'; -- Координата по X
SATTR_Y constant PKG_STD.TSTRING := 'y'; -- Координата по Y SATTR_Y constant PKG_STD.TSTRING := 'y'; -- Координата по Y
SATTR_SOURCE constant PKG_STD.TSTRING := 'source'; -- Источник SATTR_SOURCE constant PKG_STD.TSTRING := 'source'; -- Источник
SATTR_TARGET constant PKG_STD.TSTRING := 'target'; -- Приёмник SATTR_TARGET constant PKG_STD.TSTRING := 'target'; -- Приёмник
SATTR_AGG constant PKG_STD.TSTRING := 'agg'; -- Агрегатная функция SATTR_AGG constant PKG_STD.TSTRING := 'agg'; -- Агрегатная функция
SATTR_ALIAS constant PKG_STD.TSTRING := 'alias'; -- Псевдоним SATTR_ALIAS constant PKG_STD.TSTRING := 'alias'; -- Псевдоним
SATTR_USE constant PKG_STD.TSTRING := 'use'; -- Применение в запросе SATTR_USE constant PKG_STD.TSTRING := 'use'; -- Применение в запросе
SATTR_SHOW constant PKG_STD.TSTRING := 'show'; -- Отображение в запросе SATTR_SHOW constant PKG_STD.TSTRING := 'show'; -- Отображение в запросе
SATTR_PARENT_ENTITY constant PKG_STD.TSTRING := 'parentEntity'; -- Идентификатор родительской сущности
/* Константы - зарезервированные имена рагументов */ /* Константы - зарезервированные имена рагументов */
SARG_NAME_PAGE constant PKG_STD.TSTRING := 'NPAGE'; -- Номер страницы SARG_NAME_PAGE constant PKG_STD.TSTRING := 'NPAGE'; -- Номер страницы
@ -767,6 +769,7 @@ create or replace package body PKG_P8PANELS_QE_BASE as
PKG_XFAST.DOWN_NODE(SNAME => STAG_ATTR); PKG_XFAST.DOWN_NODE(SNAME => STAG_ATTR);
/* Атрибут */ /* Атрибут */
PKG_XFAST.ATTR(SNAME => SATTR_ID, SVALUE => RATTR.SID); PKG_XFAST.ATTR(SNAME => SATTR_ID, SVALUE => RATTR.SID);
PKG_XFAST.ATTR(SNAME => SATTR_PARENT_ENTITY, SVALUE => RATTR.SPARENT_ENTITY);
PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => RATTR.SNAME); PKG_XFAST.ATTR(SNAME => SATTR_NAME, SVALUE => RATTR.SNAME);
PKG_XFAST.ATTR(SNAME => SATTR_TITLE, SVALUE => RATTR.STITLE); PKG_XFAST.ATTR(SNAME => SATTR_TITLE, SVALUE => RATTR.STITLE);
PKG_XFAST.ATTR(SNAME => SATTR_DATA_TYPE, NVALUE => RATTR.NDATA_TYPE); PKG_XFAST.ATTR(SNAME => SATTR_DATA_TYPE, NVALUE => RATTR.NDATA_TYPE);
@ -798,14 +801,15 @@ create or replace package body PKG_P8PANELS_QE_BASE as
XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC); XROOT := PKG_XPATH.ROOT_NODE(RDOCUMENT => XDOC);
XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ATTR); XNODE := PKG_XPATH.SINGLE_NODE(RPARENT_NODE => XROOT, SPATTERN => '/' || STAG_ATTR);
/* Получаем значения */ /* Получаем значения */
RRES.SID := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_ID); RRES.SID := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_ID);
RRES.SNAME := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_NAME); RRES.SPARENT_ENTITY := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_PARENT_ENTITY);
RRES.STITLE := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TITLE); RRES.SNAME := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_NAME);
RRES.NDATA_TYPE := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_DATA_TYPE); RRES.STITLE := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_TITLE);
RRES.SAGG := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_AGG); RRES.NDATA_TYPE := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_DATA_TYPE);
RRES.SALIAS := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_ALIAS); RRES.SAGG := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_AGG);
RRES.NUSE := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_USE); RRES.SALIAS := PKG_XPATH.ATTRIBUTE(RNODE => XNODE, SNAME => SATTR_ALIAS);
RRES.NSHOW := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_SHOW); RRES.NUSE := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_USE);
RRES.NSHOW := PKG_XPATH.ATTRIBUTE_NUM(RNODE => XNODE, SNAME => SATTR_SHOW);
/* Освободим документ */ /* Освободим документ */
PKG_XPATH.FREE(RDOCUMENT => XDOC); PKG_XPATH.FREE(RDOCUMENT => XDOC);
exception exception
@ -908,6 +912,7 @@ create or replace package body PKG_P8PANELS_QE_BASE as
else else
/* Такого элемента нет - берем из представления */ /* Такого элемента нет - берем из представления */
RRES(RRES.LAST).SID := TATTR_ID_MAKE(SENT_ID => RENT.SID, SNAME => RVIEW_FIELD.COLUMN_NAME); RRES(RRES.LAST).SID := TATTR_ID_MAKE(SENT_ID => RENT.SID, SNAME => RVIEW_FIELD.COLUMN_NAME);
RRES(RRES.LAST).SPARENT_ENTITY := RENT.SID;
RRES(RRES.LAST).SNAME := RVIEW_FIELD.COLUMN_NAME; RRES(RRES.LAST).SNAME := RVIEW_FIELD.COLUMN_NAME;
RRES(RRES.LAST).STITLE := DMSCLVIEWSATTRS_TITLE_GET(SVIEW_NAME => RENT.SNAME, RRES(RRES.LAST).STITLE := DMSCLVIEWSATTRS_TITLE_GET(SVIEW_NAME => RENT.SNAME,
SATTR_NAME => RRES(RRES.LAST).SNAME); SATTR_NAME => RRES(RRES.LAST).SNAME);