feat: wrapping nodes with tooltip content

This commit is contained in:
laansdole 2026-02-08 10:59:22 +07:00
parent b3b7376ef2
commit 53870bd6f0
4 changed files with 140 additions and 74 deletions

View File

@ -1,6 +1,8 @@
<script setup> <script setup>
import { computed } from 'vue' import { computed } from 'vue'
import { Handle, Position } from '@vue-flow/core' import { Handle, Position } from '@vue-flow/core'
import RichTooltip from './RichTooltip.vue'
import { helpContent } from '../utils/helpContent.js'
const props = defineProps({ const props = defineProps({
id: { id: {
@ -15,11 +17,13 @@ const props = defineProps({
</script> </script>
<template> <template>
<div class="start-node" :style="{ opacity: data.opacity ?? 1 }"> <RichTooltip :content="helpContent.startNode" placement="right">
<div class="start-node-bubble" title="Start Node"></div> <div class="start-node" :style="{ opacity: data.opacity ?? 1 }">
<!-- Provide source handle at right --> <div class="start-node-bubble" title="Start Node"></div>
<Handle id="source" type="source" :position="Position.Right" class="start-node-handle" /> <!-- Provide source handle at right -->
</div> <Handle id="source" type="source" :position="Position.Right" class="start-node-handle" />
</div>
</RichTooltip>
</template> </template>
<style scoped> <style scoped>

View File

@ -2,6 +2,8 @@
import { computed, ref, nextTick, watch } from 'vue' import { computed, ref, nextTick, watch } from 'vue'
import { BaseEdge, EdgeLabelRenderer, getBezierPath, getSmoothStepPath, MarkerType } from '@vue-flow/core' import { BaseEdge, EdgeLabelRenderer, getBezierPath, getSmoothStepPath, MarkerType } from '@vue-flow/core'
import { useVueFlow } from '@vue-flow/core' import { useVueFlow } from '@vue-flow/core'
import RichTooltip from './RichTooltip.vue'
import { getEdgeHelp } from '../utils/helpContent.js'
const { findNode } = useVueFlow() const { findNode } = useVueFlow()
@ -562,6 +564,8 @@ const labelStyle = computed(() => {
return s return s
}) })
const edgeHelpContent = computed(() => getEdgeHelp(props.data))
</script> </script>
<template> <template>
@ -614,6 +618,23 @@ const labelStyle = computed(() => {
:animated="false" :animated="false"
class="nodrag nopan" class="nodrag nopan"
/> />
<!-- Tooltip-enabled hover area at edge midpoint -->
<EdgeLabelRenderer>
<RichTooltip :content="edgeHelpContent" placement="top">
<div
:style="{
position: 'absolute',
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
pointerEvents: 'all',
width: '20px',
height: '20px',
borderRadius: '50%',
cursor: 'pointer'
}"
class="edge-tooltip-trigger"
/>
</RichTooltip>
</EdgeLabelRenderer>
<EdgeLabelRenderer v-if="edgeLabel"> <EdgeLabelRenderer v-if="edgeLabel">
<div <div
:key="edgeLabelKey" :key="edgeLabelKey"
@ -649,4 +670,13 @@ const labelStyle = computed(() => {
.animated-label { .animated-label {
animation: label-pulse var(--label-anim-duration) infinite linear; animation: label-pulse var(--label-anim-duration) infinite linear;
} }
.edge-tooltip-trigger {
background: transparent;
transition: background-color 0.2s;
}
.edge-tooltip-trigger:hover {
background-color: rgba(255, 255, 255, 0.1);
}
</style> </style>

View File

@ -3,6 +3,8 @@ import { computed, ref, onMounted, onUnmounted, watch } from 'vue'
import { Handle, Position } from '@vue-flow/core' import { Handle, Position } from '@vue-flow/core'
import { getNodeStyles } from '../utils/colorUtils.js' import { getNodeStyles } from '../utils/colorUtils.js'
import { spriteFetcher } from '../utils/spriteFetcher.js' import { spriteFetcher } from '../utils/spriteFetcher.js'
import RichTooltip from './RichTooltip.vue'
import { getNodeHelp } from '../utils/helpContent.js'
const props = defineProps({ const props = defineProps({
id: { id: {
@ -37,6 +39,8 @@ const nodeDescription = computed(() => props.data?.description || '')
const isActive = computed(() => props.isActive) const isActive = computed(() => props.isActive)
const dynamicStyles = computed(() => getNodeStyles(nodeType.value)) const dynamicStyles = computed(() => getNodeStyles(nodeType.value))
const nodeHelpContent = computed(() => getNodeHelp(nodeType.value))
// Compute the current sprite path based on active state and walking frame // Compute the current sprite path based on active state and walking frame
const currentSprite = computed(() => { const currentSprite = computed(() => {
if (!props.sprite) return '' if (!props.sprite) return ''
@ -83,40 +87,42 @@ onUnmounted(() => {
</script> </script>
<template> <template>
<div class="workflow-node-container"> <RichTooltip :content="nodeHelpContent" placement="top">
<div v-if="props.sprite" class="workflow-node-sprite"> <div class="workflow-node-container">
<img :src="currentSprite" :alt="`${nodeId} sprite`" class="node-sprite-image" /> <div v-if="props.sprite" class="workflow-node-sprite">
</div> <img :src="currentSprite" :alt="`${nodeId} sprite`" class="node-sprite-image" />
<div
class="workflow-node"
:class="{ 'workflow-node-active': isActive }"
:data-type="nodeType"
:style="dynamicStyles"
@mouseenter="$emit('hover', nodeId)"
@mouseleave="$emit('leave', nodeId)"
>
<div class="workflow-node-header">
<span class="workflow-node-type">{{ nodeType }}</span>
<span class="workflow-node-id">{{ nodeId }}</span>
</div>
<div v-if="nodeDescription" class="workflow-node-description">
{{ nodeDescription }}
</div> </div>
<div
class="workflow-node"
:class="{ 'workflow-node-active': isActive }"
:data-type="nodeType"
:style="dynamicStyles"
@mouseenter="$emit('hover', nodeId)"
@mouseleave="$emit('leave', nodeId)"
>
<div class="workflow-node-header">
<span class="workflow-node-type">{{ nodeType }}</span>
<span class="workflow-node-id">{{ nodeId }}</span>
</div>
<div v-if="nodeDescription" class="workflow-node-description">
{{ nodeDescription }}
</div>
<Handle <Handle
id="source" id="source"
type="source" type="source"
:position="Position.Right" :position="Position.Right"
class="workflow-node-handle" class="workflow-node-handle"
/> />
<Handle <Handle
id="target" id="target"
type="target" type="target"
:position="Position.Left" :position="Position.Left"
class="workflow-node-handle" class="workflow-node-handle"
/> />
</div>
</div> </div>
</div> </RichTooltip>
</template> </template>
<style scoped> <style scoped>

View File

@ -87,38 +87,46 @@
> >
<!-- Pane context menu --> <!-- Pane context menu -->
<template v-if="contextMenuType === 'pane'"> <template v-if="contextMenuType === 'pane'">
<div <RichTooltip :content="helpContent.contextMenu.createNode" placement="right">
class="context-menu-item" <div
@click.stop="() => { hideContextMenu(); openCreateNodeModal(); }" class="context-menu-item"
> @click.stop="() => { hideContextMenu(); openCreateNodeModal(); }"
Create Node >
</div> Create Node
</div>
</RichTooltip>
</template> </template>
<!-- Node context menu --> <!-- Node context menu -->
<template v-else-if="contextMenuType === 'node'"> <template v-else-if="contextMenuType === 'node'">
<div <RichTooltip :content="helpContent.contextMenu.copyNode" placement="right">
class="context-menu-item" <div
@click.stop="() => { hideContextMenu(); onCopyNodeFromContext(); }" class="context-menu-item"
> @click.stop="() => { hideContextMenu(); onCopyNodeFromContext(); }"
Copy Node >
</div> Copy Node
<div </div>
class="context-menu-item" </RichTooltip>
@click.stop="() => { hideContextMenu(); onDeleteNodeFromContext(); }" <RichTooltip :content="helpContent.contextMenu.deleteNode" placement="right">
> <div
Delete Node class="context-menu-item"
</div> @click.stop="() => { hideContextMenu(); onDeleteNodeFromContext(); }"
>
Delete Node
</div>
</RichTooltip>
</template> </template>
<!-- Edge context menu --> <!-- Edge context menu -->
<template v-else-if="contextMenuType === 'edge'"> <template v-else-if="contextMenuType === 'edge'">
<div <RichTooltip :content="helpContent.contextMenu.deleteEdge" placement="right">
class="context-menu-item" <div
@click.stop="() => { hideContextMenu(); onDeleteEdgeFromContext(); }" class="context-menu-item"
> @click.stop="() => { hideContextMenu(); onDeleteEdgeFromContext(); }"
Delete Edge >
</div> Delete Edge
</div>
</RichTooltip>
</template> </template>
</div> </div>
</transition> </transition>
@ -141,15 +149,21 @@
</button> </button>
</div> </div>
<div v-if="activeTab === 'graph'" class="editor-actions"> <div v-if="activeTab === 'graph'" class="editor-actions">
<button @click="openCreateNodeModal" class="glass-button"> <RichTooltip :content="helpContent.contextMenu.createNodeButton" placement="bottom">
<span>Create Node</span> <button @click="openCreateNodeModal" class="glass-button">
</button> <span>Create Node</span>
<button @click="openConfigureGraphModal" class="glass-button"> </button>
<span>Configure Graph</span> </RichTooltip>
</button> <RichTooltip :content="helpContent.contextMenu.configureGraph" placement="bottom">
<button @click="goToLaunch" class="launch-button-primary"> <button @click="openConfigureGraphModal" class="glass-button">
<span>Launch</span> <span>Configure Graph</span>
</button> </button>
</RichTooltip>
<RichTooltip :content="helpContent.contextMenu.launch" placement="bottom">
<button @click="goToLaunch" class="launch-button-primary">
<span>Launch</span>
</button>
</RichTooltip>
<div <div
class="menu-container" class="menu-container"
@ -166,11 +180,21 @@
</div> </div>
<transition name="fade"> <transition name="fade">
<div v-if="showMenu" class="menu-dropdown"> <div v-if="showMenu" class="menu-dropdown">
<div @click="openRenameWorkflowModal" class="menu-item">Rename Workflow</div> <RichTooltip :content="helpContent.contextMenu.renameWorkflow" placement="left">
<div @click="openCopyWorkflowModal" class="menu-item">Copy Workflow</div> <div @click="openRenameWorkflowModal" class="menu-item">Rename Workflow</div>
<div @click="openManageVarsModal" class="menu-item">Manage Variables</div> </RichTooltip>
<div @click="openManageMemoriesModal" class="menu-item">Manage Memories</div> <RichTooltip :content="helpContent.contextMenu.copyWorkflow" placement="left">
<div @click="openCreateEdgeModal" class="menu-item">Create Edge</div> <div @click="openCopyWorkflowModal" class="menu-item">Copy Workflow</div>
</RichTooltip>
<RichTooltip :content="helpContent.contextMenu.manageVariables" placement="left">
<div @click="openManageVarsModal" class="menu-item">Manage Variables</div>
</RichTooltip>
<RichTooltip :content="helpContent.contextMenu.manageMemories" placement="left">
<div @click="openManageMemoriesModal" class="menu-item">Manage Memories</div>
</RichTooltip>
<RichTooltip :content="helpContent.contextMenu.createEdge" placement="left">
<div @click="openCreateEdgeModal" class="menu-item">Create Edge</div>
</RichTooltip>
</div> </div>
</transition> </transition>
</div> </div>
@ -270,8 +294,10 @@ import WorkflowNode from '../components/WorkflowNode.vue'
import WorkflowEdge from '../components/WorkflowEdge.vue' import WorkflowEdge from '../components/WorkflowEdge.vue'
import StartNode from '../components/StartNode.vue' import StartNode from '../components/StartNode.vue'
import FormGenerator from '../components/FormGenerator.vue' import FormGenerator from '../components/FormGenerator.vue'
import RichTooltip from '../components/RichTooltip.vue'
import yaml from 'js-yaml' import yaml from 'js-yaml'
import { fetchYaml, fetchVueGraph, postVuegraphs, updateYaml, postYamlNameChange, postYamlCopy } from '../utils/apiFunctions' import { fetchYaml, fetchVueGraph, postVuegraphs, updateYaml, postYamlNameChange, postYamlCopy } from '../utils/apiFunctions'
import { helpContent } from '../utils/helpContent.js'
const props = defineProps({ const props = defineProps({
workflowName: { workflowName: {