mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
## Summary When the node library is open and you click on the node toolbar info button, this causes the node library info panel & right panel node info to show the same details. ## Changes - Extract useNodeHelpContent composable so NodeHelpContent fetches its own content, allowing multiple panels to show help independently - Remove sync behavior from NodeHelpPage that caused left sidebar to change when selecting different graph nodes since we want to prioritise right panel for this behavior - Add telemetry tracking for node library help button to identify how frequently this is used ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8110-Decouple-node-help-between-sidebar-and-right-panel-2ea6d73d365081a9b3afd25aa51b34bd) by [Unito](https://www.unito.io)
206 lines
6.0 KiB
Vue
206 lines
6.0 KiB
Vue
<template>
|
|
<div ref="container" class="node-lib-node-container">
|
|
<TreeExplorerTreeNode :node="node" @contextmenu="handleContextMenu">
|
|
<template #before-label>
|
|
<Tag
|
|
v-if="nodeDef.experimental"
|
|
:value="$t('g.experimental')"
|
|
severity="primary"
|
|
/>
|
|
<Tag
|
|
v-if="nodeDef.deprecated"
|
|
:value="$t('g.deprecated')"
|
|
severity="danger"
|
|
/>
|
|
</template>
|
|
<template
|
|
v-if="nodeDef.name.startsWith(useSubgraphStore().typePrefix)"
|
|
#actions
|
|
>
|
|
<Button
|
|
variant="destructive"
|
|
size="icon-sm"
|
|
:aria-label="$t('g.delete')"
|
|
@click.stop="deleteBlueprint"
|
|
>
|
|
<i class="icon-[lucide--trash-2] size-3.5" />
|
|
</Button>
|
|
<Button
|
|
variant="muted-textonly"
|
|
size="icon-sm"
|
|
:aria-label="$t('g.edit')"
|
|
@click.stop="editBlueprint"
|
|
>
|
|
<i class="icon-[lucide--square-pen] size-3.5" />
|
|
</Button>
|
|
</template>
|
|
<template v-else #actions>
|
|
<Button
|
|
class="bookmark-button"
|
|
variant="muted-textonly"
|
|
size="icon-sm"
|
|
:aria-label="$t('icon.bookmark')"
|
|
@click.stop="toggleBookmark"
|
|
>
|
|
<i
|
|
:class="
|
|
cn(
|
|
isBookmarked ? 'pi pi-bookmark-fill' : 'pi pi-bookmark',
|
|
'size-3.5'
|
|
)
|
|
"
|
|
/>
|
|
</Button>
|
|
<Button
|
|
v-tooltip.bottom="$t('g.learnMore')"
|
|
class="help-button"
|
|
variant="muted-textonly"
|
|
size="icon-sm"
|
|
:aria-label="$t('g.learnMore')"
|
|
@click.stop="onHelpClick"
|
|
>
|
|
<i class="pi pi-question size-3.5" />
|
|
</Button>
|
|
</template>
|
|
</TreeExplorerTreeNode>
|
|
|
|
<teleport v-if="isHovered" to="#node-library-node-preview-container">
|
|
<div class="node-lib-node-preview" :style="nodePreviewStyle">
|
|
<NodePreview :node-def="nodeDef" />
|
|
</div>
|
|
</teleport>
|
|
</div>
|
|
<ContextMenu ref="menu" :model="menuItems" />
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import ContextMenu from 'primevue/contextmenu'
|
|
import type { MenuItem } from 'primevue/menuitem'
|
|
import Tag from 'primevue/tag'
|
|
import type { CSSProperties } from 'vue'
|
|
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import TreeExplorerTreeNode from '@/components/common/TreeExplorerTreeNode.vue'
|
|
import NodePreview from '@/components/node/NodePreview.vue'
|
|
import Button from '@/components/ui/button/Button.vue'
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
import { useTelemetry } from '@/platform/telemetry'
|
|
import { useNodeBookmarkStore } from '@/stores/nodeBookmarkStore'
|
|
import type { ComfyNodeDefImpl } from '@/stores/nodeDefStore'
|
|
import { useSubgraphStore } from '@/stores/subgraphStore'
|
|
import type { RenderedTreeExplorerNode } from '@/types/treeExplorerTypes'
|
|
import { cn } from '@/utils/tailwindUtil'
|
|
|
|
const { t } = useI18n()
|
|
|
|
const props = defineProps<{
|
|
node: RenderedTreeExplorerNode<ComfyNodeDefImpl>
|
|
openNodeHelp: (nodeDef: ComfyNodeDefImpl) => void
|
|
}>()
|
|
|
|
// Note: node.data should be present for leaf nodes.
|
|
const nodeDef = computed(() => props.node.data!)
|
|
const nodeBookmarkStore = useNodeBookmarkStore()
|
|
const isBookmarked = computed(() =>
|
|
nodeBookmarkStore.isBookmarked(nodeDef.value)
|
|
)
|
|
const settingStore = useSettingStore()
|
|
const sidebarLocation = computed<'left' | 'right'>(() =>
|
|
settingStore.get('Comfy.Sidebar.Location')
|
|
)
|
|
|
|
const toggleBookmark = async () => {
|
|
await nodeBookmarkStore.toggleBookmark(nodeDef.value)
|
|
}
|
|
|
|
const onHelpClick = () => {
|
|
useTelemetry()?.trackUiButtonClicked({
|
|
button_id: 'node_library_help_button'
|
|
})
|
|
props.openNodeHelp(nodeDef.value)
|
|
}
|
|
const editBlueprint = async () => {
|
|
if (!props.node.data)
|
|
throw new Error(
|
|
'Failed to edit subgraph blueprint lacking backing node data'
|
|
)
|
|
await useSubgraphStore().editBlueprint(props.node.data.name)
|
|
}
|
|
const menu = ref<InstanceType<typeof ContextMenu> | null>(null)
|
|
const menuItems = computed<MenuItem[]>(() => {
|
|
const items: MenuItem[] = [
|
|
{
|
|
label: t('g.delete'),
|
|
icon: 'pi pi-trash',
|
|
severity: 'error',
|
|
command: deleteBlueprint
|
|
}
|
|
]
|
|
return items
|
|
})
|
|
function handleContextMenu(event: Event) {
|
|
if (!nodeDef.value.name.startsWith(useSubgraphStore().typePrefix)) return
|
|
menu.value?.show(event)
|
|
}
|
|
function deleteBlueprint() {
|
|
if (!props.node.data) return
|
|
void useSubgraphStore().deleteBlueprint(props.node.data.name)
|
|
}
|
|
|
|
const nodePreviewStyle = ref<CSSProperties>({
|
|
position: 'fixed',
|
|
top: '0px',
|
|
left: '0px',
|
|
pointerEvents: 'none',
|
|
zIndex: 1001
|
|
})
|
|
|
|
const handleNodeHover = async () => {
|
|
const hoverTarget = nodeContentElement.value
|
|
if (!hoverTarget) return
|
|
|
|
const targetRect = hoverTarget.getBoundingClientRect()
|
|
const margin = 40
|
|
|
|
nodePreviewStyle.value.top = `${targetRect.top}px`
|
|
nodePreviewStyle.value.left =
|
|
sidebarLocation.value === 'left'
|
|
? `${targetRect.right + margin}px`
|
|
: `${targetRect.left - margin}px`
|
|
nodePreviewStyle.value.transform =
|
|
sidebarLocation.value === 'right' ? 'translateX(-100%)' : undefined
|
|
}
|
|
|
|
const container = ref<HTMLElement | null>(null)
|
|
const nodeContentElement = ref<HTMLElement | null>(null)
|
|
const isHovered = ref(false)
|
|
const handleMouseEnter = async () => {
|
|
isHovered.value = true
|
|
await nextTick()
|
|
await handleNodeHover()
|
|
}
|
|
const handleMouseLeave = () => {
|
|
isHovered.value = false
|
|
}
|
|
onMounted(() => {
|
|
nodeContentElement.value =
|
|
container.value?.closest('.p-tree-node-content') ?? null
|
|
nodeContentElement.value?.addEventListener('mouseenter', handleMouseEnter)
|
|
nodeContentElement.value?.addEventListener('mouseleave', handleMouseLeave)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
nodeContentElement.value?.removeEventListener('mouseenter', handleMouseEnter)
|
|
nodeContentElement.value?.removeEventListener('mouseleave', handleMouseLeave)
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
@reference '../../../../assets/css/style.css';
|
|
|
|
.node-lib-node-container {
|
|
@apply h-full w-full;
|
|
}
|
|
</style>
|