P8-Panels/app/components/p8p_svg.js

161 lines
5.4 KiB
JavaScript

/*
Парус 8 - Панели мониторинга
Компонент: Интерактивные изображения SVG
*/
//---------------------
//Подключение библиотек
//---------------------
import React, { useEffect, useRef, useState } from "react"; //Классы React
import { IconButton, Icon } from "@mui/material"; //Интерфейсные элементы
import PropTypes from "prop-types"; //Контроль свойств компонента
//---------
//Константы
//---------
//Стили
const STYLES = {
CANVAS: { width: "100%", height: "100%" },
CONTROLS: { justifyContent: "center", alignItems: "center", display: "flex" }
};
//Структура элемента изображения
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, onItemClick, canvasStyle }) => {
//Собственное состояние
const [state, setState] = useState({
images: [],
currentImage: 0,
imagesCount: 0
});
//Ссылки на DOM
const svgContainerRef = useRef(null);
const svgRef = useRef(null);
//Обработка нажатия на элемент изображения
const handleClick = e => {
let itemClickFired = false;
if (e.target.id && items && onItemClick) {
const item = items.find(item => item.id == e.target.id);
if (item) {
onItemClick({ item });
itemClickFired = true;
}
}
if (!itemClickFired && onClick) onClick(e);
};
//Формирование интерактивных элементов изображения
const makeSVGItems = () => {
items.forEach(item => {
const svgE = document.getElementById(item.id);
if (svgE) {
svgE.setAttribute("style", `${onItemClick ? "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 images = data
.split("</svg>")
.filter(i => i)
.map(i => i + "</svg>");
setState(pv => ({ ...pv, images, imagesCount: images.length, currentImage: 0 }));
};
//Отображение текущего изображения
const showSVG = () => {
if (state.imagesCount > 0) {
const parser = new DOMParser();
const doc = parser.parseFromString(state.images[state.currentImage], "image/svg+xml");
svgRef.current = doc.documentElement;
svgRef.current.onclick = handleClick;
svgContainerRef.current.replaceChildren(svgRef.current);
if (items) makeSVGItems(items);
}
};
//Переключение текущего изображения
const switchImage = direction => {
setState(pv => ({
...pv,
currentImage:
direction > 0
? pv.currentImage + 1 >= pv.imagesCount
? 0
: pv.currentImage + 1
: pv.currentImage - 1 < 0
? pv.imagesCount - 1
: pv.currentImage - 1
}));
};
//При обновлении данных
useEffect(() => {
loadSVG();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
//При загрузке изображения
useEffect(() => {
showSVG();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [state.images, state.currentImage, items]);
//При прокрутке изображений назад
const handlePrevClick = () => switchImage(-1);
//При прокрутке изображений вперёд
const handleNextClick = () => switchImage(1);
//Генерация содержимого
return (
<div>
<div ref={svgContainerRef} style={{ ...STYLES.CANVAS, ...(canvasStyle ? canvasStyle : {}) }}></div>
{state.imagesCount > 1 ? (
<div style={STYLES.CONTROLS}>
<IconButton onClick={handlePrevClick}>
<Icon>arrow_left</Icon>
</IconButton>
<IconButton onClick={handleNextClick}>
<Icon>arrow_right</Icon>
</IconButton>
</div>
) : null}
</div>
);
};
//Контроль свойств - Интерактивные изображения SVG
P8PSVG.propTypes = {
data: PropTypes.string.isRequired,
items: PropTypes.arrayOf(P8P_SVG_ITEM_SHAPE),
onClick: PropTypes.func,
onItemClick: PropTypes.func,
canvasStyle: PropTypes.object
};
//----------------
//Интерфейс модуля
//----------------
export { P8PSVG };