mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-04 07:00:23 +00:00
- Re-enables the share button in Linear Mode and have it export the current workflow - Not as nice as having it copy an actual URL, but good enough for the interim and it help with dead space - Display the Media Assets Panel on the left hand side to replace the removed Queue Panel <img width="806" alt="image" src="https://github.com/user-attachments/assets/93786dfa-8fbb-4368-8594-b9c98bbeb79e" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6794-Share-button-and-Assets-Panel-in-Linear-Mode-2b26d73d36508178aef9ededa38d47f1) by [Unito](https://www.unito.io)
197 lines
6.7 KiB
Vue
197 lines
6.7 KiB
Vue
<script setup lang="ts">
|
|
import { storeToRefs } from 'pinia'
|
|
import Button from 'primevue/button'
|
|
import Splitter from 'primevue/splitter'
|
|
import SplitterPanel from 'primevue/splitterpanel'
|
|
import { computed } from 'vue'
|
|
|
|
import ExtensionSlot from '@/components/common/ExtensionSlot.vue'
|
|
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
|
|
import LoginButton from '@/components/topbar/LoginButton.vue'
|
|
import TopbarBadges from '@/components/topbar/TopbarBadges.vue'
|
|
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
|
|
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
|
import {
|
|
isValidWidgetValue,
|
|
safeWidgetMapper
|
|
} from '@/composables/graph/useGraphNodeManager'
|
|
import { useAssetsSidebarTab } from '@/composables/sidebarTabs/useAssetsSidebarTab'
|
|
import { t } from '@/i18n'
|
|
import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
|
|
import { useTelemetry } from '@/platform/telemetry'
|
|
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
|
|
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
|
import NodeWidgets from '@/renderer/extensions/vueNodes/components/NodeWidgets.vue'
|
|
import WidgetInputNumberInput from '@/renderer/extensions/vueNodes/widgets/components/WidgetInputNumber.vue'
|
|
import { app } from '@/scripts/app'
|
|
import { useCommandStore } from '@/stores/commandStore'
|
|
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
|
|
import { useQueueSettingsStore } from '@/stores/queueStore'
|
|
import { isElectron } from '@/utils/envUtil'
|
|
|
|
const nodeOutputStore = useNodeOutputStore()
|
|
const commandStore = useCommandStore()
|
|
const nodeDatas = computed(() => {
|
|
function nodeToNodeData(node: LGraphNode) {
|
|
const mapper = safeWidgetMapper(node, new Map())
|
|
const widgets =
|
|
node.widgets?.map((widget) => {
|
|
const safeWidget = mapper(widget)
|
|
safeWidget.callback = function (value) {
|
|
if (!isValidWidgetValue(value)) return
|
|
widget.value = value ?? undefined
|
|
return widget.callback?.(widget.value)
|
|
}
|
|
return safeWidget
|
|
}) ?? []
|
|
//Only widgets is actually used
|
|
return {
|
|
id: `${node.id}`,
|
|
title: node.title,
|
|
type: node.type,
|
|
mode: 0,
|
|
selected: false,
|
|
executing: false,
|
|
widgets
|
|
}
|
|
}
|
|
return app.graph.nodes
|
|
.filter((node) => node.mode === 0 && node.widgets?.length)
|
|
.map(nodeToNodeData)
|
|
})
|
|
const { isLoggedIn } = useCurrentUser()
|
|
const isDesktop = isElectron()
|
|
|
|
const batchCountWidget = {
|
|
options: { step2: 1, precision: 1, min: 1, max: 100 },
|
|
value: 1,
|
|
name: t('Number of generations'),
|
|
type: 'number'
|
|
}
|
|
|
|
const { batchCount } = storeToRefs(useQueueSettingsStore())
|
|
|
|
//TODO: refactor out of this file.
|
|
//code length is small, but changes should propagate
|
|
async function runButtonClick(e: Event) {
|
|
const isShiftPressed = 'shiftKey' in e && e.shiftKey
|
|
const commandId = isShiftPressed
|
|
? 'Comfy.QueuePromptFront'
|
|
: 'Comfy.QueuePrompt'
|
|
|
|
useTelemetry()?.trackUiButtonClicked({
|
|
button_id: 'queue_run_linear'
|
|
})
|
|
if (batchCount.value > 1) {
|
|
useTelemetry()?.trackUiButtonClicked({
|
|
button_id: 'queue_run_multiple_batches_submitted'
|
|
})
|
|
}
|
|
await commandStore.execute(commandId, {
|
|
metadata: {
|
|
subscribe_to_run: false,
|
|
trigger_source: 'button'
|
|
}
|
|
})
|
|
}
|
|
function openFeedback() {
|
|
//TODO: Does not link to a linear specific feedback section
|
|
window.open(
|
|
'https://support.comfy.org/hc/en-us/requests/new?ticket_form_id=40026345549204',
|
|
'_blank',
|
|
'noopener,noreferrer'
|
|
)
|
|
}
|
|
</script>
|
|
<template>
|
|
<div class="absolute w-full h-full">
|
|
<div class="workflow-tabs-container pointer-events-auto h-9.5 w-full">
|
|
<div class="flex h-full items-center">
|
|
<WorkflowTabs />
|
|
<TopbarBadges />
|
|
</div>
|
|
</div>
|
|
<Splitter
|
|
class="h-[calc(100%-38px)] w-full bg-comfy-menu-secondary-bg"
|
|
:pt="{ gutter: { class: 'bg-transparent w-4 -mx-3' } }"
|
|
>
|
|
<SplitterPanel :size="1" class="min-w-min bg-comfy-menu-bg">
|
|
<div
|
|
class="sidebar-content-container h-full w-full overflow-x-hidden overflow-y-auto border-r-1 border-node-component-border"
|
|
>
|
|
<ExtensionSlot :extension="useAssetsSidebarTab()" />
|
|
</div>
|
|
</SplitterPanel>
|
|
<SplitterPanel
|
|
:size="98"
|
|
class="flex flex-row overflow-y-auto flex-wrap min-w-min gap-4 m-4"
|
|
>
|
|
<img
|
|
v-for="previewUrl in nodeOutputStore.latestOutput"
|
|
:key="previewUrl"
|
|
class="pointer-events-none object-contain flex-1 max-h-full"
|
|
:src="previewUrl"
|
|
/>
|
|
<img
|
|
v-if="nodeOutputStore.latestOutput.length === 0"
|
|
class="pointer-events-none object-contain flex-1 max-h-full brightness-50 opacity-10"
|
|
src="/assets/images/comfy-logo-mono.svg"
|
|
/>
|
|
</SplitterPanel>
|
|
<SplitterPanel :size="1" class="flex flex-col gap-1 p-1 min-w-min">
|
|
<div
|
|
class="actionbar-container flex h-12 items-center rounded-lg border border-[var(--interface-stroke)] p-2 gap-2 bg-comfy-menu-bg justify-end"
|
|
>
|
|
<Button
|
|
:label="t('g.feedback')"
|
|
severity="secondary"
|
|
@click="openFeedback"
|
|
/>
|
|
<Button
|
|
:label="t('linearMode.openWorkflow')"
|
|
severity="secondary"
|
|
class="min-w-max"
|
|
icon="icon-[comfy--workflow]"
|
|
icon-pos="right"
|
|
@click="useCanvasStore().linearMode = false"
|
|
/>
|
|
<Button
|
|
:label="t('linearMode.share')"
|
|
severity="contrast"
|
|
@click="useWorkflowService().exportWorkflow('workflow', 'workflow')"
|
|
/>
|
|
<CurrentUserButton v-if="isLoggedIn" />
|
|
<LoginButton v-else-if="isDesktop" />
|
|
</div>
|
|
<div
|
|
class="rounded-lg border p-2 gap-2 h-full border-[var(--interface-stroke)] bg-comfy-menu-bg flex flex-col"
|
|
>
|
|
<div
|
|
class="grow-1 flex justify-start flex-col overflow-y-auto contain-size *:max-h-100"
|
|
>
|
|
<NodeWidgets
|
|
v-for="nodeData of nodeDatas"
|
|
:key="nodeData.id"
|
|
:node-data
|
|
class="border-b-1 border-node-component-border pt-1 pb-2 last:border-none"
|
|
/>
|
|
</div>
|
|
<div class="p-4 pb-0 border-t border-node-component-border">
|
|
<WidgetInputNumberInput
|
|
v-model="batchCount"
|
|
:widget="batchCountWidget"
|
|
class="*:[.min-w-56]:basis-0"
|
|
/>
|
|
<Button
|
|
:label="t('menu.run')"
|
|
class="w-full mt-4"
|
|
icon="icon-[lucide--play]"
|
|
@click="runButtonClick"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</SplitterPanel>
|
|
</Splitter>
|
|
</div>
|
|
</template>
|