Better rendering of buttons for node on graph

This commit is contained in:
dimitrievgs 2025-10-13 01:19:56 +03:00
parent 05fbd5a9f4
commit 37f9986b02
5 changed files with 226 additions and 153 deletions

159
main.js

File diff suppressed because one or more lines are too long

View File

@ -19,7 +19,7 @@ import ReactFlow, {
Edge, Edge,
BackgroundVariant BackgroundVariant
} from 'reactflow'; } from 'reactflow';
import { App } from 'obsidian'; import { App, setIcon } from 'obsidian';
import { Dialog } from 'src/utils/Dialog'; import { Dialog } from 'src/utils/Dialog';
// <-- ИСПРАВЛЕНО: Это ключевой момент. Убедитесь, что esbuild настроен на обработку CSS импортов. // <-- ИСПРАВЛЕНО: Это ключевой момент. Убедитесь, что esbuild настроен на обработку CSS импортов.
@ -66,21 +66,17 @@ interface BaseNodeWrapperProps {
* @returns {React.ReactElement} Элемент React. * @returns {React.ReactElement} Элемент React.
*/ */
const BaseNodeWrapper: React.FC<BaseNodeWrapperProps> = ({ data, isConnectable, children }) => { const BaseNodeWrapper: React.FC<BaseNodeWrapperProps> = ({ data, isConnectable, children }) => {
// ----------------------------------------------- Styles -------------------------------------------------------------------- const deleteIconRef = React.useRef<HTMLSpanElement>(null);
// Стили для кнопки удаления. Вынесены для переиспользования. const regenerateIconRef = React.useRef<HTMLSpanElement>(null);
const deleteButtonStyle: React.CSSProperties = {
position: 'absolute', React.useEffect(() => {
bottom: '-30px', // Размещаем под узлом if (deleteIconRef.current) {
left: '5px', setIcon(deleteIconRef.current, 'trash-2');
background: 'transparent', }
border: 'none', if (regenerateIconRef.current) {
cursor: 'pointer', setIcon(regenerateIconRef.current, 'refresh-cw');
fontSize: '1em', }
color: 'var(--text-muted)', // Используем цвет текста Obsidian }, []);
padding: '0 5px',
opacity: data.isSelected ? 1 : 0, // Показываем только если узел выделен
transition: 'opacity 0.2s ease-in-out'
};
// ----------------------------------------------- Event Handlers ------------------------------------------------------------ // ----------------------------------------------- Event Handlers ------------------------------------------------------------
/** /**
@ -109,6 +105,7 @@ const BaseNodeWrapper: React.FC<BaseNodeWrapperProps> = ({ data, isConnectable,
} }
}; };
// ----------------------------------------------- Render -------------------------------------------------------------------- // ----------------------------------------------- Render --------------------------------------------------------------------
return React.createElement( return React.createElement(
React.Fragment, React.Fragment,
null, null,
@ -123,37 +120,28 @@ const BaseNodeWrapper: React.FC<BaseNodeWrapperProps> = ({ data, isConnectable,
isConnectable: isConnectable isConnectable: isConnectable
}), }),
children, // Здесь будет отображаться специфическое содержимое узла children, // Здесь будет отображаться специфическое содержимое узла
data.isSelected &&
React.createElement( React.createElement(
'div', 'div',
{ {
style: { className: 'node-toolbar' // Применяем класс для тулбара
position: 'absolute',
bottom: '-30px',
left: '5px',
display: 'flex',
gap: '5px'
}
}, },
React.createElement( React.createElement(
'button', 'button',
{ {
style: deleteButtonStyle,
onClick: handleNodeDelete, onClick: handleNodeDelete,
'aria-label': 'Удалить узел', 'aria-label': 'Удалить узел',
className: 'node-delete-button' className: 'node-action-button node-delete-button' // Применяем классы для кнопки
}, },
'🗑️' React.createElement('span', { ref: deleteIconRef }) // Элемент для иконки удаления
), ),
React.createElement( React.createElement(
'button', 'button',
{ {
style: deleteButtonStyle,
onClick: handleRegenerate, onClick: handleRegenerate,
'aria-label': 'Регенерировать', 'aria-label': 'Регенерировать',
className: 'node-regenerate-button' className: 'node-action-button node-regenerate-button' // Применяем классы для кнопки
}, },
'🔄' React.createElement('span', { ref: regenerateIconRef }) // Элемент для иконки регенерации
) )
) )
); );

View File

@ -842,22 +842,67 @@
} }
/* Стиль для кнопок внутри узла (например, кнопки удаления) */ /* Node Toolbar: Контейнер для кнопок действий */
.react-flow__node button.node-delete-button { .react-flow__node .node-toolbar {
background: var(--background-primary); position: absolute;
border: 1px solid var(--background-modifier-border); top: 4px; /* Отступ сверху */
border-radius: 4px; right: 3px; /* <-- ИЗМЕНЕНО: Сдвинуто на 4px правее */
color: var(--text-error); /* Цвет для кнопки удаления */ z-index: 10; /* Чтобы кнопки были поверх содержимого узла */
cursor: pointer; display: flex;
font-size: 0.8em; flex-direction: column; /* Вертикальное расположение кнопок */
padding: 3px 6px; gap: 1px; /* Отступ между кнопками */
margin-top: 5px; background-color: transparent; /* Прозрачный фон */
transition: all 0.2s ease-in-out; padding: 0px; /* Нет внутреннего отступа */
pointer-events: none; /* Изначально игнорировать события мыши */
opacity: 0; /* Изначально скрыт */
transition: opacity 0.2s ease-in-out; /* Плавное появление/исчезновение */
} }
.react-flow__node button.node-delete-button:hover { /* Показать toolbar при наведении на узел */
background-color: var(--background-modifier-error-hover); .react-flow__node:hover .node-toolbar {
color: var(--text-on-accent); pointer-events: auto; /* Разрешить события мыши при наведении */
opacity: 1; /* Полностью показать */
}
/* Стили для отдельных кнопок действий внутри узла */
.react-flow__node .node-action-button {
background-color: var(--background-secondary-alt); /* Фон как у вторичных кнопок */
border: 1px solid var(--background-modifier-border); /* Граница */
border-radius: 4px; /* Скругленные углы */
padding: 4px; /* <-- ИЗМЕНЕНО: Отступ для соответствия новому размеру иконок */
height: 16px; /* <-- ИЗМЕНЕНО: Уменьшенная высота */
width: 16px; /* <-- ИЗМЕНЕНО: Уменьшенная ширина */
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--text-muted); /* Цвет текста/иконки */
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
box-shadow: var(--shadow-s); /* Небольшая тень */
flex-shrink: 0; /* Чтобы кнопки не сжимались */
}
.react-flow__node .node-action-button:hover {
background-color: var(--background-secondary); /* <-- ИЗМЕНЕНО: Более плотный фон при наведении */
color: var(--text-normal); /* <-- ИЗМЕНЕНО: Цвет иконки при наведении */
border-color: var(--background-modifier-border); /* <-- ИЗМЕНЕНО: Цвет границы при наведении */
}
/* Удаляем старые стили, которые конфликтуют или больше не нужны */
.react-flow__node button.node-delete-button,
.react-flow__node button.node-regenerate-button {
position: static; /* Сбрасываем абсолютное позиционирование */
bottom: auto; /* Сбрасываем позицию */
left: auto; /* Сбрасываем позицию */
margin-top: 0; /* Сбрасываем внешний отступ */
font-size: 1em; /* Размер шрифта, если используется текст */
}
/* Убеждаемся, что SVG иконка внутри кнопки получает цвет */
.react-flow__node .node-action-button svg {
width: 8px; /* <-- ИЗМЕНЕНО: Размер иконки уменьшен в 2 раза */
height: 8px; /* <-- ИЗМЕНЕНО: Размер иконки уменьшен в 2 раза */
stroke: currentColor; /* Использовать текущий цвет текста для иконки */
} }

View File

@ -323,8 +323,13 @@ export class GraphView extends ItemView {
} }
const data = await response.json(); const data = await response.json();
// Чтобы не раздувать отправляемые эндпоинтом данные
const nodesWithGraphId = data.graph_nodes ?
data.graph_nodes.map((node: any) => ({ ...node, graph_id: graphId })) :
[];
this.graphData = { this.graphData = {
nodes: data.graph_nodes || [], nodes: nodesWithGraphId,
edges: data.graph_edges || [] edges: data.graph_edges || []
}; };
this.currentNode = data.current_node_id; this.currentNode = data.current_node_id;

File diff suppressed because one or more lines are too long