mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-19 22:34:15 +00:00
feat: add visual indicator for list output slots (#8766)
## Summary Add rounded square dot shape and "(Iterative)" tooltip for list-type output slots in Vue nodes, matching litegraph's visual indicator. ## Changes - **What**: `SlotConnectionDot.vue` renders `rounded-[1px]` instead of `rounded-full` when slot shape is `RenderShape.GRID`. `OutputSlot.vue` appends "(Iterative)" to the tooltip for these slots. <img width="807" height="542" alt="Screenshot 2026-02-10 at 03 38 42" src="https://github.com/user-attachments/assets/137b60c5-ac3b-457f-a52d-58f5f28a59ea" /> ## Review Focus - i18n key added for the iterative suffix ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8766-feat-add-visual-indicator-for-list-output-slots-3036d73d3650813aad85ce094d29c42b) by [Unito](https://www.unito.io)
This commit is contained in:
committed by
GitHub
parent
da56c9e554
commit
7f30d6b6a5
@@ -2834,6 +2834,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"vueNodesSlot": {
|
||||
"iterative": "(Iterative)"
|
||||
},
|
||||
"vueNodesBanner": {
|
||||
"title": "Introducing Nodes 2.0",
|
||||
"desc": "– More flexible workflows, powerful new widgets, built for extensibility",
|
||||
|
||||
@@ -23,9 +23,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onErrorCaptured, ref, watchEffect } from 'vue'
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { useErrorHandling } from '@/composables/useErrorHandling'
|
||||
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
|
||||
import { RenderShape } from '@/lib/litegraph/src/types/globalEnums'
|
||||
import { useSlotLinkDragUIState } from '@/renderer/core/canvas/links/slotLinkDragUIState'
|
||||
import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
|
||||
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
|
||||
@@ -47,6 +49,8 @@ interface OutputSlotProps {
|
||||
|
||||
const props = defineProps<OutputSlotProps>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const hasNoLabel = computed(
|
||||
() => !props.slotData.localized_name && props.slotData.name === ''
|
||||
)
|
||||
@@ -65,7 +69,11 @@ const tooltipConfig = computed(() => {
|
||||
const slotName = props.slotData.name || ''
|
||||
const tooltipText = getOutputSlotTooltip(props.index)
|
||||
const fallbackText = tooltipText || `Output: ${slotName}`
|
||||
return createTooltipConfig(fallbackText)
|
||||
const iterativeSuffix =
|
||||
props.slotData.shape === RenderShape.GRID
|
||||
? ` ${t('vueNodesSlot.iterative')}`
|
||||
: ''
|
||||
return createTooltipConfig(fallbackText + iterativeSuffix)
|
||||
})
|
||||
|
||||
onErrorCaptured((error) => {
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
|
||||
import { RenderShape } from '@/lib/litegraph/src/types/globalEnums'
|
||||
|
||||
import SlotConnectionDot from './SlotConnectionDot.vue'
|
||||
|
||||
const defaultSlot: INodeSlot = {
|
||||
name: 'output',
|
||||
type: 'IMAGE',
|
||||
boundingRect: [0, 0, 0, 0]
|
||||
}
|
||||
|
||||
function mountDot(slotData?: INodeSlot) {
|
||||
return mount(SlotConnectionDot, {
|
||||
props: { slotData }
|
||||
})
|
||||
}
|
||||
|
||||
describe('SlotConnectionDot', () => {
|
||||
it('renders circle shape by default', () => {
|
||||
const wrapper = mountDot(defaultSlot)
|
||||
|
||||
const dot = wrapper.find('.slot-dot')
|
||||
expect(dot.classes()).toContain('rounded-full')
|
||||
expect(dot.element.tagName).toBe('DIV')
|
||||
})
|
||||
|
||||
it('renders rounded square for GRID shape', () => {
|
||||
const wrapper = mountDot({
|
||||
...defaultSlot,
|
||||
shape: RenderShape.GRID
|
||||
})
|
||||
|
||||
const dot = wrapper.find('.slot-dot')
|
||||
expect(dot.classes()).toContain('rounded-[1px]')
|
||||
expect(dot.classes()).not.toContain('rounded-full')
|
||||
expect(dot.element.tagName).toBe('DIV')
|
||||
})
|
||||
})
|
||||
@@ -3,6 +3,7 @@ import { computed, useTemplateRef } from 'vue'
|
||||
|
||||
import { getSlotColor } from '@/constants/slotColors'
|
||||
import type { INodeSlot } from '@/lib/litegraph/src/litegraph'
|
||||
import { RenderShape } from '@/lib/litegraph/src/types/globalEnums'
|
||||
import { cn } from '@/utils/tailwindUtil'
|
||||
import type { ClassValue } from '@/utils/tailwindUtil'
|
||||
|
||||
@@ -41,9 +42,12 @@ defineExpose({
|
||||
slotElRef
|
||||
})
|
||||
|
||||
const isListShape = computed(() => props.slotData?.shape === RenderShape.GRID)
|
||||
|
||||
const slotClass = computed(() =>
|
||||
cn(
|
||||
'bg-slate-300 rounded-full slot-dot',
|
||||
'bg-slate-300 slot-dot',
|
||||
isListShape.value ? 'rounded-[1px]' : 'rounded-full',
|
||||
'transition-all duration-150',
|
||||
'border border-solid border-node-component-slot-dot-outline',
|
||||
props.multi
|
||||
@@ -63,7 +67,7 @@ const slotClass = computed(() =>
|
||||
"
|
||||
>
|
||||
<div
|
||||
v-if="types.length === 1 && slotData?.shape == undefined"
|
||||
v-if="types.length === 1 && (slotData?.shape == undefined || isListShape)"
|
||||
ref="slot-el"
|
||||
:style="{ backgroundColor: types.length === 1 ? types[0] : undefined }"
|
||||
:class="slotClass"
|
||||
|
||||
Reference in New Issue
Block a user