Files
ComfyUI_frontend/src/renderer/extensions/linearMode/LinearPreview.vue
AustinMroz 7a01be388f More app fixes (#9432)
- Increased the z-index on app mode outputs so that they display above a
zoomed image
- The "view job" button on the job queued toast in mobile app mode will
take you to outputs instead of assets
- Image previews now have a minimum zoom of ~20% and a maximum zoom of
~50x
- The enter panel in linear mode now has a minimum size of ~1/5th screen
size
- In arrange mode, dragging to rearrange inputs will no longer cause a
horizontal scrollbar to appear.
- Videos will now display the first frame instead of a generic video
icon
- Muted/Bypassed nodes can no longer be selected as inputs/outputs, or
be displayed when in app mode.
- Linked input can no longer be selected or displayed
- Adds a share workflow button in app mode and wires up the existing
context menu

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9432-More-app-fixes-31a6d73d365081509cd0ea74bfdc9b95)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
2026-03-06 13:41:52 -08:00

176 lines
5.9 KiB
Vue

<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { downloadFile } from '@/base/common/downloadUtil'
import Popover from '@/components/ui/Popover.vue'
import Button from '@/components/ui/button/Button.vue'
import { useAppMode } from '@/composables/useAppMode'
import { useMediaAssetActions } from '@/platform/assets/composables/useMediaAssetActions'
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 LatentPreview from '@/renderer/extensions/linearMode/LatentPreview.vue'
import LinearWelcome from '@/renderer/extensions/linearMode/LinearWelcome.vue'
import LinearArrange from '@/renderer/extensions/linearMode/LinearArrange.vue'
import LinearFeedback from '@/renderer/extensions/linearMode/LinearFeedback.vue'
import MediaOutputPreview from '@/renderer/extensions/linearMode/MediaOutputPreview.vue'
import OutputHistory from '@/renderer/extensions/linearMode/OutputHistory.vue'
import { useOutputHistory } from '@/renderer/extensions/linearMode/useOutputHistory'
import type { OutputSelection } from '@/renderer/extensions/linearMode/linearModeTypes'
import { app } from '@/scripts/app'
import { useCommandStore } from '@/stores/commandStore'
import { useExecutionStore } from '@/stores/executionStore'
import { useQueueStore } from '@/stores/queueStore'
import type { ResultItemImpl } from '@/stores/queueStore'
const { t } = useI18n()
const commandStore = useCommandStore()
const executionStore = useExecutionStore()
const mediaActions = useMediaAssetActions()
const queueStore = useQueueStore()
const { isBuilderMode, isArrangeMode } = useAppMode()
const { allOutputs } = useOutputHistory()
const { runButtonClick, mobile, typeformWidgetId } = defineProps<{
runButtonClick?: (e: Event) => void
mobile?: boolean
typeformWidgetId?: string
}>()
const selectedItem = ref<AssetItem>()
const selectedOutput = ref<ResultItemImpl>()
const canShowPreview = ref(true)
const latentPreview = ref<string>()
function handleSelection(sel: OutputSelection) {
selectedItem.value = sel.asset
selectedOutput.value = sel.output
canShowPreview.value = sel.canShowPreview
latentPreview.value = sel.latentPreviewUrl
}
function downloadAsset(item?: AssetItem) {
for (const output of allOutputs(item))
downloadFile(output.url, output.filename)
}
async function loadWorkflow(item: AssetItem | undefined) {
if (!item) return
const { workflow } = await extractWorkflowFromAsset(item)
if (!workflow) return
if (workflow.id !== app.rootGraph.id) return app.loadGraphData(workflow)
//update graph to new version, set old to top of undo queue
const changeTracker = useWorkflowStore().activeWorkflow?.changeTracker
if (!changeTracker) return app.loadGraphData(workflow)
changeTracker.redoQueue = []
await changeTracker.updateState([workflow], changeTracker.undoQueue)
}
async function rerun(e: Event) {
if (!runButtonClick) return
await loadWorkflow(selectedItem.value)
runButtonClick(e)
}
</script>
<template>
<section
v-if="selectedItem || selectedOutput || !executionStore.isIdle"
data-testid="linear-output-info"
class="flex w-full flex-wrap justify-center gap-2 p-4 text-sm tabular-nums md:z-10"
>
<template v-if="selectedItem">
<Button size="md" @click="rerun">
{{ t('linearMode.rerun') }}
<i class="icon-[lucide--refresh-cw]" />
</Button>
<Button size="md" @click="() => loadWorkflow(selectedItem)">
{{ t('linearMode.reuseParameters') }}
<i class="icon-[lucide--list-restart]" />
</Button>
<div class="mx-1 border-r border-border-subtle" />
</template>
<Button
v-if="selectedOutput"
v-tooltip.top="t('g.download')"
size="icon"
:aria-label="t('g.download')"
@click="
() => {
if (selectedOutput?.url) downloadFile(selectedOutput.url)
}
"
>
<i class="icon-[lucide--download]" />
</Button>
<Button
v-if="!executionStore.isIdle && !selectedItem"
variant="destructive"
@click="commandStore.execute('Comfy.Interrupt')"
>
<i class="icon-[lucide--x]" />
{{ t('linearMode.cancelThisRun') }}
</Button>
<Popover
v-if="selectedItem"
:entries="[
...(allOutputs(selectedItem).length > 1
? [
{
icon: 'icon-[lucide--download]',
label: t('linearMode.downloadAll', {
count: allOutputs(selectedItem).length
}),
command: () => downloadAsset(selectedItem)
},
{ separator: true }
]
: []),
{
icon: 'icon-[lucide--trash-2]',
label: t('queue.jobMenu.deleteAsset'),
command: () => mediaActions.deleteAssets(selectedItem!)
}
]"
/>
</section>
<ImagePreview
v-if="canShowPreview && latentPreview"
:mobile
:src="latentPreview"
/>
<MediaOutputPreview
v-else-if="selectedOutput"
:output="selectedOutput"
:mobile
/>
<LatentPreview v-else-if="queueStore.runningTasks.length > 0" />
<LinearArrange v-else-if="isArrangeMode" />
<LinearWelcome v-else />
<div
v-if="!mobile"
class="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center"
>
<LinearFeedback
v-if="typeformWidgetId"
side="left"
:widget-id="typeformWidgetId"
/>
<OutputHistory
v-if="!isBuilderMode"
class="z-10 min-w-0"
@update-selection="handleSelection"
/>
<LinearFeedback
v-if="typeformWidgetId"
side="right"
:widget-id="typeformWidgetId"
/>
</div>
<OutputHistory
v-else-if="!isBuilderMode"
@update-selection="handleSelection"
/>
</template>