From da5084407a71e92fc63b826f5cad0883306ec7a6 Mon Sep 17 00:00:00 2001 From: Mikhail Chechnev Date: Thu, 9 May 2024 20:40:11 +0300 Subject: [PATCH] =?UTF-8?q?WEB=20APP:=20P8PSVG=20-=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BF=D0=BE=D0=BD=D0=B5=D1=82=20=D0=B4=D0=BB=D1=8F=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D0=B8=D0=BD=D1=82?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D1=8B=D0=BC?= =?UTF-8?q?=D0=B8=20=D0=B8=D0=B7=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=D0=BC=D0=B8=20=D0=B8=20=D0=BF=D1=80=D0=B8=D0=BC?= =?UTF-8?q?=D0=B5=D1=80=D1=8B=20=D0=B4=D0=BB=D1=8F=20=D0=BD=D0=B5=D0=B3?= =?UTF-8?q?=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/components/p8p_svg.js | 94 ++++++++++++++++++++++++++++++ app/panels/samples/samples.js | 4 +- app/panels/samples/svg.js | 103 +++++++++++++++++++++++++++++++++ img/sample.svg | 105 ++++++++++++++++++++++++++++++++++ 4 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 app/components/p8p_svg.js create mode 100644 app/panels/samples/svg.js create mode 100644 img/sample.svg diff --git a/app/components/p8p_svg.js b/app/components/p8p_svg.js new file mode 100644 index 0000000..602d9d9 --- /dev/null +++ b/app/components/p8p_svg.js @@ -0,0 +1,94 @@ +/* + Парус 8 - Панели мониторинга + Компонент: Интерактивные изображения SVG +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useEffect, useRef } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента + +//--------- +//Константы +//--------- + +//Стили +const STYLES = { + CANVAS: { width: "100%", height: "100%" } +}; + +//Структура элемента изображения +const P8P_SVG_ITEM_SHAPE = PropTypes.shape({ + id: PropTypes.string.isRequired, + backgroundColor: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]) +}); + +//----------- +//Тело модуля +//----------- + +//Интерактивные изображения SVG +const P8PSVG = ({ data, items, onClick, style }) => { + //Ссылки на DOM + const svgContainerRef = useRef(null); + const svgRef = useRef(null); + + //Обработка нажатия на элемент изображения + const handleClick = e => { + if (e.target.id && items && onClick) { + const item = items.find(item => item.id == e.target.id); + if (item) onClick({ item }); + } + }; + + //Формирование интерактивных элементов изображения + const makeSVGItems = () => { + items.forEach(item => { + const svgE = document.getElementById(item.id); + if (svgE) { + svgE.setAttribute("style", `${onClick ? "cursor: pointer" : ""}; ${item.backgroundColor ? `fill: ${item.backgroundColor}` : ""}`); + if (item?.title) { + const titleE = document.createElementNS("http://www.w3.org/2000/svg", "title"); + titleE.textContent = item.title; + svgE.replaceChildren(titleE); + } + } + }); + }; + + //Загрузка изображения + const loadSVG = () => { + const parser = new DOMParser(); + const doc = parser.parseFromString(data, "image/svg+xml"); + svgRef.current = doc.documentElement; + svgRef.current.onclick = handleClick; + svgContainerRef.current.replaceChildren(svgRef.current); + if (items) makeSVGItems(items); + }; + + //При обновлении данных + useEffect(() => { + console.log(items); + loadSVG(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data, items]); + + //Генерация содержимого + return
; +}; + +//Контроль свойств - Интерактивные изображения SVG +P8PSVG.propTypes = { + data: PropTypes.string.isRequired, + items: PropTypes.arrayOf(P8P_SVG_ITEM_SHAPE), + onClick: PropTypes.func, + style: PropTypes.object +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { P8PSVG }; diff --git a/app/panels/samples/samples.js b/app/panels/samples/samples.js index 323ef08..cbf8df8 100644 --- a/app/panels/samples/samples.js +++ b/app/panels/samples/samples.js @@ -17,6 +17,7 @@ import { Loader } from "./loader"; //Пример: Индикатор проце import { DataGrid } from "./data_grid"; //Пример: Таблица данных "P8PDataGrid" import { Chart } from "./chart"; //Пример: Графики "P8PChart" import { Gantt } from "./gantt"; //Пример: Диаграмма Ганта "P8PGantt" +import { Svg } from "./svg"; //Пример: Интерактивные изображения "P8PSVG" //--------- //Константы @@ -30,7 +31,8 @@ const MODES = { LOADER: { name: "LOADER", caption: "Индикатор процесса", component: Loader }, DATAGRID: { name: "DATAGRID", caption: 'Таблица данных "P8PDataGrid"', component: DataGrid }, CHART: { name: "CHART", caption: 'Графики "P8PChart"', component: Chart }, - GANTT: { name: "GANTT", caption: 'Диаграмма Ганта "P8PGantt"', component: Gantt } + GANTT: { name: "GANTT", caption: 'Диаграмма Ганта "P8PGantt"', component: Gantt }, + SVG: { name: "SVG", caption: 'Интерактивные изображения "P8PSVG"', component: Svg } }; //Стили diff --git a/app/panels/samples/svg.js b/app/panels/samples/svg.js new file mode 100644 index 0000000..04460d3 --- /dev/null +++ b/app/panels/samples/svg.js @@ -0,0 +1,103 @@ +/* + Парус 8 - Панели мониторинга - Примеры для разработчиков + Пример: Интерактивные изображения "P8PSVG" +*/ + +//--------------------- +//Подключение библиотек +//--------------------- + +import React, { useState, useEffect } from "react"; //Классы React +import PropTypes from "prop-types"; //Контроль свойств компонента +import { Typography, Grid, FormControl, FormLabel, RadioGroup, FormControlLabel, Radio } from "@mui/material"; //Интерфейсные элементы +import { P8PSVG } from "../../components/p8p_svg"; //Интерактивные изображения + +//--------- +//Константы +//--------- + +//Адрес тестового изображения +const SAMPLE_URL = "http://localhost:8080/P8WebClient/Modules/p8-panels/img/sample.svg"; + +//Стили +const STYLES = { + CONTAINER: { textAlign: "center", paddingTop: "20px" }, + TITLE: { paddingBottom: "15px" }, + SVG: { width: "95vw", height: "60vw", display: "flex", justifyContent: "center" } +}; + +//----------- +//Тело модуля +//----------- + +//Пример: Интерактивные изображения "P8PSVG" +const Svg = ({ title }) => { + //Собственное состояние - SVG-изображение + const [svg, setSVG] = useState({ + loaded: false, + data: null, + mode: "items1", + items1: [ + { id: "1", backgroundColor: "red", desc: "Цифра на флюзеляже" }, + { id: "2", backgroundColor: "magenta", desc: "Ребро флюзеляжа" }, + { id: "3", backgroundColor: "yellow", desc: "Люк" } + ], + items2: [ + { id: "4", backgroundColor: "green", desc: "Хвост", title: "Хвост" }, + { id: "5", backgroundColor: "blue", desc: "Хвостовой руль", title: "Хвостовой руль" }, + { id: "6", backgroundColor: "aquamarine", desc: "Ребро жесткости хвоста", title: "Ребро жесткости хвоста" } + ], + selectedItemDesc: "" + }); + + //Загрузка изображения + const loadSVG = async () => { + const resp = await fetch(SAMPLE_URL); + const data = await resp.text(); + setSVG(pv => ({ ...pv, loaded: true, data })); + }; + + //Отработка нажатия на элемент изображения + const handleSVGItemClick = ({ item }) => { + setSVG(pv => ({ ...pv, selectedItemDesc: item?.desc ? `Выбран элемент: ${item.desc}` : "Для выбранного элемента не задано описание" })); + }; + + //При подключении к странице + useEffect(() => { + loadSVG(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + //Генерация содержимого + return ( +
+ + {title} + + + Группа элементов + setSVG(pv => ({ ...pv, mode: e.target.value, selectedItemDesc: "" }))}> + } label="Элементы первой группы" /> + } label="Элементы второй группы" /> + + {svg.selectedItemDesc ? svg.selectedItemDesc : "Нажмите на элемент изображения для получения его описания"} + + + + {svg.loaded ? : null} + + +
+ ); +}; + +//Контроль свойств - Пример: Интерактивные изображения "P8PSVG" +Svg.propTypes = { + title: PropTypes.string.isRequired +}; + +//---------------- +//Интерфейс модуля +//---------------- + +export { Svg }; diff --git a/img/sample.svg b/img/sample.svg new file mode 100644 index 0000000..54a7426 --- /dev/null +++ b/img/sample.svg @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +