mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-26 17:30:07 +00:00
Cleanup: YAGNI readonly props, private swap on ComfyApp, Canvas resize events simplification, v-memos on individual instances (#5869)
## Summary Assorted cleanup opportunities found while working through the Vue node rendering logic cleanup. ## Review Focus Am I wrong that the readonly logic was never actually executing because it was defined as False in GraphCanvas when making each LGraphNode? Is there an edge case or some other reason that the ResizeObserver wouldn't work as a single signal to resize the canvas? ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5869-Cleanup-YAGNI-readonly-props-private-swap-on-ComfyApp-Canvas-resize-events-simplificat-27e6d73d3650811ba1dcf29e8d43091e) by [Unito](https://www.unito.io) --------- Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
ref="connectionDotRef"
|
||||
:color="slotColor"
|
||||
:class="cn('-translate-x-1/2', errorClassesDot)"
|
||||
v-on="readonly ? {} : { pointerdown: onPointerDown }"
|
||||
@pointerdown="onPointerDown"
|
||||
/>
|
||||
|
||||
<!-- Slot Name -->
|
||||
@@ -54,7 +54,6 @@ interface InputSlotProps {
|
||||
index: number
|
||||
connected?: boolean
|
||||
compatible?: boolean
|
||||
readonly?: boolean
|
||||
dotOnly?: boolean
|
||||
}
|
||||
|
||||
@@ -117,7 +116,7 @@ const slotColor = computed(() => {
|
||||
const slotWrapperClass = computed(() =>
|
||||
cn(
|
||||
'lg-slot lg-slot--input flex items-center group rounded-r-lg h-6',
|
||||
props.readonly ? 'cursor-default opacity-70' : 'cursor-crosshair',
|
||||
'cursor-crosshair',
|
||||
props.dotOnly
|
||||
? 'lg-slot--dot-only'
|
||||
: 'pr-6 hover:bg-black/5 hover:dark:bg-white/5',
|
||||
@@ -148,7 +147,6 @@ useSlotElementTracking({
|
||||
const { onPointerDown } = useSlotLinkInteraction({
|
||||
nodeId: props.nodeId ?? '',
|
||||
index: props.index,
|
||||
type: 'input',
|
||||
readonly: props.readonly
|
||||
type: 'input'
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -50,15 +50,7 @@
|
||||
<SlotConnectionDot multi class="absolute right-0 translate-x-1/2" />
|
||||
</template>
|
||||
<NodeHeader
|
||||
v-memo="[
|
||||
nodeData.title,
|
||||
nodeData.color,
|
||||
nodeData.bgcolor,
|
||||
isCollapsed,
|
||||
nodeData.flags?.pinned
|
||||
]"
|
||||
:node-data="nodeData"
|
||||
:readonly="readonly"
|
||||
:collapsed="isCollapsed"
|
||||
@collapse="handleCollapse"
|
||||
@update:title="handleHeaderTitleUpdate"
|
||||
@@ -100,37 +92,19 @@
|
||||
:data-testid="`node-body-${nodeData.id}`"
|
||||
>
|
||||
<!-- Slots only rendered at full detail -->
|
||||
<NodeSlots
|
||||
v-memo="[
|
||||
nodeData.inputs?.length,
|
||||
nodeData.outputs?.length,
|
||||
executionStore.lastNodeErrors
|
||||
]"
|
||||
:node-data="nodeData"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
<NodeSlots :node-data="nodeData" />
|
||||
|
||||
<!-- Widgets rendered at reduced+ detail -->
|
||||
<NodeWidgets
|
||||
v-if="nodeData.widgets?.length"
|
||||
v-memo="[nodeData.widgets?.length]"
|
||||
:node-data="nodeData"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
<NodeWidgets v-if="nodeData.widgets?.length" :node-data="nodeData" />
|
||||
|
||||
<!-- Custom content at reduced+ detail -->
|
||||
<NodeContent
|
||||
v-if="hasCustomContent"
|
||||
:node-data="nodeData"
|
||||
:readonly="readonly"
|
||||
:image-urls="nodeImageUrls"
|
||||
/>
|
||||
<!-- Live preview image -->
|
||||
<div
|
||||
v-if="shouldShowPreviewImg"
|
||||
v-memo="[latestPreviewUrl]"
|
||||
class="px-4"
|
||||
>
|
||||
<div v-if="shouldShowPreviewImg" class="px-4">
|
||||
<img
|
||||
:src="latestPreviewUrl"
|
||||
alt="preview"
|
||||
@@ -180,16 +154,11 @@ import SlotConnectionDot from './SlotConnectionDot.vue'
|
||||
// Extended props for main node component
|
||||
interface LGraphNodeProps {
|
||||
nodeData: VueNodeData
|
||||
readonly?: boolean
|
||||
error?: string | null
|
||||
zoomLevel?: number
|
||||
}
|
||||
|
||||
const {
|
||||
nodeData,
|
||||
error = null,
|
||||
readonly = false
|
||||
} = defineProps<LGraphNodeProps>()
|
||||
const { nodeData, error = null } = defineProps<LGraphNodeProps>()
|
||||
|
||||
const {
|
||||
handleNodeCollapse,
|
||||
|
||||
@@ -27,7 +27,6 @@ import ImagePreview from './ImagePreview.vue'
|
||||
interface NodeContentProps {
|
||||
node?: LGraphNode // For backwards compatibility
|
||||
nodeData?: VueNodeData // New clean data structure
|
||||
readonly?: boolean
|
||||
imageUrls?: string[]
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,6 @@ const mountHeader = (
|
||||
...config,
|
||||
props: {
|
||||
nodeData: makeNodeData(),
|
||||
readonly: false,
|
||||
collapsed: false,
|
||||
...props
|
||||
}
|
||||
@@ -182,20 +181,6 @@ describe('NodeHeader.vue', () => {
|
||||
expect(wrapper.get('[data-testid="node-title"]').text()).toContain('KeepMe')
|
||||
})
|
||||
|
||||
it('honors readonly: hides collapse button and prevents editing', async () => {
|
||||
const wrapper = mountHeader({ readonly: true })
|
||||
|
||||
// Collapse button should be hidden
|
||||
const btn = wrapper.find('[data-testid="node-collapse-button"]')
|
||||
expect(btn.exists()).toBe(true)
|
||||
// v-show hides via display:none
|
||||
expect((btn.element as HTMLButtonElement).style.display).toBe('none')
|
||||
// In unit test, presence is fine; simulate double click should not create input
|
||||
await wrapper.get('[data-testid="node-header-1"]').trigger('dblclick')
|
||||
const input = wrapper.find('[data-testid="node-title-input"]')
|
||||
expect(input.exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('renders correct chevron icon based on collapsed prop', async () => {
|
||||
const wrapper = mountHeader({ collapsed: false })
|
||||
const expandedIcon = wrapper.get('i')
|
||||
@@ -222,16 +207,6 @@ describe('NodeHeader.vue', () => {
|
||||
expect(directive).toBeTruthy()
|
||||
})
|
||||
|
||||
it('disables tooltip when in readonly mode', () => {
|
||||
const wrapper = mountHeader({
|
||||
readonly: true,
|
||||
nodeData: makeNodeData({ type: 'KSampler' })
|
||||
})
|
||||
|
||||
const titleElement = wrapper.find('[data-testid="node-title"]')
|
||||
expect(titleElement.exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('disables tooltip when editing is active', async () => {
|
||||
const wrapper = mountHeader({
|
||||
nodeData: makeNodeData({ type: 'KSampler' })
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
<div class="flex items-center justify-between gap-2.5 relative">
|
||||
<!-- Collapse/Expand Button -->
|
||||
<button
|
||||
v-show="!readonly"
|
||||
class="bg-transparent border-transparent flex items-center lod-toggle"
|
||||
data-testid="node-collapse-button"
|
||||
@click.stop="handleCollapse"
|
||||
@@ -43,7 +42,7 @@
|
||||
data-testid="node-pin-indicator"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!readonly" class="flex items-center lod-toggle shrink-0">
|
||||
<div class="flex items-center lod-toggle shrink-0">
|
||||
<IconButton
|
||||
v-if="isSubgraphNode"
|
||||
size="sm"
|
||||
@@ -85,11 +84,10 @@ import LODFallback from './LODFallback.vue'
|
||||
|
||||
interface NodeHeaderProps {
|
||||
nodeData?: VueNodeData
|
||||
readonly?: boolean
|
||||
collapsed?: boolean
|
||||
}
|
||||
|
||||
const { nodeData, readonly, collapsed } = defineProps<NodeHeaderProps>()
|
||||
const { nodeData, collapsed } = defineProps<NodeHeaderProps>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
collapse: []
|
||||
@@ -118,7 +116,7 @@ const { getNodeDescription, createTooltipConfig } = useNodeTooltips(
|
||||
)
|
||||
|
||||
const tooltipConfig = computed(() => {
|
||||
if (readonly || isEditing.value) {
|
||||
if (isEditing.value) {
|
||||
return { value: '', disabled: true }
|
||||
}
|
||||
const description = getNodeDescription.value
|
||||
@@ -189,9 +187,7 @@ const handleCollapse = () => {
|
||||
}
|
||||
|
||||
const handleDoubleClick = () => {
|
||||
if (!readonly) {
|
||||
isEditing.value = true
|
||||
}
|
||||
isEditing.value = true
|
||||
}
|
||||
|
||||
const handleTitleEdit = (newTitle: string) => {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
:node-type="nodeData?.type || ''"
|
||||
:node-id="nodeData?.id != null ? String(nodeData.id) : ''"
|
||||
:index="getActualInputIndex(input, index)"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +22,6 @@
|
||||
:node-type="nodeData?.type || ''"
|
||||
:node-id="nodeData?.id != null ? String(nodeData.id) : ''"
|
||||
:index="index"
|
||||
:readonly="readonly"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,10 +40,9 @@ import OutputSlot from './OutputSlot.vue'
|
||||
|
||||
interface NodeSlotsProps {
|
||||
nodeData?: VueNodeData
|
||||
readonly?: boolean
|
||||
}
|
||||
|
||||
const { nodeData = null, readonly } = defineProps<NodeSlotsProps>()
|
||||
const { nodeData = null } = defineProps<NodeSlotsProps>()
|
||||
|
||||
// Filter out input slots that have corresponding widgets
|
||||
const filteredInputs = computed(() => {
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
}"
|
||||
:node-id="nodeData?.id != null ? String(nodeData.id) : ''"
|
||||
:index="getWidgetInputIndex(widget)"
|
||||
:readonly="readonly"
|
||||
:dot-only="true"
|
||||
/>
|
||||
</div>
|
||||
@@ -44,7 +43,6 @@
|
||||
v-tooltip.left="widget.tooltipConfig"
|
||||
:widget="widget.simplified"
|
||||
:model-value="widget.value"
|
||||
:readonly="readonly"
|
||||
:node-id="nodeData?.id != null ? String(nodeData.id) : ''"
|
||||
class="flex-1"
|
||||
@update:model-value="widget.updateHandler"
|
||||
@@ -76,10 +74,9 @@ import InputSlot from './InputSlot.vue'
|
||||
|
||||
interface NodeWidgetsProps {
|
||||
nodeData?: VueNodeData
|
||||
readonly?: boolean
|
||||
}
|
||||
|
||||
const { nodeData, readonly } = defineProps<NodeWidgetsProps>()
|
||||
const { nodeData } = defineProps<NodeWidgetsProps>()
|
||||
|
||||
const { shouldHandleNodePointerEvents, forwardEventToCanvas } =
|
||||
useCanvasInteractions()
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
ref="connectionDotRef"
|
||||
:color="slotColor"
|
||||
class="translate-x-1/2"
|
||||
v-on="readonly ? {} : { pointerdown: onPointerDown }"
|
||||
@pointerdown="onPointerDown"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -50,7 +50,6 @@ interface OutputSlotProps {
|
||||
index: number
|
||||
connected?: boolean
|
||||
compatible?: boolean
|
||||
readonly?: boolean
|
||||
dotOnly?: boolean
|
||||
}
|
||||
|
||||
@@ -87,7 +86,7 @@ const slotColor = computed(() => getSlotColor(props.slotData.type))
|
||||
const slotWrapperClass = computed(() =>
|
||||
cn(
|
||||
'lg-slot lg-slot--output flex items-center justify-end group rounded-l-lg h-6',
|
||||
props.readonly ? 'cursor-default opacity-70' : 'cursor-crosshair',
|
||||
'cursor-crosshair',
|
||||
props.dotOnly
|
||||
? 'lg-slot--dot-only justify-center'
|
||||
: 'pl-6 hover:bg-black/5 hover:dark:bg-white/5',
|
||||
@@ -120,7 +119,6 @@ useSlotElementTracking({
|
||||
const { onPointerDown } = useSlotLinkInteraction({
|
||||
nodeId: props.nodeId ?? '',
|
||||
index: props.index,
|
||||
type: 'output',
|
||||
readonly: props.readonly
|
||||
type: 'output'
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user