mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-08 06:30:04 +00:00
- 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>
176 lines
5.9 KiB
Vue
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>
|