Better rendering of buttons for node on graph
This commit is contained in:
parent
05fbd5a9f4
commit
37f9986b02
|
|
@ -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,39 +120,30 @@ const BaseNodeWrapper: React.FC<BaseNodeWrapperProps> = ({ data, isConnectable,
|
||||||
isConnectable: isConnectable
|
isConnectable: isConnectable
|
||||||
}),
|
}),
|
||||||
children, // Здесь будет отображаться специфическое содержимое узла
|
children, // Здесь будет отображаться специфическое содержимое узла
|
||||||
data.isSelected &&
|
React.createElement(
|
||||||
|
'div',
|
||||||
|
{
|
||||||
|
className: 'node-toolbar' // Применяем класс для тулбара
|
||||||
|
},
|
||||||
React.createElement(
|
React.createElement(
|
||||||
'div',
|
'button',
|
||||||
{
|
{
|
||||||
style: {
|
onClick: handleNodeDelete,
|
||||||
position: 'absolute',
|
'aria-label': 'Удалить узел',
|
||||||
bottom: '-30px',
|
className: 'node-action-button node-delete-button' // Применяем классы для кнопки
|
||||||
left: '5px',
|
|
||||||
display: 'flex',
|
|
||||||
gap: '5px'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
React.createElement(
|
React.createElement('span', { ref: deleteIconRef }) // Элемент для иконки удаления
|
||||||
'button',
|
),
|
||||||
{
|
React.createElement(
|
||||||
style: deleteButtonStyle,
|
'button',
|
||||||
onClick: handleNodeDelete,
|
{
|
||||||
'aria-label': 'Удалить узел',
|
onClick: handleRegenerate,
|
||||||
className: 'node-delete-button'
|
'aria-label': 'Регенерировать',
|
||||||
},
|
className: 'node-action-button node-regenerate-button' // Применяем классы для кнопки
|
||||||
'🗑️'
|
},
|
||||||
),
|
React.createElement('span', { ref: regenerateIconRef }) // Элемент для иконки регенерации
|
||||||
React.createElement(
|
|
||||||
'button',
|
|
||||||
{
|
|
||||||
style: deleteButtonStyle,
|
|
||||||
onClick: handleRegenerate,
|
|
||||||
'aria-label': 'Регенерировать',
|
|
||||||
className: 'node-regenerate-button'
|
|
||||||
},
|
|
||||||
'🔄'
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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; /* Использовать текущий цвет текста для иконки */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
62
styles.css
62
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user