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

View File

@ -2,6 +2,8 @@
import { computed, ref, nextTick, watch } from 'vue'
import { BaseEdge, EdgeLabelRenderer, getBezierPath, getSmoothStepPath, MarkerType } from '@vue-flow/core'
import { useVueFlow } from '@vue-flow/core'
import RichTooltip from './RichTooltip.vue'
import { getEdgeHelp } from '../utils/helpContent.js'
const { findNode } = useVueFlow()
@ -562,6 +564,8 @@ const labelStyle = computed(() => {
return s
})
const edgeHelpContent = computed(() => getEdgeHelp(props.data))
</script>
<template>
@ -614,6 +618,23 @@ const labelStyle = computed(() => {
:animated="false"
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">
<div
:key="edgeLabelKey"
@ -649,4 +670,13 @@ const labelStyle = computed(() => {
.animated-label {
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>

View File

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

View File

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