Style: Pinned, Muted, Bypassed header indicators (#6530)

## Summary

Match (mostly) the header design for Modern Nodes' statuses.

## Changes

- **What**: Styling to match designs for more states.

## Screenshot

<img width="591" height="687" alt="image"
src="https://github.com/user-attachments/assets/bf8fe5d1-bd42-455c-ab20-668632ab5049"
/>

<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6530-WIP-Pinned-and-Muted-styles-29f6d73d36508191bb89dc13674db8ba)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Alexander Brown
2025-11-03 18:14:38 -08:00
committed by GitHub
parent 4cfa5b4b5d
commit eae93c2a79
10 changed files with 67 additions and 30 deletions

View File

@@ -11,7 +11,7 @@
@click="toggleBypass"
>
<template #icon>
<i class="icon-[lucide--ban] h-4 w-4" />
<i class="icon-[lucide--redo-dot] h-4 w-4" />
</template>
</Button>
</template>

View File

@@ -140,7 +140,7 @@ import { useI18n } from 'vue-i18n'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { toggleNodeOptions } from '@/composables/graph/useMoreOptionsMenu'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { LGraphEventMode, LiteGraph } from '@/lib/litegraph/src/litegraph'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useTelemetry } from '@/platform/telemetry'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
@@ -222,8 +222,10 @@ const hasAnyError = computed((): boolean => {
})
const isCollapsed = computed(() => nodeData.flags?.collapsed ?? false)
const bypassed = computed((): boolean => nodeData.mode === 4)
const muted = computed((): boolean => nodeData.mode === 2) // NEVER mode
const bypassed = computed(
(): boolean => nodeData.mode === LGraphEventMode.BYPASS
)
const muted = computed((): boolean => nodeData.mode === LGraphEventMode.NEVER)
const nodeBodyBackgroundColor = computed(() => {
const colorPaletteStore = useColorPaletteStore()

View File

@@ -1,19 +1,31 @@
<template>
<div
v-if="badge.icon || badge.text"
class="min-w-max rounded-sm bg-node-component-surface px-1 py-0.5 text-xs"
v-if="text"
class="min-w-max rounded-sm bg-node-component-surface px-1 py-0.5 text-xs flex items-center gap-1"
:style="{
color: badge.fgColor,
backgroundColor: badge.bgColor
color: fgColor,
backgroundColor: bgColor
}"
>
{{ badge.text }}
{{ text }}
<i v-if="cssIcon" :class="cn(cssIcon)" />
</div>
</template>
<script setup lang="ts">
import type { LGraphBadge } from '@/lib/litegraph/src/LGraphBadge'
import { cn } from '@/utils/tailwindUtil'
const { badge } = defineProps<{
badge: LGraphBadge
}>()
export interface NodeBadgeProps {
text: LGraphBadge['text']
fgColor?: LGraphBadge['fgColor']
bgColor?: LGraphBadge['bgColor']
cssIcon?: string
}
const {
text,
fgColor = 'currentColor',
bgColor = '',
cssIcon
} = defineProps<NodeBadgeProps>()
</script>

View File

@@ -54,17 +54,22 @@
@edit="handleTitleEdit"
@cancel="handleTitleCancel"
/>
<i
v-if="isPinned"
class="icon-[lucide--pin] size-5 text-node-component-header-icon"
data-testid="node-pin-indicator"
/>
</div>
<LODFallback />
</div>
<div class="lod-toggle flex shrink-0 items-center justify-between gap-2">
<NodeBadge v-for="badge of nodeBadges" :key="badge.text" :badge />
<NodeBadge
v-for="badge of nodeBadges"
:key="badge.text"
v-bind="badge"
/>
<NodeBadge v-if="statusBadge" v-bind="statusBadge" />
<i-comfy:pin
v-if="isPinned"
class="size-5"
data-testid="node-pin-indicator"
/>
<IconButton
v-if="isSubgraphNode"
v-tooltip.top="enterSubgraphTooltipConfig"
@@ -91,7 +96,7 @@ import EditableText from '@/components/common/EditableText.vue'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { st } from '@/i18n'
import type { LGraphBadge } from '@/lib/litegraph/src/LGraphBadge'
import { LGraphEventMode } from '@/lib/litegraph/src/litegraph'
import { useSettingStore } from '@/platform/settings/settingStore'
import NodeBadge from '@/renderer/extensions/vueNodes/components/NodeBadge.vue'
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
@@ -106,6 +111,7 @@ import {
import { cn } from '@/utils/tailwindUtil'
import LODFallback from './LODFallback.vue'
import type { NodeBadgeProps } from './NodeBadge.vue'
interface NodeHeaderProps {
nodeData?: VueNodeData
@@ -178,19 +184,21 @@ const resolveTitle = (info: VueNodeData | undefined) => {
// Local state for title to provide immediate feedback
const displayTitle = ref(resolveTitle(nodeData))
// Watch for external changes to the node title or type
watch(
() => [nodeData?.title, nodeData?.type] as const,
() => {
const next = resolveTitle(nodeData)
if (next !== displayTitle.value) {
displayTitle.value = next
}
}
const bypassed = computed(
(): boolean => nodeData?.mode === LGraphEventMode.BYPASS
)
const muted = computed((): boolean => nodeData?.mode === LGraphEventMode.NEVER)
const statusBadge = computed((): NodeBadgeProps | undefined =>
muted.value
? { text: 'Muted', cssIcon: 'icon-[lucide--ban]' }
: bypassed.value
? { text: 'Bypassed', cssIcon: 'icon-[lucide--redo-dot]' }
: undefined
)
const nodeBadges = computed<LGraphBadge[]>(() =>
(nodeData?.badges ?? []).map(toValue)
const nodeBadges = computed<NodeBadgeProps[]>(() =>
[...(nodeData?.badges ?? [])].map(toValue)
)
const isPinned = computed(() => Boolean(nodeData?.flags?.pinned))
const isApiNode = computed(() => Boolean(nodeData?.apiNode))
@@ -210,6 +218,17 @@ const isSubgraphNode = computed(() => {
return litegraphNode?.isSubgraphNode() ?? false
})
// Watch for external changes to the node title or type
watch(
() => [nodeData?.title, nodeData?.type] as const,
() => {
const next = resolveTitle(nodeData)
if (next !== displayTitle.value) {
displayTitle.value = next
}
}
)
// Event handlers
const handleCollapse = () => {
emit('collapse')