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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -0,0 +1,4 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.13727 11.2201L6.27454 14.4398" stroke="var(--pin-color, var(--color-smoke-800, #8a8a8a))" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" />
<path d="M6.28085 6.68385C6.21652 6.92342 6.08664 7.1403 5.9058 7.31009C5.72497 7.47989 5.50035 7.59587 5.25721 7.645L3.95569 7.91742C3.71254 7.96655 3.48793 8.08253 3.30709 8.25233C3.12626 8.42212 2.99637 8.639 2.93204 8.87857L2.80091 9.36797C2.75515 9.53876 2.7791 9.72073 2.86751 9.87385C2.95591 10.027 3.10153 10.1387 3.27231 10.1845L10.9997 12.255C11.1705 12.3008 11.3525 12.2768 11.5056 12.1884C11.6587 12.1 11.7705 11.9544 11.8162 11.7836L11.9474 11.2942C12.0114 11.0546 12.0074 10.8018 11.9357 10.5643C11.864 10.3269 11.7274 10.1141 11.5414 9.95001L10.5505 9.06333C10.3645 8.89921 10.2279 8.68646 10.1562 8.44899C10.0845 8.21153 10.0805 7.95877 10.1446 7.71913L10.7933 5.29787C10.8391 5.12709 10.9508 4.98148 11.1039 4.89307C11.2571 4.80466 11.439 4.78071 11.6098 4.82647C11.9514 4.91799 12.3153 4.87008 12.6216 4.69327C12.9278 4.51646 13.1513 4.22523 13.2428 3.88366C13.3343 3.54209 13.2864 3.17815 13.1096 2.8719C12.9328 2.56566 12.6416 2.34219 12.3 2.25067L7.1484 0.870299C6.80683 0.778775 6.44289 0.826689 6.13665 1.0035C5.8304 1.18031 5.60694 1.47154 5.51541 1.81311C5.42389 2.15468 5.4718 2.51862 5.64861 2.82487C5.82542 3.13111 6.11665 3.35458 6.45822 3.4461C6.62901 3.49186 6.77462 3.6036 6.86302 3.75672C6.95143 3.90984 6.97539 4.09181 6.92962 4.2626L6.28085 6.68385Z" fill="var(--handle-color, var(--color-gold-600, #fd9903))" />
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

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')