Linear mode bug fixes (#8054)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8054-Linear-mode-bug-fixes-2e86d73d365081ed8d75d6e2af679f6c)
by [Unito](https://www.unito.io)
This commit is contained in:
AustinMroz
2026-01-14 20:49:13 -08:00
committed by GitHub
parent ed3c855eb6
commit efc242b968
9 changed files with 79 additions and 14 deletions

View File

@@ -4,7 +4,7 @@
variant="textonly"
@click="toggleHelpCenter"
>
{{ $t('menu.helpAndFeedback') }}
<div class="not-md:hidden">{{ $t('menu.helpAndFeedback') }}</div>
<i class="icon-[lucide--circle-help] ml-0.5" />
<span
v-if="shouldShowRedDot"

View File

@@ -1234,7 +1234,7 @@ export function useCoreCommands(): ComfyCommand[] {
{
id: 'Comfy.ToggleLinear',
icon: 'pi pi-database',
label: 'toggle linear mode',
label: 'Toggle Simple Mode',
function: () => {
const newMode = !canvasStore.linearMode
app.rootGraph.extra.linearMode = newMode

View File

@@ -276,7 +276,7 @@
"label": "Help Center"
},
"Comfy_ToggleLinear": {
"label": "toggle linear mode"
"label": "Toggle Simple Mode"
},
"Comfy_ToggleQPOV2": {
"label": "Toggle Queue Panel V2"

View File

@@ -1187,7 +1187,7 @@
"Experimental: Enable AssetAPI": "Experimental: Enable AssetAPI",
"Canvas Performance": "Canvas Performance",
"Help Center": "Help Center",
"toggle linear mode": "toggle simple mode",
"toggle linear mode": "Toggle Simple Mode",
"Toggle Queue Panel V2": "Toggle Queue Panel V2",
"Toggle Theme (Dark/Light)": "Toggle Theme (Dark/Light)",
"Undo": "Undo",
@@ -2494,7 +2494,7 @@
"linearMode": "Simple Mode",
"beta": "Beta - Give Feedback",
"graphMode": "Graph Mode",
"dragAndDropImage": "Drag and drop an image",
"dragAndDropImage": "Click to browse or drag an image",
"runCount": "Run count:",
"rerun": "Rerun",
"reuseParameters": "Reuse Parameters",

View File

@@ -23,6 +23,7 @@ import { useCommandStore } from '@/stores/commandStore'
import { useExecutionStore } from '@/stores/executionStore'
import { useQueueSettingsStore } from '@/stores/queueStore'
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
import { cn } from '@/utils/tailwindUtil'
const commandStore = useCommandStore()
const executionStore = useExecutionStore()
@@ -55,13 +56,16 @@ function nodeToNodeData(node: LGraphNode) {
? undefined
: {
iconClass: 'icon-[lucide--image]',
label: t('linearMode.dragAndDropImage')
label: t('linearMode.dragAndDropImage'),
onClick: () => node.widgets?.[1]?.callback?.(undefined)
}
const nodeData = extractVueNodeData(node)
for (const widget of nodeData.widgets ?? []) widget.slotMetadata = undefined
return {
...nodeData,
//note lastNodeErrors uses exeuctionid, node.id is execution for root
hasErrors: !!executionStore.lastNodeErrors?.[node.id],
dropIndicator,
onDragDrop: node.onDragDrop,
@@ -69,13 +73,18 @@ function nodeToNodeData(node: LGraphNode) {
}
}
const partitionedNodes = computed(() => {
return partition(
const parts = partition(
graphNodes.value
.filter((node) => node.mode === 0 && node.widgets?.length)
.map(nodeToNodeData)
.reverse(),
(node) => ['MarkdownNote', 'Note'].includes(node.type)
)
for (const noteNode of parts[0]) {
for (const widget of noteNode.widgets ?? [])
widget.options = { ...widget.options, read_only: true }
}
return parts
})
const batchCountWidget: SimplifiedWidget<number> = {
@@ -165,7 +174,7 @@ defineExpose({ runButtonClick })
<Popover
v-if="partitionedNodes[0].length"
align="start"
class="overflow-y-auto overflow-x-clip max-h-(--reka-popover-content-available-height)"
class="overflow-y-auto overflow-x-clip max-h-(--reka-popover-content-available-height) z-100"
:reference="notesTo"
side="left"
:to="notesTo"
@@ -217,7 +226,13 @@ defineExpose({ runButtonClick })
>
<NodeWidgets
:node-data
class="py-3 gap-y-4 **:[.col-span-2]:grid-cols-1 text-sm **:[.p-floatlabel]:h-35 rounded-lg"
:class="
cn(
'py-3 gap-y-3 **:[.col-span-2]:grid-cols-1 not-has-[textarea]:flex-0 rounded-lg',
nodeData.hasErrors &&
'ring-2 ring-inset ring-node-stroke-error'
)
"
:style="{ background: applyLightThemeColor(nodeData.bgcolor) }"
/>
</DropZone>

View File

@@ -2,7 +2,6 @@
import { computed } from 'vue'
import { downloadFile } from '@/base/common/downloadUtil'
import Load3dViewerContent from '@/components/load3d/Load3dViewerContent.vue'
import Popover from '@/components/ui/Popover.vue'
import Button from '@/components/ui/button/Button.vue'
import { d, t } from '@/i18n'
@@ -12,6 +11,7 @@ import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { extractWorkflowFromAsset } from '@/platform/workflow/utils/workflowExtractionUtil'
import ImagePreview from '@/renderer/extensions/linearMode/ImagePreview.vue'
import Preview3d from '@/renderer/extensions/linearMode/Preview3d.vue'
import VideoPreview from '@/renderer/extensions/linearMode/VideoPreview.vue'
import {
getMediaType,
@@ -172,7 +172,7 @@ async function rerun(e: Event) {
class="w-full max-w-128 m-auto my-12 overflow-y-auto"
v-text="selectedOutput!.url"
/>
<Load3dViewerContent
<Preview3d
v-else-if="getMediaType(selectedOutput) === '3d'"
:model-url="selectedOutput!.url"
/>

View File

@@ -277,6 +277,7 @@ useEventListener(document.body, 'keydown', (e: KeyboardEvent) => {
'border-2'
)
"
@click="selectedIndex = [index, key]"
>
<i
:class="

View File

@@ -0,0 +1,44 @@
<script setup lang="ts">
import { useTemplateRef, watch } from 'vue'
import AnimationControls from '@/components/load3d/controls/AnimationControls.vue'
import { useLoad3dViewer } from '@/composables/useLoad3dViewer'
const { modelUrl } = defineProps<{
modelUrl: string
}>()
const containerRef = useTemplateRef('containerRef')
const viewer = useLoad3dViewer()
watch([containerRef, () => modelUrl], async () => {
if (!containerRef.value || !modelUrl) return
await viewer.initializeStandaloneViewer(containerRef.value, modelUrl)
})
//TODO: refactor to add control buttons
</script>
<template>
<div
ref="containerRef"
class="relative w-full h-full"
@mouseenter="viewer.handleMouseEnter"
@mouseleave="viewer.handleMouseLeave"
@resize="viewer.handleResize"
>
<div class="pointer-events-none absolute top-0 left-0 size-full">
<AnimationControls
v-if="viewer.animations.value && viewer.animations.value.length > 0"
v-model:animations="viewer.animations.value"
v-model:playing="viewer.playing.value"
v-model:selected-speed="viewer.selectedSpeed.value"
v-model:selected-animation="viewer.selectedAnimation.value"
v-model:animation-progress="viewer.animationProgress.value"
v-model:animation-duration="viewer.animationDuration.value"
@seek="viewer.handleSeek"
/>
</div>
</div>
</template>

View File

@@ -44,7 +44,10 @@ const bottomRightRef = useTemplateRef('bottomRightRef')
const linearWorkflowRef = useTemplateRef('linearWorkflowRef')
</script>
<template>
<div class="absolute w-full h-full">
<div
class="absolute w-full h-full"
@wheel.capture="(e: WheelEvent) => outputHistoryRef?.onWheel(e)"
>
<div class="workflow-tabs-container pointer-events-auto h-9.5 w-full">
<div class="flex h-full items-center">
<WorkflowTabs />
@@ -82,7 +85,10 @@ const linearWorkflowRef = useTemplateRef('linearWorkflowRef')
/>
<LinearControls ref="linearWorkflowRef" mobile />
<div class="text-base-foreground flex items-center gap-4 justify-end m-4">
<div v-text="t('linearMode.beta')" />
<a
href="https://form.typeform.com/to/gmVqFi8l"
v-text="t('linearMode.beta')"
/>
<TypeformPopoverButton data-tf-widget="gmVqFi8l" />
</div>
</div>
@@ -122,7 +128,6 @@ const linearWorkflowRef = useTemplateRef('linearWorkflowRef')
id="linearCenterPanel"
:size="98"
class="flex flex-col min-w-min gap-4 mx-2 px-10 pt-8 pb-4 relative text-muted-foreground outline-none"
@wheel.capture="(e: WheelEvent) => outputHistoryRef?.onWheel(e)"
>
<LinearPreview
:latent-preview="