mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 10:42:44 +00:00
Misc app mode fixes (#9368)
A working branch of smaller app mode fixes. Can be merged at any time and I'll make a new branch. - Selected inputs and outputs can now be re-ordered when clicking on label text - 3d outputs once again display correctly - Some padding has been added to the side so that control buttons don't overlap with the floating app sidebar controls - A "Share" button placeholder has been added to the menu, but is disabled - Adds a workaround for canvas read_only state being disabled when 'space' is pressed. - This one is particularly hacky, and can be pulled out if problematic - Fix download all only downloading the first output ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9368-Misc-app-mode-fixes-3196d73d365081eab02ad1e693784707) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -33,12 +33,12 @@ const entries = computed(() => {
|
||||
</script>
|
||||
<template>
|
||||
<div class="p-2 my-2 rounded-lg flex items-center-safe gap-2">
|
||||
<span
|
||||
class="mr-auto flex-[4_1_0%] max-w-max min-w-0 truncate"
|
||||
<div
|
||||
class="mr-auto flex-[4_1_0%] max-w-max min-w-0 truncate drag-handle inline"
|
||||
v-text="title"
|
||||
/>
|
||||
<span
|
||||
class="flex-[2_1_0%] max-w-max min-w-0 truncate text-muted-foreground text-end"
|
||||
<div
|
||||
class="flex-[2_1_0%] max-w-max min-w-0 truncate text-muted-foreground text-end drag-handle inline"
|
||||
v-text="subTitle"
|
||||
/>
|
||||
<Popover :entries>
|
||||
|
||||
@@ -187,6 +187,15 @@ export function useWorkflowActionsMenu(
|
||||
visible: isRoot
|
||||
})
|
||||
|
||||
addItem({
|
||||
id: 'share',
|
||||
label: t('menuLabels.Share'),
|
||||
icon: 'icon-[comfy--send]',
|
||||
command: async () => {},
|
||||
disabled: true,
|
||||
visible: isRoot
|
||||
})
|
||||
|
||||
addItem({
|
||||
id: 'enter-app-mode',
|
||||
label: t('breadcrumbsMenu.enterAppMode'),
|
||||
|
||||
@@ -1280,6 +1280,7 @@
|
||||
"Duplicate Current Workflow": "Duplicate Current Workflow",
|
||||
"Export": "Export",
|
||||
"Export (API)": "Export (API)",
|
||||
"Share": "Share",
|
||||
"Convert Selection to Subgraph": "Convert Selection to Subgraph",
|
||||
"Edit Subgraph Widgets": "Edit Subgraph Widgets",
|
||||
"Exit Subgraph": "Exit Subgraph",
|
||||
|
||||
@@ -45,7 +45,8 @@ const props = defineProps<{
|
||||
|
||||
defineEmits<{ navigateAssets: [] }>()
|
||||
|
||||
const jobFinishedQueue = ref(true)
|
||||
//NOTE: due to batching, will never be greater than 2
|
||||
const pendingJobQueues = ref(0)
|
||||
const { ready: jobToastTimeout, start: resetJobToastTimeout } = useTimeout(
|
||||
8000,
|
||||
{ controls: true, immediate: false }
|
||||
@@ -133,9 +134,8 @@ const partitionedNodes = computed(() => {
|
||||
//TODO: refactor out of this file.
|
||||
//code length is small, but changes should propagate
|
||||
async function runButtonClick(e: Event) {
|
||||
if (!jobFinishedQueue.value) return
|
||||
try {
|
||||
jobFinishedQueue.value = false
|
||||
pendingJobQueues.value += 1
|
||||
resetJobToastTimeout()
|
||||
const isShiftPressed = 'shiftKey' in e && e.shiftKey
|
||||
const commandId = isShiftPressed
|
||||
@@ -155,7 +155,7 @@ async function runButtonClick(e: Event) {
|
||||
})
|
||||
} finally {
|
||||
//TODO: Error state indicator for failed queue?
|
||||
jobFinishedQueue.value = true
|
||||
pendingJobQueues.value -= 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ defineExpose({ runButtonClick })
|
||||
</template>
|
||||
</section>
|
||||
<Teleport
|
||||
v-if="!jobToastTimeout || !jobFinishedQueue"
|
||||
v-if="!jobToastTimeout || pendingJobQueues > 0"
|
||||
defer
|
||||
:disabled="mobile"
|
||||
:to="toastTo"
|
||||
@@ -252,7 +252,7 @@ defineExpose({ runButtonClick })
|
||||
<div
|
||||
class="bg-base-foreground md:bg-secondary-background text-base-background md:text-base-foreground rounded-lg flex h-10 md:h-8 p-1 pr-2 gap-2 items-center"
|
||||
>
|
||||
<template v-if="jobFinishedQueue">
|
||||
<template v-if="pendingJobQueues === 0">
|
||||
<i
|
||||
class="icon-[lucide--check] size-5 not-md:bg-success-background"
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { defineAsyncComponent, 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 { getOutputAssetMetadata } from '@/platform/assets/schemas/assetMetadataSchema'
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { extractWorkflowFromAsset } from '@/platform/workflow/utils/workflowExtractionUtil'
|
||||
@@ -16,9 +16,8 @@ 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 OutputHistory from '@/renderer/extensions/linearMode/OutputHistory.vue'
|
||||
import { useOutputHistory } from '@/renderer/extensions/linearMode/useOutputHistory'
|
||||
import type { OutputSelection } from '@/renderer/extensions/linearMode/linearModeTypes'
|
||||
// Lazy-loaded to avoid pulling THREE.js into the main bundle
|
||||
const Preview3d = () => import('@/renderer/extensions/linearMode/Preview3d.vue')
|
||||
import VideoPreview from '@/renderer/extensions/linearMode/VideoPreview.vue'
|
||||
import { getMediaType } from '@/renderer/extensions/linearMode/mediaTypes'
|
||||
import { app } from '@/scripts/app'
|
||||
@@ -28,13 +27,19 @@ import { useQueueStore } from '@/stores/queueStore'
|
||||
import type { ResultItemImpl } from '@/stores/queueStore'
|
||||
import { collectAllNodes } from '@/utils/graphTraversalUtil'
|
||||
import { executeWidgetsCallback } from '@/utils/litegraphUtil'
|
||||
import { useAppMode } from '@/composables/useAppMode'
|
||||
|
||||
// Lazy-loaded to avoid pulling THREE.js into the main bundle
|
||||
const Preview3d = defineAsyncComponent(
|
||||
() => import('@/renderer/extensions/linearMode/Preview3d.vue')
|
||||
)
|
||||
|
||||
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
|
||||
@@ -54,8 +59,7 @@ function handleSelection(sel: OutputSelection) {
|
||||
}
|
||||
|
||||
function downloadAsset(item?: AssetItem) {
|
||||
const user_metadata = getOutputAssetMetadata(item?.user_metadata)
|
||||
for (const output of user_metadata?.allOutputs ?? [])
|
||||
for (const output of allOutputs(item))
|
||||
downloadFile(output.url, output.filename)
|
||||
}
|
||||
|
||||
@@ -127,7 +131,7 @@ async function rerun(e: Event) {
|
||||
{
|
||||
icon: 'icon-[lucide--download]',
|
||||
label: t('linearMode.downloadAll'),
|
||||
command: () => downloadAsset(selectedItem!)
|
||||
command: () => downloadAsset(selectedItem)
|
||||
},
|
||||
{ separator: true },
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ watch([containerRef, () => modelUrl], async () => {
|
||||
<template>
|
||||
<div
|
||||
ref="containerRef"
|
||||
class="relative w-full h-full"
|
||||
class="relative w-full md:w-[calc(100%-150px)] h-full self-center"
|
||||
@mouseenter="viewer.handleMouseEnter"
|
||||
@mouseleave="viewer.handleMouseLeave"
|
||||
@resize="viewer.handleResize"
|
||||
|
||||
@@ -29,5 +29,6 @@ export const mediaTypes: Record<string, StatItem> = {
|
||||
export function getMediaType(output?: ResultItemImpl) {
|
||||
if (!output) return ''
|
||||
if (output.isVideo) return 'video'
|
||||
if (output.isImage) return 'images'
|
||||
return output.mediaType
|
||||
}
|
||||
|
||||
@@ -215,7 +215,9 @@
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
<template v-if="!isCollapsed && nodeData.resizable !== false">
|
||||
<template
|
||||
v-if="!isCollapsed && nodeData.resizable !== false && !isSelectMode"
|
||||
>
|
||||
<div
|
||||
v-for="handle in RESIZE_HANDLES"
|
||||
:key="handle.corner"
|
||||
|
||||
@@ -75,9 +75,20 @@ export const useAppModeStore = defineStore('appMode', () => {
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
let unwatch: () => void | undefined
|
||||
watch(
|
||||
() => mode.value === 'builder:select',
|
||||
(inSelect) => (getCanvas().read_only = inSelect)
|
||||
(inSelect) => {
|
||||
const { state } = getCanvas()
|
||||
if (!state) return
|
||||
state.readOnly = inSelect
|
||||
unwatch?.()
|
||||
if (inSelect)
|
||||
unwatch = watch(
|
||||
() => state.readOnly,
|
||||
() => (state.readOnly = true)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
function enterBuilder() {
|
||||
|
||||
Reference in New Issue
Block a user