big update

This commit is contained in:
Arsen Mirzaev Tatyano-Muradovich 2022-12-03 15:05:28 +10:00
parent a854584070
commit a104e244df

235
graph.js
View File

@ -6,8 +6,38 @@ import Victor from "https://cdn.skypack.dev/victor@1.1.0";
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/ */
class graph { class graph {
// Идентификатор HTML-элемента-оболочки (instanceof HTMLElement)
#id = 'graph';
// Прочитать идентификатор HTML-элемента-оболочки (instanceof HTMLElement)
get id() {
return this.#id;
}
// Классы которые будут записаны в HTML-элементы
classes = {
node: {
shell: ['nodes'],
element: ['node'],
onmouseenter: ['onmouseenter'],
title: ['title'],
description: ['description'],
wrappers: {
both: ['wrapper'],
left: ['left'],
right: ['right']
},
cover: ['cover'],
close: ['close']
},
connection: {
shell: ['connections'],
element: ['connection']
}
};
// Оболочка (instanceof HTMLElement) // Оболочка (instanceof HTMLElement)
#shell = document.getElementById("graph"); #shell = document.getElementById(this.id);
get shell() { get shell() {
return this.#shell; return this.#shell;
} }
@ -44,8 +74,18 @@ class graph {
return this.#operator; return this.#operator;
} }
// HTML-элемент-оболочка
#shell;
// Прочитать HTML-элемент-оболочка
get shell() {
return this.#shell;
}
// HTML-элемент // HTML-элемент
#element; #element;
// Прочитать HTML-элемент
get element() { get element() {
return this.#element; return this.#element;
} }
@ -94,10 +134,33 @@ class graph {
}; };
constructor(operator, data) { constructor(operator, data) {
// Запись в свойство
this.#operator = operator;
// Инициализация ссылки на ядро
const _this = this;
// Инициализация HTML-элемента-оболочки узлов
if ((this.#shell = document.getElementById(this.#operator.id + '_nodes')) instanceof HTMLElement);
else {
// Не найден HTML-элемент-оболочки узлов
// Инициализация HTML-элемента-оболочки узлов
const shell = document.createElement('section');
shell.id = this.#operator.id + '_nodes';
shell.classList.add(...this.#operator.classes.node.shell);
// Запись в документ
this.#operator.shell.appendChild(shell);
// Запись в свойство
this.#shell = shell;
}
// Инициализация HTML-элемента узла // Инициализация HTML-элемента узла
const article = document.createElement("article"); const article = document.createElement("article");
article.id = operator.nodes.size; article.id = this.#operator.id + '_node_' + this.#operator.nodes.size;
article.classList.add(data.color ?? 'white', "node", "unselectable"); article.classList.add(data.color ?? null, ..._this.operator.classes.node.element);
if (typeof data.href === "string") { if (typeof data.href === "string") {
article.href = data.href; article.href = data.href;
} }
@ -105,12 +168,12 @@ class graph {
// Запись анимации "выделение обводкой" (чтобы не проигрывалась при открытии страницы) // Запись анимации "выделение обводкой" (чтобы не проигрывалась при открытии страницы)
article.onmouseenter = fn => { article.onmouseenter = fn => {
// Запись класса с анимацией // Запись класса с анимацией
article.classList.add('animated'); article.classList.add(..._this.#operator.classes.node.onmouseenter);
}; };
// Инициализация заголовка // Инициализация заголовка
const title = document.createElement("h4"); const title = document.createElement("h4");
title.classList.add('title'); title.classList.add(..._this.#operator.classes.node.title);
title.innerText = data.title ?? ''; title.innerText = data.title ?? '';
// Запись в оболочку // Запись в оболочку
@ -118,13 +181,13 @@ class graph {
// Инициализация описания // Инициализация описания
const description = document.createElement("div"); const description = document.createElement("div");
description.classList.add('description'); description.classList.add(..._this.#operator.classes.node.title.description);
if (typeof data.popup === 'string') description.title = data.popup; if (typeof data.popup === 'string') description.title = data.popup;
// Запись анимации "выделение обводкой" (чтобы не проигрывалась при открытии страницы) // Запись анимации "выделение обводкой" (чтобы не проигрывалась при открытии страницы)
description.onmouseenter = fn => { description.onmouseenter = fn => {
// Запись класса с анимацией // Запись класса с анимацией
description.classList.add('animated'); description.classList.add(..._this.#operator.classes.node.onmouseenter);
}; };
// Запись блокировки открытия описания в случае, если был перемещён узел // Запись блокировки открытия описания в случае, если был перемещён узел
@ -136,7 +199,7 @@ class graph {
// Запись события открытия описания // Запись события открытия описания
title.onclick = (onclick) => { title.onclick = (onclick) => {
// Отображение описания // Отображение описания
show(); _this.show();
// Удаление событий // Удаление событий
title.onclick = title.onmousemove = null; title.onclick = title.onmousemove = null;
@ -175,7 +238,7 @@ class graph {
// Запись события открытия описания // Запись события открытия описания
title.onclick = (onclick) => { title.onclick = (onclick) => {
// Отображение описания // Отображение описания
show(); _this.show();
// Удаление событий // Удаление событий
title.onclick = title.onmousemove = null; title.onclick = title.onmousemove = null;
@ -198,14 +261,14 @@ class graph {
// Инициализация левой фигуры для обёртки текста // Инициализация левой фигуры для обёртки текста
const left = document.createElement("span"); const left = document.createElement("span");
left.classList.add('left', 'wrapper'); left.classList.add(..._this.#operator.classes.node.wrappers.both, ..._this.#operator.classes.node.wrappers.left);
// Запись в описание // Запись в описание
description.appendChild(left); description.appendChild(left);
// Инициализация правой фигуры для обёртки текста // Инициализация правой фигуры для обёртки текста
const right = document.createElement("span"); const right = document.createElement("span");
right.classList.add('right', 'wrapper'); right.classList.add(..._this.#operator.classes.node.wrappers.both, ..._this.#operator.classes.node.wrappers.right);
// Запись в описание // Запись в описание
description.appendChild(right); description.appendChild(right);
@ -299,7 +362,7 @@ class graph {
const cover = document.createElement("img"); const cover = document.createElement("img");
if (typeof cover.src === 'string') cover.src = data.cover; if (typeof cover.src === 'string') cover.src = data.cover;
if (typeof cover.alt === 'string') cover.alt = data.title; if (typeof cover.alt === 'string') cover.alt = data.title;
cover.classList.add('cover', 'unselectable'); cover.classList.add(..._this.#operator.classes.node.cover);
// Запись в описание // Запись в описание
description.appendChild(cover); description.appendChild(cover);
@ -317,7 +380,7 @@ class graph {
// Инициализация кнопки закрытия // Инициализация кнопки закрытия
const close = document.createElement('i'); const close = document.createElement('i');
close.classList.add('icon', 'close'); close.classList.add(..._this.#operator.classes.node.close);
close.style.display = 'none'; close.style.display = 'none';
// Запись блокировки закрытия в случае, если был перемещён узел // Запись блокировки закрытия в случае, если был перемещён узел
@ -329,7 +392,7 @@ class graph {
// Запись события открытия описания // Запись события открытия описания
close.onclick = (onclick) => { close.onclick = (onclick) => {
// Скрытие описания // Скрытие описания
hide(); _this.hide();
// Удаление событий // Удаление событий
close.onclick = close.onmousemove = null; close.onclick = close.onmousemove = null;
@ -368,7 +431,7 @@ class graph {
// Запись события открытия описания // Запись события открытия описания
close.onclick = (onclick) => { close.onclick = (onclick) => {
// Скрытие описания // Скрытие описания
hide(); _this.hide();
// Удаление событий // Удаление событий
close.onclick = close.onmousemove = null; close.onclick = close.onmousemove = null;
@ -390,7 +453,7 @@ class graph {
article.appendChild(close); article.appendChild(close);
// Запись в документ // Запись в документ
operator.shell.appendChild(article); this.#shell.appendChild(article);
// Запись диаметра описания в зависимости от размера заголовка (чтобы вмещался) // Запись диаметра описания в зависимости от размера заголовка (чтобы вмещался)
description.style.width = description.style.height = (a.offsetWidth === 0 ? 50 : a.offsetWidth) * 3 + 'px'; description.style.width = description.style.height = (a.offsetWidth === 0 ? 50 : a.offsetWidth) * 3 + 'px';
@ -404,7 +467,7 @@ class graph {
/** /**
* Показать описание * Показать описание
*/ */
function show() { this.show = () => {
// Отображение описания и кнопки закрытия описания // Отображение описания и кнопки закрытия описания
description.style.display = close.style.display = null; description.style.display = close.style.display = null;
@ -424,7 +487,7 @@ class graph {
/** /**
* Скрыть описание * Скрыть описание
*/ */
function hide() { this.hide = () => {
// Скрытие описания и кнопки закрытия описания // Скрытие описания и кнопки закрытия описания
description.style.display = close.style.display = 'none'; description.style.display = close.style.display = 'none';
@ -435,18 +498,15 @@ class graph {
// Запись в свойство // Запись в свойство
this.#element = article; this.#element = article;
// Запись в свойство
this.#operator = operator;
// Инициализация // Инициализация
this.init(); this.init();
// Перемещение // Перемещение
this.move( this.move(
operator.shell.offsetWidth / 2 - this.#operator.shell.offsetWidth / 2 -
this.#diameter / 2 + this.#diameter / 2 +
(0.5 - Math.random()) * 500, (0.5 - Math.random()) * 500,
operator.shell.offsetHeight / 2 - this.#operator.shell.offsetHeight / 2 -
this.#diameter / 2 + this.#diameter / 2 +
(0.5 - Math.random()) * 500, (0.5 - Math.random()) * 500,
true, true,
@ -513,10 +573,10 @@ class graph {
// Обработка столкновений // Обработка столкновений
if (collision && !collision.has(this)) if (collision && !collision.has(this))
this.collision(this.operator.nodes, collision); this.collision(this.#operator.nodes, collision);
// Инициализация буфера реестра узлов // Инициализация буфера реестра узлов
const registry = new Set(this.operator.nodes); const registry = new Set(this.#operator.nodes);
if (pushing && !pushing.has(this)) { if (pushing && !pushing.has(this)) {
// Активно отталкивание // Активно отталкивание
@ -606,10 +666,10 @@ class graph {
if (pushing) this.pushing(registry, pushing); if (pushing) this.pushing(registry, pushing);
// Синхронизация местоположения исходящих соединений // Синхронизация местоположения исходящих соединений
for (const connection of this.outputs) connection.sync(this); for (const connection of this.outputs) connection.synchronize(this);
// Синхронизация местоположения входящих соединений // Синхронизация местоположения входящих соединений
for (const connection of this.inputs) connection.sync(this); for (const connection of this.inputs) connection.synchronize(this);
} }
collision(nodes, involved) { collision(nodes, involved) {
@ -1024,33 +1084,41 @@ class graph {
} }
}; };
// Класс узла // Прочитать класс узла
get node() { get node() {
return this.#node; return this.#node;
} }
// Класс соединения // Класс соединения
#connection = class connection { #connection = class connection {
// HTML-элемент // HTML-элемент-оболочка
#shell;
// Прочитать HTML-элемент-оболочку
get shell() {
return this.#shell;
}
// HTML-элемент соединения
#element; #element;
// HTML-элемент // Прочитать HTML-элемент соединения
get element() { get element() {
return this.#element; return this.#element;
} }
// Инстанция node от которой начинается соединение // Инстанция this.operator.node от которой начинается соединение
#from; #from;
// Инстанция node от которой начинается соединение // Прочитать инстанцию this.operator.node от которой начинается соединение
get from() { get from() {
return this.#from; return this.#from;
} }
// Инстанция node на которой заканчивается соединение // Инстанция this.operator.node на которой заканчивается соединение
#to; #to;
// Инстанция node на которой заканчивается соединение // Прочитать инстанцию this.operator.node на которой заканчивается соединение
get to() { get to() {
return this.#to; return this.#to;
} }
@ -1058,7 +1126,7 @@ class graph {
// Оператор // Оператор
#operator; #operator;
// Оператор // Прочитать оператора
get operator() { get operator() {
return this.#operator; return this.#operator;
} }
@ -1073,12 +1141,22 @@ class graph {
// Запись свойства // Запись свойства
this.#to = to; this.#to = to;
// Инициализация оболочки // Инициализация HTML-элемента-оболочки соединений
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); if ((this.#shell = document.getElementById(this.#operator.id + '_connections')) instanceof SVGElement);
svg.id = operator.connections.size; else {
svg.classList.add("connection"); // Не найден HTML-элемент-оболочки соединений
svg.setAttribute("data-from", from.element.id);
svg.setAttribute("data-to", to.element.id); // Инициализация HTML-элемента-оболочки соединений
const shell = document.createElementNS("http://www.w3.org/2000/svg", "svg");
shell.id = this.#operator.id + '_connections';
shell.classList.add(...this.#operator.classes.connection.shell);
// Запись в документ
this.#operator.shell.appendChild(shell);
// Запись в свойство
this.#shell = shell;
}
// Инициализация универсального буфера // Инициализация универсального буфера
let buffer; let buffer;
@ -1110,15 +1188,16 @@ class graph {
); );
line.setAttribute("stroke", "grey"); line.setAttribute("stroke", "grey");
line.setAttribute("stroke-width", "8px"); line.setAttribute("stroke-width", "8px");
line.id = this.#operator.id + '_connection_' + operator.connections.size;
// Запись свойства line.classList.add(...this.operator.classes.connection.element);
this.#element = svg; line.setAttribute("data-from", from.element.id);
line.setAttribute("data-to", to.element.id);
// Запись в оболочку // Запись в оболочку
svg.append(line); this.shell.append(line);
// Запись в документ // Запись в свойство
operator.shell.appendChild(svg); this.#element = line;
} }
/** /**
@ -1126,7 +1205,7 @@ class graph {
* *
* @param {node} node Инстанция узла (связанного с соединением) * @param {node} node Инстанция узла (связанного с соединением)
*/ */
sync(node) { synchronize(node) {
// Инициализация названий аттрибутов // Инициализация названий аттрибутов
let x = "x", let x = "x",
y = "y"; y = "y";
@ -1149,14 +1228,14 @@ class graph {
let buffer; let buffer;
// Запись отступа (координаты по горизонтали) // Запись отступа (координаты по горизонтали)
this.element.children[0].setAttribute( this.element.setAttribute(
x, x,
(isNaN((buffer = parseInt(node.element.style.left))) ? 0 : buffer) + (isNaN((buffer = parseInt(node.element.style.left))) ? 0 : buffer) +
node.element.offsetWidth / 2 node.element.offsetWidth / 2
); );
// Запись отступа (координаты по вертикали) // Запись отступа (координаты по вертикали)
this.element.children[0].setAttribute( this.element.setAttribute(
y, y,
(isNaN((buffer = parseInt(node.element.style.top))) ? 0 : buffer) + (isNaN((buffer = parseInt(node.element.style.top))) ? 0 : buffer) +
node.element.offsetHeight / 2 node.element.offsetHeight / 2
@ -1164,56 +1243,88 @@ class graph {
} }
}; };
// Класс соединения // Прочитать класс соединения
get connection() { get connection() {
return this.#connection; return this.#connection;
} }
// Разрешено перемещать узлы?
#move = true; #move = true;
// Разрешено перемещать камеру? (svg-элементы-соединения - рёбра)
#camera = true; #camera = true;
constructor(shell, camera = true) { constructor(shell, body = true, camera = true) {
// Запись оболочки // Запись оболочки
if (shell instanceof HTMLElement) this.#shell = shell; if (shell instanceof HTMLElement) this.#shell = shell;
else if (typeof shell === 'string') this.#shell = document.getElementById(shell);
// Проверка на инициализированность HTML-элемента-оболочки
if (typeof this.#shell === undefined) return false;
// Запись идентификатора
this.#id = this.#shell.id;
// Инициализация ссылки на обрабатываемый объект // Инициализация ссылки на обрабатываемый объект
const _this = this; const _this = this;
// Инициализация цели для переноса
const target = body ? document.body : shell;
// Перемещение камеры // Перемещение камеры
if (camera === true) { if (camera === true) {
this.shell.onmousedown = function (e) { target.onmousedown = function (onmousedown) {
// Начало переноса // Начало переноса
if (_this.#camera) { if (_this.#camera) {
// Разрешено двигать камеру (оболочку) // Разрешено двигать камеру (оболочку)
// Запись иконки курсора
target.style.cursor = 'move';
// Инициализация координат // Инициализация координат
const coords = _this.shell.getBoundingClientRect(); const coords = _this.shell.getBoundingClientRect();
const x = e.pageX - coords.left + pageXOffset; const x = onmousedown.pageX - coords.left + scrollX;
const y = e.pageY - coords.top + pageYOffset; const y = onmousedown.pageY - coords.top + scrollY;
// Инициализация буфера высчитанных отступов полотна
let _x, _y;
// Инициализация функции переноса полотна // Инициализация функции переноса полотна
function move(onmousemove) { function move(onmousemove) {
// Запись нового отступа от лева // Запись нового отступа от лева
_this.shell.style.left = onmousemove.pageX - x + "px"; _this.shell.style.left = (_x = onmousemove.pageX - x) + "px";
// Запись нового отступа от верха // Запись нового отступа от верха
_this.shell.style.top = onmousemove.pageY - y + "px"; _this.shell.style.top = (_y = onmousemove.pageY - y) + "px";
// for (const connection of _this.#connections) {
// // Перебор всех HTML-элементов-соединений
// // Запись нового отступа от лева
// connection.element.style.left = -_x + "px";
// // Запись нового отступа от верха
// connection.element.style.top = -_y + "px";
// }
} }
// Запись слушателя события: "перенос полотна" // Запись слушателя события: "перенос полотна"
document.onmousemove = move; target.onmousemove = move;
} }
// Конец переноса // Конец переноса
_this.shell.onmouseup = function () { target.onmouseup = function () {
document.onmousemove = null; target.onmousemove = null;
_this.shell.onmouseup = null; target.onmouseup = null;
// Запись иконки курсора
target.style.cursor = null;
}; };
}; };
// Блокировка событий браузера (чтобы не дёргалось) // Блокировка событий браузера (чтобы не дёргалось)
_this.shell.ondragstart = null; target.ondragstart = null;
} }
} }